1 /*** dadd.c -- perform simple date arithmetic, date plus duration
2  *
3  * Copyright (C) 2011-2016 Sebastian Freundt
4  *
5  * Author:  Sebastian Freundt <freundt@ga-group.nl>
6  *
7  * This file is part of dateutils.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * 3. Neither the name of the author nor the names of any contributors
21  *    may be used to endorse or promote products derived from this
22  *    software without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27  * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
31  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
32  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
33  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
34  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35  *
36  **/
37 #if defined HAVE_CONFIG_H
38 # include "config.h"
39 #endif	/* HAVE_CONFIG_H */
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <stdint.h>
43 #include <sys/time.h>
44 #include <time.h>
45 
46 #include "dt-core.h"
47 #include "dt-io.h"
48 #include "dt-core-tz-glue.h"
49 #include "dt-locale.h"
50 #include "prchunk.h"
51 
52 const char *prog = "dadd";
53 
54 
55 static bool
durs_only_d_p(struct dt_dtdur_s dur[],size_t ndur)56 durs_only_d_p(struct dt_dtdur_s dur[], size_t ndur)
57 {
58 	for (size_t i = 0; i < ndur; i++) {
59 		if (dur[i].durtyp >= (dt_dtdurtyp_t)DT_NDURTYP) {
60 			return false;
61 		}
62 	}
63 	return true;
64 }
65 
66 static struct dt_dt_s
dadd_add(struct dt_dt_s d,struct dt_dtdur_s dur[],size_t ndur)67 dadd_add(struct dt_dt_s d, struct dt_dtdur_s dur[], size_t ndur)
68 {
69 	for (size_t i = 0; i < ndur; i++) {
70 		d = dt_dtadd(d, dur[i]);
71 	}
72 	return d;
73 }
74 
75 
76 struct mass_add_clo_s {
77 	void *pctx;
78 	const struct grep_atom_soa_s *gra;
79 	struct __strpdtdur_st_s st;
80 	struct dt_dt_s rd;
81 	zif_t fromz;
82 	zif_t hackz;
83 	zif_t z;
84 	const char *ofmt;
85 	int sed_mode_p;
86 	int quietp;
87 };
88 
89 static int
proc_line(const struct mass_add_clo_s * clo,char * line,size_t llen)90 proc_line(const struct mass_add_clo_s *clo, char *line, size_t llen)
91 {
92 	struct dt_dt_s d;
93 	char *sp = NULL;
94 	char *ep = NULL;
95 	int rc = 0;
96 
97 	do {
98 		/* check if line matches, */
99 		d = dt_io_find_strpdt2(
100 			line, llen, clo->gra, &sp, &ep, clo->fromz);
101 
102 		if (!dt_unk_p(d)) {
103 			if (UNLIKELY(d.fix) && !clo->quietp) {
104 				rc = 2;
105 			}
106 			/* perform addition now */
107 			d = dadd_add(d, clo->st.durs, clo->st.ndurs);
108 
109 			if (clo->hackz == NULL && clo->fromz != NULL) {
110 				/* fixup zone */
111 				d = dtz_forgetz(d, clo->fromz);
112 			}
113 
114 			if (clo->sed_mode_p) {
115 				__io_write(line, sp - line, stdout);
116 				dt_io_write(d, clo->ofmt, clo->z, '\0');
117 				llen -= (ep - line);
118 				line = ep;
119 			} else {
120 				dt_io_write(d, clo->ofmt, clo->z, '\n');
121 				break;
122 			}
123 		} else if (clo->sed_mode_p) {
124 			line[llen] = '\n';
125 			__io_write(line, llen + 1, stdout);
126 			break;
127 		} else {
128 			/* obviously unmatched, warn about it in non -q mode */
129 			if (!clo->quietp) {
130 				dt_io_warn_strpdt(line);
131 				rc = 2;
132 			}
133 			break;
134 		}
135 	} while (1);
136 	return rc;
137 }
138 
139 static int
mass_add_dur(const struct mass_add_clo_s * clo)140 mass_add_dur(const struct mass_add_clo_s *clo)
141 {
142 /* read lines from stdin
143  * interpret as dates
144  * add to reference duration
145  * output */
146 	size_t lno = 0;
147 	int rc = 0;
148 
149 	for (char *line; prchunk_haslinep(clo->pctx); lno++) {
150 		size_t llen = prchunk_getline(clo->pctx, &line);
151 
152 		rc |= proc_line(clo, line, llen);
153 	}
154 	return rc;
155 }
156 
157 static int
mass_add_d(const struct mass_add_clo_s * clo)158 mass_add_d(const struct mass_add_clo_s *clo)
159 {
160 /* read lines from stdin
161  * interpret as durations
162  * add to reference date
163  * output */
164 	size_t lno = 0;
165 	struct dt_dt_s d;
166 	struct __strpdtdur_st_s st = __strpdtdur_st_initialiser();
167 	int rc = 0;
168 
169 	for (char *line; prchunk_haslinep(clo->pctx); lno++) {
170 		size_t llen;
171 		int has_dur_p  = 1;
172 
173 		llen = prchunk_getline(clo->pctx, &line);
174 
175 		/* check for durations on this line */
176 		do {
177 			if (dt_io_strpdtdur(&st, line) < 0) {
178 				has_dur_p = 0;
179 			}
180 		} while (__strpdtdur_more_p(&st));
181 
182 		/* finish with newline again */
183 		line[llen] = '\n';
184 
185 		if (has_dur_p) {
186 			if (UNLIKELY(clo->rd.fix) && !clo->quietp) {
187 				rc = 2;
188 			}
189 			/* perform addition now */
190 			d = dadd_add(clo->rd, st.durs, st.ndurs);
191 
192 			if (clo->hackz == NULL && clo->fromz != NULL) {
193 				/* fixup zone */
194 				d = dtz_forgetz(d, clo->fromz);
195 			}
196 
197 			/* no sed mode here */
198 			dt_io_write(d, clo->ofmt, clo->z, '\n');
199 		} else if (clo->sed_mode_p) {
200 			__io_write(line, llen + 1, stdout);
201 		} else if (!clo->quietp) {
202 			line[llen] = '\0';
203 			dt_io_warn_strpdt(line);
204 			rc = 2;
205 		}
206 		/* just reset the ndurs slot */
207 		st.ndurs = 0;
208 	}
209 	/* free associated duration resources */
210 	__strpdtdur_free(&st);
211 	return rc;
212 }
213 
214 
215 #include "dadd.yucc"
216 
217 int
main(int argc,char * argv[])218 main(int argc, char *argv[])
219 {
220 	yuck_t argi[1U];
221 	struct dt_dt_s d;
222 	struct __strpdtdur_st_s st = __strpdtdur_st_initialiser();
223 	const char *ofmt;
224 	char **fmt;
225 	size_t nfmt;
226 	int rc = 0;
227 	bool dt_given_p = false;
228 	zif_t fromz = NULL;
229 	zif_t z = NULL;
230 	zif_t hackz = NULL;
231 
232 	if (yuck_parse(argi, argc, argv)) {
233 		rc = 1;
234 		goto out;
235 	} else if (argi->nargs == 0) {
236 		error("Error: DATE or DURATION must be specified\n");
237 		yuck_auto_help(argi);
238 		rc = 1;
239 		goto out;
240 	}
241 	/* init and unescape sequences, maybe */
242 	ofmt = argi->format_arg;
243 	fmt = argi->input_format_args;
244 	nfmt = argi->input_format_nargs;
245 	if (argi->backslash_escapes_flag) {
246 		dt_io_unescape(argi->format_arg);
247 		for (size_t i = 0; i < nfmt; i++) {
248 			dt_io_unescape(fmt[i]);
249 		}
250 	}
251 
252 	if (argi->from_locale_arg) {
253 		setilocale(argi->from_locale_arg);
254 	}
255 	if (argi->locale_arg) {
256 		setflocale(argi->locale_arg);
257 	}
258 
259 	/* try and read the from and to time zones */
260 	if (argi->from_zone_arg) {
261 		fromz = dt_io_zone(argi->from_zone_arg);
262 	}
263 	if (argi->zone_arg) {
264 		z = dt_io_zone(argi->zone_arg);
265 	}
266 	if (argi->base_arg) {
267 		struct dt_dt_s base = dt_strpdt(argi->base_arg, NULL, NULL);
268 		dt_set_base(base);
269 	}
270 
271 	/* sanity checks, decide whether we're a mass date adder
272 	 * or a mass duration adder, or both, a date and durations are
273 	 * present on the command line */
274 	with (const char *inp = argi->args[0U]) {
275 		/* date parsing needed postponing as we need to find out
276 		 * about the durations */
277 		if (!dt_unk_p(dt_io_strpdt(inp, fmt, nfmt, NULL))) {
278 			dt_given_p = true;
279 		}
280 	}
281 
282 	/* check first arg, if it's a date the rest of the arguments are
283 	 * durations, if not, dates must be read from stdin */
284 	for (size_t i = dt_given_p; i < argi->nargs; i++) {
285 		const char *inp = argi->args[i];
286 		do {
287 			if (dt_io_strpdtdur(&st, inp) < 0) {
288 				serror("Error: \
289 cannot parse duration string `%s'", st.istr);
290 				rc = 1;
291 				goto dur_out;
292 			}
293 		} while (__strpdtdur_more_p(&st));
294 	}
295 	/* check if there's only d durations */
296 	hackz = durs_only_d_p(st.durs, st.ndurs) ? NULL : fromz;
297 
298 	/* read the first argument again in light of a completely parsed
299 	 * duration sequence */
300 	if (dt_given_p) {
301 		const char *inp = argi->args[0U];
302 		if (dt_unk_p(d = dt_io_strpdt(inp, fmt, nfmt, hackz))) {
303 			error("\
304 Error: cannot interpret date/time string `%s'", inp);
305 			rc = 1;
306 			goto dur_out;
307 		}
308 	}
309 
310 	/* start the actual work */
311 	if (dt_given_p && st.ndurs) {
312 		if (!dt_unk_p(d = dadd_add(d, st.durs, st.ndurs))) {
313 			if (UNLIKELY(d.fix) && !argi->quiet_flag) {
314 				rc = 2;
315 			}
316 			if (hackz == NULL && fromz != NULL) {
317 				/* fixup zone */
318 				d = dtz_forgetz(d, fromz);
319 			}
320 			dt_io_write(d, ofmt, z, '\n');
321 		} else {
322 			rc = 1;
323 		}
324 
325 	} else if (st.ndurs) {
326 		/* read dates from stdin */
327 		struct grep_atom_s __nstk[16], *needle = __nstk;
328 		size_t nneedle = countof(__nstk);
329 		struct grep_atom_soa_s ndlsoa;
330 		struct mass_add_clo_s clo[1];
331 		void *pctx;
332 
333 		/* no threads reading this stream */
334 		__io_setlocking_bycaller(stdout);
335 
336 		/* lest we overflow the stack */
337 		if (nfmt >= nneedle) {
338 			/* round to the nearest 8-multiple */
339 			nneedle = (nfmt | 7) + 1;
340 			needle = calloc(nneedle, sizeof(*needle));
341 		}
342 		/* and now build the needle */
343 		ndlsoa = build_needle(needle, nneedle, fmt, nfmt);
344 
345 		/* using the prchunk reader now */
346 		if ((pctx = init_prchunk(STDIN_FILENO)) == NULL) {
347 			serror("could not open stdin");
348 			goto ndl_free;
349 		}
350 
351 		/* build the clo and then loop */
352 		clo->pctx = pctx;
353 		clo->gra = &ndlsoa;
354 		clo->st = st;
355 		clo->fromz = fromz;
356 		clo->hackz = hackz;
357 		clo->z = z;
358 		clo->ofmt = ofmt;
359 		clo->sed_mode_p = argi->sed_mode_flag;
360 		clo->quietp = argi->quiet_flag;
361 		while (prchunk_fill(pctx) >= 0) {
362 			rc |= mass_add_dur(clo);
363 		}
364 		/* get rid of resources */
365 		free_prchunk(pctx);
366 	ndl_free:
367 		if (needle != __nstk) {
368 			free(needle);
369 		}
370 
371 	} else {
372 		/* mass-adding durations to reference date */
373 		struct mass_add_clo_s clo[1];
374 		void *pctx;
375 
376 		/* no threads reading this stream */
377 		__io_setlocking_bycaller(stdout);
378 
379 		/* using the prchunk reader now */
380 		if ((pctx = init_prchunk(STDIN_FILENO)) == NULL) {
381 			serror("could not open stdin");
382 			goto dur_out;
383 		}
384 
385 		/* build the clo and then loop */
386 		clo->pctx = pctx;
387 		clo->rd = d;
388 		clo->fromz = fromz;
389 		clo->hackz = hackz;
390 		clo->z = z;
391 		clo->ofmt = ofmt;
392 		clo->sed_mode_p = argi->sed_mode_flag;
393 		clo->quietp = argi->quiet_flag;
394 		while (prchunk_fill(pctx) >= 0) {
395 			rc |= mass_add_d(clo);
396 		}
397 		/* get rid of resources */
398 		free_prchunk(pctx);
399 	}
400 dur_out:
401 	/* free the strpdur status */
402 	__strpdtdur_free(&st);
403 
404 	dt_io_clear_zones();
405 	if (argi->from_locale_arg) {
406 		setilocale(NULL);
407 	}
408 	if (argi->locale_arg) {
409 		setflocale(NULL);
410 	}
411 
412 out:
413 	yuck_free(argi);
414 	return rc;
415 }
416 
417 /* dadd.c ends here */
418