1 /*** ddiff.c -- perform simple date arithmetic, date minus date
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 "date-core-private.h"
47 #include "dt-core-private.h"
48 #include "dt-io.h"
49 #include "dt-locale.h"
50 #include "prchunk.h"
51 
52 #if !defined UNUSED
53 # define UNUSED(_x)	__attribute__((unused)) _x
54 #endif	/* !UNUSED */
55 
56 typedef union {
57 	unsigned int flags;
58 	struct {
59 		unsigned int has_year:1;
60 		unsigned int has_mon:1;
61 		unsigned int has_week:1;
62 		unsigned int has_day:1;
63 		unsigned int has_biz:1;
64 
65 		unsigned int has_hour:1;
66 		unsigned int has_min:1;
67 		unsigned int has_sec:1;
68 		unsigned int has_nano:1;
69 
70 		unsigned int has_tai:1;
71 	};
72 } durfmt_t;
73 
74 const char *prog = "ddiff";
75 
76 
77 static durfmt_t
determine_durfmt(const char * fmt)78 determine_durfmt(const char *fmt)
79 {
80 	durfmt_t res = {0};
81 	dt_dtyp_t special;
82 
83 	if (fmt == NULL) {
84 		/* decide later on */
85 		;
86 	} else if (UNLIKELY((special = __trans_dfmt_special(fmt)) != DT_DUNK)) {
87 		switch (special) {
88 		default:
89 		case DT_DUNK:
90 			break;
91 		case DT_YMD:
92 			res.has_year = 1;
93 			res.has_mon = 1;
94 			res.has_day = 1;
95 			break;
96 		case DT_YMCW:
97 			res.has_year = 1;
98 			res.has_mon = 1;
99 			res.has_week = 1;
100 			res.has_day = 1;
101 			break;
102 		case DT_BIZDA:
103 			res.has_year = 1;
104 			res.has_mon = 1;
105 			res.has_day = 1;
106 			res.has_biz = 1;
107 			break;
108 		case DT_DAISY:
109 			res.has_day = 1;
110 			break;
111 		case DT_BIZSI:
112 			res.has_day = 1;
113 			res.has_biz = 1;
114 			break;
115 		case DT_YWD:
116 			res.has_year = 1;
117 			res.has_week = 1;
118 			res.has_day = 1;
119 			break;
120 		case DT_YD:
121 			res.has_year = 1;
122 			res.has_day = 1;
123 			break;
124 		}
125 
126 		/* all special types have %0H:%0M:%0S */
127 		res.has_hour = 1;
128 		res.has_min = 1;
129 		res.has_sec = 1;
130 	} else {
131 		/* go through the fmt specs */
132 		for (const char *fp = fmt; *fp;) {
133 			const char *fp_sav = fp;
134 			struct dt_spec_s spec = __tok_spec(fp_sav, &fp);
135 
136 			switch (spec.spfl) {
137 			case DT_SPFL_UNK:
138 			default:
139 				/* nothing changes */
140 				break;
141 			case DT_SPFL_N_YEAR:
142 				res.has_year = 1;
143 				break;
144 			case DT_SPFL_N_MON:
145 			case DT_SPFL_S_MON:
146 				res.has_mon = 1;
147 				break;
148 			case DT_SPFL_N_DCNT_MON:
149 				if (spec.bizda) {
150 					res.has_biz = 1;
151 				}
152 			case DT_SPFL_N_DSTD:
153 			case DT_SPFL_N_DCNT_YEAR:
154 				res.has_day = 1;
155 				break;
156 			case DT_SPFL_N_WCNT_MON:
157 			case DT_SPFL_N_WCNT_YEAR:
158 			case DT_SPFL_N_DCNT_WEEK:
159 			case DT_SPFL_S_WDAY:
160 				res.has_week = 1;
161 				break;
162 
163 			case DT_SPFL_N_TSTD:
164 			case DT_SPFL_N_SEC:
165 				if (spec.tai) {
166 					res.has_tai = 1;
167 				}
168 				res.has_sec = 1;
169 				break;
170 			case DT_SPFL_N_HOUR:
171 				res.has_hour = 1;
172 				break;
173 			case DT_SPFL_N_MIN:
174 				res.has_min = 1;
175 				break;
176 			case DT_SPFL_N_NANO:
177 				res.has_nano = 1;
178 				break;
179 			}
180 		}
181 	}
182 	return res;
183 }
184 
185 static dt_dtdurtyp_t
determine_durtype(struct dt_dt_s d1,struct dt_dt_s d2,durfmt_t f)186 determine_durtype(struct dt_dt_s d1, struct dt_dt_s d2, durfmt_t f)
187 {
188 	/* the type-multiplication table looks like:
189 	 *
190 	 * -   D   T   DT
191 	 * D   d   x   d
192 	 * T   x   t   x
193 	 * DT  d   x   s
194 	 *
195 	 * where d means a ddur type, t a tdur type and s is DT_SEXY */
196 
197 	if (UNLIKELY(dt_sandwich_only_t_p(d1) && dt_sandwich_only_t_p(d2))) {
198 		/* time only duration */
199 		;
200 	} else if (dt_sandwich_only_t_p(d1) || dt_sandwich_only_t_p(d2)) {
201 		/* isn't defined */
202 		return (dt_dtdurtyp_t)DT_DURUNK;
203 	} else if (f.has_week && f.has_mon) {
204 		return (dt_dtdurtyp_t)DT_DURYMCW;
205 	} else if (f.has_week && f.has_year) {
206 		return (dt_dtdurtyp_t)DT_DURYWD;
207 	} else if (f.has_mon) {
208 		return (dt_dtdurtyp_t)DT_DURYMD;
209 	} else if (f.has_year && f.has_day) {
210 		return (dt_dtdurtyp_t)DT_DURYD;
211 	} else if (f.has_day && f.has_biz) {
212 		return (dt_dtdurtyp_t)DT_DURBD;
213 	} else if (f.has_year) {
214 		return (dt_dtdurtyp_t)DT_DURYMD;
215 	} else if (dt_sandwich_only_d_p(d1) || dt_sandwich_only_d_p(d2)) {
216 		/* default date-only type */
217 		return (dt_dtdurtyp_t)DT_DURD;
218 	} else if (UNLIKELY(f.has_tai)) {
219 		/* we has tais */
220 		return (dt_dtdurtyp_t)0xffU;
221 	}
222 	/* otherwise */
223 	return DT_DURS;
224 }
225 
226 
227 static size_t
ltostr(char * restrict buf,size_t bsz,long int v,int range,unsigned int pad)228 ltostr(char *restrict buf, size_t bsz, long int v,
229        int range, unsigned int pad)
230 {
231 #define C(x)	(char)((x) + '0')
232 	char *restrict bp = buf;
233 	const char *const ep = buf + bsz;
234 	bool negp;
235 
236 	if (UNLIKELY((negp = v < 0))) {
237 		v = -v;
238 	} else if (!v) {
239 		*bp++ = C(0U);
240 		range--;
241 	}
242 	/* write the mantissa right to left */
243 	for (; v && bp < ep; range--) {
244 		register unsigned int x = v % 10U;
245 
246 		v /= 10U;
247 		*bp++ = C(x);
248 	}
249 	/* fill up with padding */
250 	if (UNLIKELY(pad)) {
251 		static const char pads[] = " 0";
252 		const char p = pads[2U - pad];
253 
254 		while (range-- > 0) {
255 			*bp++ = p;
256 		}
257 	}
258 	/* write the sign */
259 	if (UNLIKELY(negp)) {
260 		*bp++ = '-';
261 	}
262 
263 	/* reverse the string */
264 	for (char *ip = buf, *jp = bp - 1; ip < jp; ip++, jp--) {
265 		register char tmp = *ip;
266 		*ip = *jp;
267 		*jp = tmp;
268 	}
269 #undef C
270 	return bp - buf;
271 }
272 
273 static inline void
dt_io_warn_dur(const char * d1,const char * d2)274 dt_io_warn_dur(const char *d1, const char *d2)
275 {
276 	error("\
277 duration between `%s' and `%s' is not defined", d1, d2);
278 	return;
279 }
280 
281 static __attribute__((pure)) long int
__strf_tot_secs(struct dt_dtdur_s dur)282 __strf_tot_secs(struct dt_dtdur_s dur)
283 {
284 /* return time portion of duration in UTC seconds */
285 	long int s = dur.dv;
286 
287 	if (UNLIKELY(dur.tai) && dur.durtyp == DT_DURS) {
288 		return dur.soft - dur.corr;
289 	}
290 
291 	switch (dur.durtyp) {
292 	default:
293 		/* all the date types */
294 		return dur.t.sdur;
295 	case DT_DURH:
296 		s *= MINS_PER_HOUR;
297 		/*@fallthrough@*/
298 	case DT_DURM:
299 		s *= SECS_PER_MIN;
300 		/*@fallthrough@*/
301 	case DT_DURS:
302 		break;
303 	case DT_DURNANO:
304 		s /= NANOS_PER_SEC;
305 		break;
306 	}
307 	return s;
308 }
309 
310 static __attribute__((pure)) long int
__strf_tot_corr(struct dt_dtdur_s dur)311 __strf_tot_corr(struct dt_dtdur_s dur)
312 {
313 	if (dur.durtyp == DT_DURS && dur.tai) {
314 		return dur.corr;
315 	}
316 	/* otherwise no corrections */
317 	return 0;
318 }
319 
320 static __attribute__((pure)) int
__strf_tot_days(struct dt_dtdur_s dur)321 __strf_tot_days(struct dt_dtdur_s dur)
322 {
323 /* return date portion of DURation in days */
324 	int d;
325 
326 	switch (dur.d.durtyp) {
327 	case DT_DURD:
328 	case DT_DURBD:
329 		d = dur.d.dv;
330 		break;
331 	case DT_DURBIZDA:
332 		d = dur.d.bizda.bd;
333 		break;
334 	case DT_DURYMD:
335 		d = dur.d.ymd.d;
336 		break;
337 	case DT_DURYD:
338 		d = dur.d.yd.d;
339 		break;
340 	case DT_DURYMCW:
341 		d = dur.d.ymcw.w + dur.d.ymcw.c * (int)GREG_DAYS_P_WEEK;
342 		break;
343 	case DT_DURYWD:
344 		d = dur.d.ywd.w + dur.d.ywd.c * (int)GREG_DAYS_P_WEEK;
345 		break;
346 	default:
347 		d = 0;
348 		break;
349 	}
350 	return d;
351 }
352 
353 static __attribute__((pure)) int
__strf_tot_mon(struct dt_dtdur_s dur)354 __strf_tot_mon(struct dt_dtdur_s dur)
355 {
356 /* DUR expressed as month and days */
357 	int m;
358 
359 	switch (dur.d.durtyp) {
360 	case DT_DURBIZDA:
361 		m = dur.d.bizda.m + dur.d.bizda.y * (int)GREG_MONTHS_P_YEAR;
362 		break;
363 	case DT_DURYMD:
364 		m = dur.d.ymd.m + dur.d.ymd.y * (int)GREG_MONTHS_P_YEAR;
365 		break;
366 	case DT_DURYMCW:
367 		m = dur.d.ymcw.m + dur.d.ymcw.y * (int)GREG_MONTHS_P_YEAR;
368 		break;
369 	case DT_DURYD:
370 		m = dur.d.yd.y * (int)GREG_MONTHS_P_YEAR;
371 		break;
372 	case DT_DURYWD:
373 		m = dur.d.ywd.y * (int)GREG_MONTHS_P_YEAR;
374 		break;
375 	default:
376 		m = 0;
377 		break;
378 	}
379 	return m;
380 }
381 
382 static __attribute__((pure)) int
__strf_ym_mon(struct dt_dtdur_s dur)383 __strf_ym_mon(struct dt_dtdur_s dur)
384 {
385 	return __strf_tot_mon(dur) % (int)GREG_MONTHS_P_YEAR;
386 }
387 
388 static __attribute__((pure)) int
__strf_tot_years(struct dt_dtdur_s dur)389 __strf_tot_years(struct dt_dtdur_s dur)
390 {
391 	return __strf_tot_mon(dur) / (int)GREG_MONTHS_P_YEAR;
392 }
393 
394 static struct precalc_s {
395 	int Y;
396 	int m;
397 	int w;
398 	int d;
399 	int db;
400 
401 	long int H;
402 	long int M;
403 	long int S;
404 	long int N;
405 
406 	long int rS;
precalc(durfmt_t f,struct dt_dtdur_s dur)407 } precalc(durfmt_t f, struct dt_dtdur_s dur)
408 {
409 #define MINS_PER_DAY	(MINS_PER_HOUR * HOURS_PER_DAY)
410 #define SECS_PER_WEEK	(SECS_PER_DAY * GREG_DAYS_P_WEEK)
411 #define MINS_PER_WEEK	(MINS_PER_DAY * GREG_DAYS_P_WEEK)
412 #define HOURS_PER_WEEK	(HOURS_PER_DAY * GREG_DAYS_P_WEEK)
413 	struct precalc_s res = {0};
414 	long long int us;
415 
416 	/* date specs */
417 	if (f.has_year) {
418 		/* just years */
419 		res.Y = __strf_tot_years(dur);
420 	}
421 	if (f.has_year && f.has_mon) {
422 		/* years and months */
423 		res.m = __strf_ym_mon(dur);
424 	} else if (f.has_mon) {
425 		/* just months */
426 		res.m = __strf_tot_mon(dur);
427 	}
428 
429 	/* the other units are easily converted as their factors are fixed.
430 	 * we operate on clean seconds and attribute leap seconds only
431 	 * to the S slot, so 59 seconds plus a leap second != 1 minute */
432 	with (long long int S = __strf_tot_secs(dur), d = __strf_tot_days(dur)) {
433 		us = d * (int)SECS_PER_DAY + S;
434 	}
435 
436 	if (f.has_week) {
437 		/* week shadows days in the hierarchy */
438 		res.w = us / (int)SECS_PER_WEEK;
439 		us %= (int)SECS_PER_WEEK;
440 	}
441 	if (f.has_day) {
442 		res.d += us / (int)SECS_PER_DAY;
443 		us %= (int)SECS_PER_DAY;
444 	}
445 	if (f.has_hour) {
446 		res.H = us / (long int)SECS_PER_HOUR;
447 		us %= (long int)SECS_PER_HOUR;
448 	}
449 	if (f.has_min) {
450 		/* minutes and seconds */
451 		res.M = us / (long int)SECS_PER_MIN;
452 		us %= (long int)SECS_PER_MIN;
453 	}
454 	if (f.has_sec) {
455 		res.S = us + __strf_tot_corr(dur);
456 	}
457 
458 	/* just in case the duration iss negative jump through all
459 	 * the hoops again, backwards */
460 	if (res.w < 0 || res.d < 0 ||
461 	    res.H < 0 || res.M < 0 || res.S < 0) {
462 		if (0) {
463 		fixup_d:
464 			res.d = -res.d;
465 		fixup_H:
466 			res.H = -res.H;
467 		fixup_M:
468 			res.M = -res.M;
469 		fixup_S:
470 			res.S = -res.S;
471 		} else if (f.has_week) {
472 			goto fixup_d;
473 		} else if (f.has_day) {
474 			goto fixup_H;
475 		} else if (f.has_hour) {
476 			goto fixup_M;
477 		} else if (f.has_min) {
478 			goto fixup_S;
479 		}
480 	}
481 	return res;
482 }
483 
484 static size_t
__strfdtdur(char * restrict buf,size_t bsz,const char * fmt,struct dt_dtdur_s dur,durfmt_t f,bool only_d_p)485 __strfdtdur(
486 	char *restrict buf, size_t bsz, const char *fmt,
487 	struct dt_dtdur_s dur, durfmt_t f, bool only_d_p)
488 {
489 /* like strfdtdur() but do some calculations based on F on the way there */
490 	static const char sexy_dflt_dur[] = "%0T";
491 	static const char ddur_dflt_dur[] = "%d";
492 	struct precalc_s pre;
493 	const char *fp;
494 	char *bp;
495 
496 	if (UNLIKELY(buf == NULL || bsz == 0)) {
497 		bp = buf;
498 		goto out;
499 	}
500 
501 	/* translate high-level format names */
502 	if (fmt == NULL && dur.durtyp >= (dt_dtdurtyp_t)DT_NDURTYP) {
503 		fmt = sexy_dflt_dur;
504 		f.has_sec = 1U;
505 	} else if (fmt == NULL) {
506 		fmt = ddur_dflt_dur;
507 		f.has_day = 1U;
508 	} else if (only_d_p) {
509 		__trans_ddurfmt(&fmt);
510 	} else {
511 		__trans_dtdurfmt(&fmt);
512 	}
513 
514 	/* precompute */
515 	pre = precalc(f, dur);
516 
517 	/* assign and go */
518 	bp = buf;
519 	fp = fmt;
520 	if (dur.neg) {
521 		*bp++ = '-';
522 	}
523 	for (char *const eo = buf + bsz; *fp && bp < eo;) {
524 		const char *fp_sav = fp;
525 		struct dt_spec_s spec = __tok_spec(fp_sav, &fp);
526 
527 		if (spec.spfl == DT_SPFL_UNK) {
528 			/* must be literal then */
529 			*bp++ = *fp_sav;
530 		} else if (UNLIKELY(spec.rom)) {
531 			continue;
532 		}
533 		/* otherwise switch over spec.spfl */
534 		switch (spec.spfl) {
535 		case DT_SPFL_LIT_PERCENT:
536 			/* literal % */
537 			*bp++ = '%';
538 			break;
539 		case DT_SPFL_LIT_TAB:
540 			/* literal tab */
541 			*bp++ = '\t';
542 			break;
543 		case DT_SPFL_LIT_NL:
544 			/* literal \n */
545 			*bp++ = '\n';
546 			break;
547 
548 		case DT_SPFL_N_DSTD:
549 			bp += ltostr(bp, eo - bp, pre.d, -1, DT_SPPAD_NONE);
550 			*bp++ = 'd';
551 			goto bizda_suffix;
552 
553 		case DT_SPFL_N_DCNT_MON: {
554 			int rng = 2;
555 
556 			if (!f.has_mon && !f.has_week && f.has_year) {
557 				rng++;
558 			}
559 			bp += ltostr(bp, eo - bp, pre.d, rng, spec.pad);
560 		}
561 		bizda_suffix:
562 			if (spec.bizda) {
563 				/* don't print the b after an ordinal */
564 				dt_bizda_param_t bprm;
565 
566 				bprm.bs = dur.d.param;
567 				switch (bprm.ab) {
568 				case BIZDA_AFTER:
569 					*bp++ = 'b';
570 					break;
571 				case BIZDA_BEFORE:
572 					*bp++ = 'B';
573 					break;
574 				}
575 			}
576 			break;
577 
578 		case DT_SPFL_N_WCNT_MON:
579 		case DT_SPFL_N_DCNT_WEEK:
580 			bp += ltostr(bp, eo - bp, pre.w, 2, spec.pad);
581 			break;
582 
583 		case DT_SPFL_N_MON:
584 			bp += ltostr(bp, eo - bp, pre.m, 2, spec.pad);
585 			break;
586 
587 		case DT_SPFL_N_YEAR:
588 			bp += ltostr(bp, eo - bp, pre.Y, -1, DT_SPPAD_NONE);
589 			break;
590 
591 			/* time specs */
592 		case DT_SPFL_N_TSTD:
593 			if (UNLIKELY(spec.tai)) {
594 				pre.S += __strf_tot_corr(dur);
595 			}
596 			bp += ltostr(bp, eo - bp, pre.S, -1, DT_SPPAD_NONE);
597 			*bp++ = 's';
598 			break;
599 
600 		case DT_SPFL_N_SEC:
601 			if (UNLIKELY(spec.tai)) {
602 				pre.S += __strf_tot_corr(dur);
603 			}
604 
605 			bp += ltostr(bp, eo - bp, pre.S, 2, spec.pad);
606 			break;
607 
608 		case DT_SPFL_N_MIN:
609 			bp += ltostr(bp, eo - bp, pre.M, 2, spec.pad);
610 			break;
611 
612 		case DT_SPFL_N_HOUR:
613 			bp += ltostr(bp, eo - bp, pre.H, 2, spec.pad);
614 			break;
615 
616 		default:
617 			break;
618 		}
619 	}
620 out:
621 	if (bp < buf + bsz) {
622 		*bp = '\0';
623 	}
624 	return bp - buf;
625 }
626 
627 static int
ddiff_prnt(struct dt_dtdur_s dur,const char * fmt,durfmt_t f,bool only_d_p)628 ddiff_prnt(struct dt_dtdur_s dur, const char *fmt, durfmt_t f, bool only_d_p)
629 {
630 /* this is mainly a better dt_strfdtdur() */
631 	char buf[256];
632 	size_t res = __strfdtdur(buf, sizeof(buf), fmt, dur, f, only_d_p);
633 
634 	if (res > 0 && buf[res - 1] != '\n') {
635 		/* auto-newline */
636 		buf[res++] = '\n';
637 	}
638 	if (res > 0) {
639 		__io_write(buf, res, stdout);
640 	}
641 	return (res > 0) - 1;
642 }
643 
644 
645 #include "ddiff.yucc"
646 
647 int
main(int argc,char * argv[])648 main(int argc, char *argv[])
649 {
650 	yuck_t argi[1U];
651 	struct dt_dt_s d;
652 	const char *ofmt;
653 	const char *refinp;
654 	char **fmt;
655 	size_t nfmt;
656 	int rc = 0;
657 	durfmt_t dfmt;
658 	dt_dtdurtyp_t dtyp;
659 	zif_t fromz = NULL;
660 
661 	if (yuck_parse(argi, argc, argv)) {
662 		rc = 1;
663 		goto out;
664 	}
665 	/* unescape sequences, maybe */
666 	if (argi->backslash_escapes_flag) {
667 		dt_io_unescape(argi->format_arg);
668 	}
669 
670 	if (argi->from_locale_arg) {
671 		setilocale(argi->from_locale_arg);
672 	}
673 
674 	/* try and read the from and to time zones */
675 	if (argi->from_zone_arg) {
676 		fromz = dt_io_zone(argi->from_zone_arg);
677 	}
678 	if (argi->base_arg) {
679 		struct dt_dt_s base = dt_strpdt(argi->base_arg, NULL, NULL);
680 		dt_set_base(base);
681 	}
682 
683 	ofmt = argi->format_arg;
684 	fmt = argi->input_format_args;
685 	nfmt = argi->input_format_nargs;
686 
687 	if (argi->nargs == 0 ||
688 	    (refinp = argi->args[0U],
689 	     dt_unk_p(d = dt_io_strpdt(refinp, fmt, nfmt, fromz)) &&
690 	     dt_unk_p(d = dt_io_strpdt(refinp, NULL, 0U, fromz)))) {
691 		error("Error: reference DATE must be specified\n");
692 		yuck_auto_help(argi);
693 		rc = 1;
694 		goto out;
695 	} else if (UNLIKELY(d.fix) && !argi->quiet_flag) {
696 		rc = 2;
697 	}
698 
699 	/* try and guess the diff tgttype most suitable for user's FMT */
700 	dfmt = determine_durfmt(ofmt);
701 
702 	if (argi->nargs > 1) {
703 		for (size_t i = 1; i < argi->nargs; i++) {
704 			struct dt_dt_s d2;
705 			struct dt_dtdur_s dur;
706 			const char *inp = argi->args[i];
707 			bool onlydp;
708 
709 			d2 = dt_io_strpdt(inp, fmt, nfmt, fromz);
710 			if (dt_unk_p(d2)) {
711 				if (!argi->quiet_flag) {
712 					dt_io_warn_strpdt(inp);
713 					rc = 2;
714 				}
715 				continue;
716 			} else if (UNLIKELY(d2.fix) && !argi->quiet_flag) {
717 				rc = 2;
718 			}
719 			/* guess the diff type */
720 			onlydp = dt_sandwich_only_d_p(d) ||
721 				dt_sandwich_only_d_p(d2);
722 			if (!(dtyp = determine_durtype(d, d2, dfmt))) {
723 				if (!argi->quiet_flag) {
724 				        dt_io_warn_dur(refinp, inp);
725 					rc = 2;
726 				}
727 				continue;
728 			}
729 			/* subtraction and print */
730 			dur = dt_dtdiff(dtyp, d, d2);
731 			ddiff_prnt(dur, ofmt, dfmt, onlydp);
732 		}
733 	} else {
734 		/* read from stdin */
735 		size_t lno = 0;
736 		void *pctx;
737 
738 		/* no threads reading this stream */
739 		__io_setlocking_bycaller(stdout);
740 
741 		/* using the prchunk reader now */
742 		if ((pctx = init_prchunk(STDIN_FILENO)) == NULL) {
743 			serror("Error: could not open stdin");
744 			goto out;
745 		}
746 		while (prchunk_fill(pctx) >= 0) {
747 			for (char *line; prchunk_haslinep(pctx); lno++) {
748 				struct dt_dt_s d2;
749 				struct dt_dtdur_s dur;
750 				bool onlydp;
751 
752 				(void)prchunk_getline(pctx, &line);
753 				d2 = dt_io_strpdt(line, fmt, nfmt, fromz);
754 
755 				if (dt_unk_p(d2)) {
756 					if (!argi->quiet_flag) {
757 						dt_io_warn_strpdt(line);
758 						rc = 2;
759 					}
760 					if (argi->skip_illegal_flag) {
761 						/* empty line */
762 						__io_write("\n", 1U, stdout);
763 					}
764 					continue;
765 				} else if (UNLIKELY(d2.fix) &&
766 					   !argi->quiet_flag) {
767 					rc = 2;
768 				}
769 				/* guess the diff type */
770 				onlydp = dt_sandwich_only_d_p(d) ||
771 					dt_sandwich_only_d_p(d2);
772 				if (!(dtyp = determine_durtype(d, d2, dfmt))) {
773 					if (!argi->quiet_flag) {
774 						dt_io_warn_dur(refinp, line);
775 						rc = 2;
776 					}
777 					continue;
778 				}
779 				/* perform subtraction now */
780 				dur = dt_dtdiff(dtyp, d, d2);
781 				ddiff_prnt(dur, ofmt, dfmt, onlydp);
782 			}
783 		}
784 		/* get rid of resources */
785 		free_prchunk(pctx);
786 	}
787 
788 	dt_io_clear_zones();
789 	if (argi->from_locale_arg) {
790 		setilocale(NULL);
791 	}
792 
793 out:
794 	yuck_free(argi);
795 	return rc;
796 }
797 
798 /* ddiff.c ends here */
799