]> git.ipfire.org Git - thirdparty/util-linux.git/blob - lib/timeutils.c
Merge branch 'minor-improvement' of https://github.com/calestyo/util-linux
[thirdparty/util-linux.git] / lib / timeutils.c
1 /*
2 * SPDX-License-Identifier: LGPL-2.1-or-later
3 *
4 * First set of functions in this file are part of systemd, and were
5 * copied to util-linux at August 2013.
6 *
7 * Copyright 2010 Lennart Poettering
8 * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
9 *
10 * This is free software; you can redistribute it and/or modify it under the
11 * terms of the GNU Lesser General Public License as published by the Free
12 * Software Foundation; either version 2.1 of the License, or (at your option)
13 * any later version.
14 */
15 #include <assert.h>
16 #include <ctype.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <time.h>
20 #include <sys/time.h>
21 #include <inttypes.h>
22
23 #include "c.h"
24 #include "nls.h"
25 #include "strutils.h"
26 #include "timeutils.h"
27
28 #define WHITESPACE " \t\n\r"
29
30 #define streq(a,b) (strcmp((a),(b)) == 0)
31
32 static int parse_sec(const char *t, usec_t *usec)
33 {
34 static const struct {
35 const char *suffix;
36 usec_t usec;
37 } table[] = {
38 { "seconds", USEC_PER_SEC },
39 { "second", USEC_PER_SEC },
40 { "sec", USEC_PER_SEC },
41 { "s", USEC_PER_SEC },
42 { "minutes", USEC_PER_MINUTE },
43 { "minute", USEC_PER_MINUTE },
44 { "min", USEC_PER_MINUTE },
45 { "months", USEC_PER_MONTH },
46 { "month", USEC_PER_MONTH },
47 { "msec", USEC_PER_MSEC },
48 { "ms", USEC_PER_MSEC },
49 { "m", USEC_PER_MINUTE },
50 { "hours", USEC_PER_HOUR },
51 { "hour", USEC_PER_HOUR },
52 { "hr", USEC_PER_HOUR },
53 { "h", USEC_PER_HOUR },
54 { "days", USEC_PER_DAY },
55 { "day", USEC_PER_DAY },
56 { "d", USEC_PER_DAY },
57 { "weeks", USEC_PER_WEEK },
58 { "week", USEC_PER_WEEK },
59 { "w", USEC_PER_WEEK },
60 { "years", USEC_PER_YEAR },
61 { "year", USEC_PER_YEAR },
62 { "y", USEC_PER_YEAR },
63 { "usec", 1ULL },
64 { "us", 1ULL },
65 { "", USEC_PER_SEC }, /* default is sec */
66 };
67
68 const char *p;
69 usec_t r = 0;
70 int something = FALSE;
71
72 assert(t);
73 assert(usec);
74
75 p = t;
76 for (;;) {
77 long long l, z = 0;
78 char *e;
79 unsigned i, n = 0;
80
81 p += strspn(p, WHITESPACE);
82
83 if (*p == 0) {
84 if (!something)
85 return -EINVAL;
86
87 break;
88 }
89
90 errno = 0;
91 l = strtoll(p, &e, 10);
92
93 if (errno > 0)
94 return -errno;
95
96 if (l < 0)
97 return -ERANGE;
98
99 if (*e == '.') {
100 char *b = e + 1;
101
102 errno = 0;
103 z = strtoll(b, &e, 10);
104 if (errno > 0)
105 return -errno;
106
107 if (z < 0)
108 return -ERANGE;
109
110 if (e == b)
111 return -EINVAL;
112
113 n = e - b;
114
115 } else if (e == p)
116 return -EINVAL;
117
118 e += strspn(e, WHITESPACE);
119
120 for (i = 0; i < ARRAY_SIZE(table); i++)
121 if (startswith(e, table[i].suffix)) {
122 usec_t k = (usec_t) z * table[i].usec;
123
124 for (; n > 0; n--)
125 k /= 10;
126
127 r += (usec_t) l *table[i].usec + k;
128 p = e + strlen(table[i].suffix);
129
130 something = TRUE;
131 break;
132 }
133
134 if (i >= ARRAY_SIZE(table))
135 return -EINVAL;
136
137 }
138
139 *usec = r;
140
141 return 0;
142 }
143
144 static int parse_subseconds(const char *t, usec_t *usec)
145 {
146 usec_t ret = 0;
147 int factor = USEC_PER_SEC / 10;
148
149 if (*t != '.' && *t != ',')
150 return -1;
151
152 while (*(++t)) {
153 if (!isdigit(*t) || factor < 1)
154 return -1;
155
156 ret += ((usec_t) *t - '0') * factor;
157 factor /= 10;
158 }
159
160 *usec = ret;
161 return 0;
162 }
163
164 static const char *parse_epoch_seconds(const char *t, struct tm *tm)
165 {
166 int64_t s;
167 time_t st;
168 int f, c;
169
170 f = sscanf(t, "%"SCNd64"%n", &s, &c);
171 if (f < 1)
172 return NULL;
173 st = s;
174 if ((int64_t) st < s)
175 return NULL;
176 if (!localtime_r(&st, tm))
177 return NULL;
178 return t + c;
179 }
180
181 static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec)
182 {
183 static const struct {
184 const char *name;
185 const int nr;
186 } day_nr[] = {
187 { "Sunday", 0 },
188 { "Sun", 0 },
189 { "Monday", 1 },
190 { "Mon", 1 },
191 { "Tuesday", 2 },
192 { "Tue", 2 },
193 { "Wednesday", 3 },
194 { "Wed", 3 },
195 { "Thursday", 4 },
196 { "Thu", 4 },
197 { "Friday", 5 },
198 { "Fri", 5 },
199 { "Saturday", 6 },
200 { "Sat", 6 },
201 };
202
203 const char *k;
204 struct tm tm, copy;
205 usec_t plus = 0, minus = 0, ret = 0;
206 int r, weekday = -1;
207 unsigned i;
208
209 /*
210 * Allowed syntaxes:
211 *
212 * 2012-09-22 16:34:22 !
213 * 2012-09-22T16:34:22 !
214 * 20120922163422 !
215 * @1348331662 ! (seconds since the Epoch (1970-01-01 00:00 UTC))
216 * 2012-09-22 16:34 (seconds will be set to 0)
217 * 2012-09-22 (time will be set to 00:00:00)
218 * 16:34:22 ! (date will be set to today)
219 * 16:34 (date will be set to today, seconds to 0)
220 * now
221 * yesterday (time is set to 00:00:00)
222 * today (time is set to 00:00:00)
223 * tomorrow (time is set to 00:00:00)
224 * +5min
225 * -5days
226 *
227 * Syntaxes marked with '!' also optionally allow up to six digits of
228 * subsecond granularity, separated by '.' or ',':
229 *
230 * 2012-09-22 16:34:22.12
231 * 2012-09-22 16:34:22.123456
232 *
233 *
234 */
235
236 assert(t);
237 assert(usec);
238
239 localtime_r(&x, &tm);
240 tm.tm_isdst = -1;
241
242 if (streq(t, "now"))
243 goto finish;
244
245 else if (streq(t, "today")) {
246 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
247 goto finish;
248
249 } else if (streq(t, "yesterday")) {
250 tm.tm_mday--;
251 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
252 goto finish;
253
254 } else if (streq(t, "tomorrow")) {
255 tm.tm_mday++;
256 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
257 goto finish;
258
259 } else if (t[0] == '+') {
260
261 r = parse_sec(t + 1, &plus);
262 if (r < 0)
263 return r;
264
265 goto finish;
266 } else if (t[0] == '-') {
267
268 r = parse_sec(t + 1, &minus);
269 if (r < 0)
270 return r;
271
272 goto finish;
273 } else if (t[0] == '@') {
274 k = parse_epoch_seconds(t + 1, &tm);
275 if (k && *k == 0)
276 goto finish;
277 else if (k && parse_subseconds(k, &ret) == 0)
278 goto finish;
279
280 return -EINVAL;
281 } else if (endswith(t, " ago")) {
282 char *z;
283
284 z = strndup(t, strlen(t) - 4);
285 if (!z)
286 return -ENOMEM;
287
288 r = parse_sec(z, &minus);
289 free(z);
290 if (r < 0)
291 return r;
292
293 goto finish;
294 }
295
296 for (i = 0; i < ARRAY_SIZE(day_nr); i++) {
297 size_t skip;
298
299 if (!startswith_no_case(t, day_nr[i].name))
300 continue;
301
302 skip = strlen(day_nr[i].name);
303 if (t[skip] != ' ')
304 continue;
305
306 weekday = day_nr[i].nr;
307 t += skip + 1;
308 break;
309 }
310
311 copy = tm;
312 k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
313 if (k && *k == 0)
314 goto finish;
315 else if (k && parse_subseconds(k, &ret) == 0)
316 goto finish;
317
318 tm = copy;
319 k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
320 if (k && *k == 0)
321 goto finish;
322 else if (k && parse_subseconds(k, &ret) == 0)
323 goto finish;
324
325 tm = copy;
326 k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm);
327 if (k && *k == 0)
328 goto finish;
329 else if (k && parse_subseconds(k, &ret) == 0)
330 goto finish;
331
332 tm = copy;
333 k = strptime(t, "%y-%m-%d %H:%M", &tm);
334 if (k && *k == 0) {
335 tm.tm_sec = 0;
336 goto finish;
337 }
338
339 tm = copy;
340 k = strptime(t, "%Y-%m-%d %H:%M", &tm);
341 if (k && *k == 0) {
342 tm.tm_sec = 0;
343 goto finish;
344 }
345
346 tm = copy;
347 k = strptime(t, "%y-%m-%d", &tm);
348 if (k && *k == 0) {
349 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
350 goto finish;
351 }
352
353 tm = copy;
354 k = strptime(t, "%Y-%m-%d", &tm);
355 if (k && *k == 0) {
356 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
357 goto finish;
358 }
359
360 tm = copy;
361 k = strptime(t, "%H:%M:%S", &tm);
362 if (k && *k == 0)
363 goto finish;
364 else if (k && parse_subseconds(k, &ret) == 0)
365 goto finish;
366
367 tm = copy;
368 k = strptime(t, "%H:%M", &tm);
369 if (k && *k == 0) {
370 tm.tm_sec = 0;
371 goto finish;
372 }
373
374 tm = copy;
375 k = strptime(t, "%Y%m%d%H%M%S", &tm);
376 if (k && *k == 0)
377 goto finish;
378 else if (k && parse_subseconds(k, &ret) == 0)
379 goto finish;
380
381 return -EINVAL;
382
383 finish:
384 x = mktime(&tm);
385 if (x == (time_t)-1)
386 return -EINVAL;
387
388 if (weekday >= 0 && tm.tm_wday != weekday)
389 return -EINVAL;
390
391 ret += (usec_t) x * USEC_PER_SEC;
392
393 if (minus > ret)
394 return -ERANGE;
395 if ((ret + plus) < ret)
396 return -ERANGE;
397
398 ret += plus;
399 ret -= minus;
400
401 *usec = ret;
402
403 return 0;
404 }
405
406 int parse_timestamp(const char *t, usec_t *usec)
407 {
408 return parse_timestamp_reference(time(NULL), t, usec);
409 }
410
411 /* Returns the difference in seconds between its argument and GMT. If if TP is
412 * invalid or no DST information is available default to UTC, that is, zero.
413 * tzset is called so, for example, 'TZ="UTC" hwclock' will work as expected.
414 * Derived from glibc/time/strftime_l.c
415 */
416 int get_gmtoff(const struct tm *tp)
417 {
418 if (tp->tm_isdst < 0)
419 return 0;
420
421 #if HAVE_TM_GMTOFF
422 return tp->tm_gmtoff;
423 #else
424 struct tm tm;
425 struct tm gtm;
426 struct tm ltm = *tp;
427 time_t lt;
428
429 tzset();
430 lt = mktime(&ltm);
431 /* Check if mktime returning -1 is an error or a valid time_t */
432 if (lt == (time_t) -1) {
433 if (! localtime_r(&lt, &tm)
434 || ((ltm.tm_sec ^ tm.tm_sec)
435 | (ltm.tm_min ^ tm.tm_min)
436 | (ltm.tm_hour ^ tm.tm_hour)
437 | (ltm.tm_mday ^ tm.tm_mday)
438 | (ltm.tm_mon ^ tm.tm_mon)
439 | (ltm.tm_year ^ tm.tm_year)))
440 return 0;
441 }
442
443 if (! gmtime_r(&lt, &gtm))
444 return 0;
445
446 /* Calculate the GMT offset, that is, the difference between the
447 * TP argument (ltm) and GMT (gtm).
448 *
449 * Compute intervening leap days correctly even if year is negative.
450 * Take care to avoid int overflow in leap day calculations, but it's OK
451 * to assume that A and B are close to each other.
452 */
453 int a4 = (ltm.tm_year >> 2) + (1900 >> 2) - ! (ltm.tm_year & 3);
454 int b4 = (gtm.tm_year >> 2) + (1900 >> 2) - ! (gtm.tm_year & 3);
455 int a100 = a4 / 25 - (a4 % 25 < 0);
456 int b100 = b4 / 25 - (b4 % 25 < 0);
457 int a400 = a100 >> 2;
458 int b400 = b100 >> 2;
459 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
460
461 int years = ltm.tm_year - gtm.tm_year;
462 int days = (365 * years + intervening_leap_days
463 + (ltm.tm_yday - gtm.tm_yday));
464
465 return (60 * (60 * (24 * days + (ltm.tm_hour - gtm.tm_hour))
466 + (ltm.tm_min - gtm.tm_min)) + (ltm.tm_sec - gtm.tm_sec));
467 #endif
468 }
469
470 static int format_iso_time(const struct tm *tm, uint32_t nsec, int flags, char *buf, size_t bufsz)
471 {
472 uint32_t usec = nsec / NSEC_PER_USEC;
473 char *p = buf;
474 int len;
475
476 if (flags & ISO_DATE) {
477 len = snprintf(p, bufsz, "%4ld-%.2d-%.2d",
478 tm->tm_year + (long) 1900,
479 tm->tm_mon + 1, tm->tm_mday);
480 if (len < 0 || (size_t) len > bufsz)
481 goto err;
482 bufsz -= len;
483 p += len;
484 }
485
486 if ((flags & ISO_DATE) && (flags & ISO_TIME)) {
487 if (bufsz < 1)
488 goto err;
489 *p++ = (flags & ISO_T) ? 'T' : ' ';
490 bufsz--;
491 }
492
493 if (flags & ISO_TIME) {
494 len = snprintf(p, bufsz, "%02d:%02d:%02d", tm->tm_hour,
495 tm->tm_min, tm->tm_sec);
496 if (len < 0 || (size_t) len > bufsz)
497 goto err;
498 bufsz -= len;
499 p += len;
500 }
501
502 if (flags & ISO_DOTNSEC) {
503 len = snprintf(p, bufsz, ".%09"PRIu32, nsec);
504 if (len < 0 || (size_t) len > bufsz)
505 goto err;
506 bufsz -= len;
507 p += len;
508
509 } else if (flags & ISO_COMMANSEC) {
510 len = snprintf(p, bufsz, ",%09"PRIu32, nsec);
511 if (len < 0 || (size_t) len > bufsz)
512 goto err;
513 bufsz -= len;
514 p += len;
515 } else if (flags & ISO_DOTUSEC) {
516 len = snprintf(p, bufsz, ".%06"PRIu32, usec);
517 if (len < 0 || (size_t) len > bufsz)
518 goto err;
519 bufsz -= len;
520 p += len;
521
522 } else if (flags & ISO_COMMAUSEC) {
523 len = snprintf(p, bufsz, ",%06"PRIu32, usec);
524 if (len < 0 || (size_t) len > bufsz)
525 goto err;
526 bufsz -= len;
527 p += len;
528 }
529
530 if (flags & ISO_TIMEZONE) {
531 int tmin = get_gmtoff(tm) / 60;
532 int zhour = tmin / 60;
533 int zmin = abs(tmin % 60);
534 len = snprintf(p, bufsz, "%+03d:%02d", zhour,zmin);
535 if (len < 0 || (size_t) len > bufsz)
536 goto err;
537 }
538 return 0;
539 err:
540 warnx(_("format_iso_time: buffer overflow."));
541 return -1;
542 }
543
544 /* timespec to ISO 8601 */
545 int strtimespec_iso(const struct timespec *ts, int flags, char *buf, size_t bufsz)
546 {
547 struct tm tm;
548 struct tm *rc;
549
550 if (flags & ISO_GMTIME)
551 rc = gmtime_r(&ts->tv_sec, &tm);
552 else
553 rc = localtime_r(&ts->tv_sec, &tm);
554
555 if (rc)
556 return format_iso_time(&tm, ts->tv_nsec, flags, buf, bufsz);
557
558 warnx(_("time %"PRId64" is out of range."), (int64_t)(ts->tv_sec));
559 return -1;
560 }
561
562 /* timeval to ISO 8601 */
563 int strtimeval_iso(const struct timeval *tv, int flags, char *buf, size_t bufsz)
564 {
565 struct timespec ts = {
566 .tv_sec = tv->tv_sec,
567 .tv_nsec = tv->tv_usec * NSEC_PER_USEC,
568 };
569
570 return strtimespec_iso(&ts, flags, buf, bufsz);
571 }
572
573 /* struct tm to ISO 8601 */
574 int strtm_iso(const struct tm *tm, int flags, char *buf, size_t bufsz)
575 {
576 return format_iso_time(tm, 0, flags, buf, bufsz);
577 }
578
579 /* time_t to ISO 8601 */
580 int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz)
581 {
582 struct tm tm;
583 struct tm *rc;
584
585 if (flags & ISO_GMTIME)
586 rc = gmtime_r(t, &tm);
587 else
588 rc = localtime_r(t, &tm);
589
590 if (rc)
591 return format_iso_time(&tm, 0, flags, buf, bufsz);
592
593 warnx(_("time %"PRId64" is out of range."), (int64_t)*t);
594 return -1;
595 }
596
597 /* relative time functions */
598 static inline int time_is_thisyear(struct tm const *const tm,
599 struct tm const *const tmnow)
600 {
601 return tm->tm_year == tmnow->tm_year;
602 }
603
604 static inline int time_is_today(struct tm const *const tm,
605 struct tm const *const tmnow)
606 {
607 return (tm->tm_yday == tmnow->tm_yday &&
608 time_is_thisyear(tm, tmnow));
609 }
610
611 int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz)
612 {
613 struct tm tm, tmnow;
614 int rc = 0;
615
616 if (now->tv_sec == 0)
617 gettimeofday(now, NULL);
618
619 localtime_r(t, &tm);
620 localtime_r(&now->tv_sec, &tmnow);
621
622 if (time_is_today(&tm, &tmnow)) {
623 rc = snprintf(buf, bufsz, "%02d:%02d", tm.tm_hour, tm.tm_min);
624 if (rc < 0 || (size_t) rc > bufsz)
625 return -1;
626 rc = 1;
627
628 } else if (time_is_thisyear(&tm, &tmnow)) {
629 if (flags & UL_SHORTTIME_THISYEAR_HHMM)
630 rc = strftime(buf, bufsz, "%b%d/%H:%M", &tm);
631 else
632 rc = strftime(buf, bufsz, "%b%d", &tm);
633 } else
634 rc = strftime(buf, bufsz, "%Y-%b%d", &tm);
635
636 return rc <= 0 ? -1 : 0;
637 }
638
639 int strtimespec_relative(const struct timespec *ts, char *buf, size_t bufsz)
640 {
641 time_t secs = ts->tv_sec;
642 size_t i, parts = 0;
643 int rc;
644
645 if (bufsz)
646 buf[0] = '\0';
647
648 static const struct {
649 const char * const suffix;
650 int width;
651 int64_t secs;
652 } table[] = {
653 { "y", 4, NSEC_PER_YEAR / NSEC_PER_SEC },
654 { "d", 3, NSEC_PER_DAY / NSEC_PER_SEC },
655 { "h", 2, NSEC_PER_HOUR / NSEC_PER_SEC },
656 { "m", 2, NSEC_PER_MINUTE / NSEC_PER_SEC },
657 { "s", 2, NSEC_PER_SEC / NSEC_PER_SEC },
658 };
659
660 for (i = 0; i < ARRAY_SIZE(table); i++) {
661 if (secs >= table[i].secs) {
662 rc = snprintf(buf, bufsz,
663 "%*"PRId64"%s%s",
664 parts ? table[i].width : 0,
665 secs / table[i].secs, table[i].suffix,
666 secs % table[i].secs ? " " : "");
667 if (rc < 0 || (size_t) rc > bufsz)
668 goto err;
669 parts++;
670 buf += rc;
671 bufsz -= rc;
672 secs %= table[i].secs;
673 }
674 }
675
676 if (ts->tv_nsec) {
677 if (ts->tv_nsec % NSEC_PER_MSEC) {
678 rc = snprintf(buf, bufsz, "%*luns",
679 parts ? 10 : 0, ts->tv_nsec);
680 if (rc < 0 || (size_t) rc > bufsz)
681 goto err;
682 } else {
683 rc = snprintf(buf, bufsz, "%*llums",
684 parts ? 4 : 0, ts->tv_nsec / NSEC_PER_MSEC);
685 if (rc < 0 || (size_t) rc > bufsz)
686 goto err;
687 }
688 }
689
690 return 0;
691 err:
692 warnx(_("format_reltime: buffer overflow."));
693 return -1;
694 }
695
696 #ifndef HAVE_TIMEGM
697 time_t timegm(struct tm *tm)
698 {
699 const char *zone = getenv("TZ");
700 time_t ret;
701
702 setenv("TZ", "", 1);
703 tzset();
704 ret = mktime(tm);
705 if (zone)
706 setenv("TZ", zone, 1);
707 else
708 unsetenv("TZ");
709 tzset();
710 return ret;
711 }
712 #endif /* HAVE_TIMEGM */
713
714 #ifdef TEST_PROGRAM_TIMEUTILS
715
716 static int run_unittest_timestamp(void)
717 {
718 int rc = EXIT_SUCCESS;
719 time_t reference = 1674180427;
720 static const struct testcase {
721 const char * const input;
722 usec_t expected;
723 } testcases[] = {
724 { "2012-09-22 16:34:22" , 1348331662000000 },
725 { "2012-09-22 16:34:22,012", 1348331662012000 },
726 { "2012-09-22 16:34:22.012", 1348331662012000 },
727 { "@1348331662" , 1348331662000000 },
728 { "@1348331662.234567" , 1348331662234567 },
729 { "@0" , 0 },
730 { "2012-09-22 16:34" , 1348331640000000 },
731 { "2012-09-22" , 1348272000000000 },
732 { "16:34:22" , 1674232462000000 },
733 { "16:34:22,123456" , 1674232462123456 },
734 { "16:34:22.123456" , 1674232462123456 },
735 { "16:34" , 1674232440000000 },
736 { "now" , 1674180427000000 },
737 { "yesterday" , 1674086400000000 },
738 { "today" , 1674172800000000 },
739 { "tomorrow" , 1674259200000000 },
740 { "+5min" , 1674180727000000 },
741 { "-5days" , 1673748427000000 },
742 { "20120922163422" , 1348331662000000 },
743 };
744
745 setenv("TZ", "GMT", 1);
746 tzset();
747
748 for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
749 struct testcase t = testcases[i];
750 usec_t result;
751 int r = parse_timestamp_reference(reference, t.input, &result);
752 if (r) {
753 fprintf(stderr, "Could not parse '%s'\n", t.input);
754 rc = EXIT_FAILURE;
755 }
756
757 if (result != t.expected) {
758 fprintf(stderr, "#%02zu %-25s: %"PRId64" != %"PRId64"\n",
759 i, t.input, result, t.expected);
760 rc = EXIT_FAILURE;
761 }
762 }
763
764 return rc;
765 }
766
767 static int run_unittest_format(void)
768 {
769 int rc = EXIT_SUCCESS;
770 const struct timespec ts = {
771 .tv_sec = 1674180427,
772 .tv_nsec = 12345,
773 };
774 char buf[FORMAT_TIMESTAMP_MAX];
775 static const struct testcase {
776 int flags;
777 const char * const expected;
778 } testcases[] = {
779 { ISO_DATE, "2023-01-20" },
780 { ISO_TIME, "02:07:07" },
781 { ISO_TIMEZONE, "+00:00" },
782 { ISO_TIMESTAMP_T, "2023-01-20T02:07:07+00:00" },
783 { ISO_TIMESTAMP_COMMA_G, "2023-01-20 02:07:07,000012+00:00" },
784 { ISO_TIME | ISO_DOTNSEC, "02:07:07.000012345" },
785 };
786
787 setenv("TZ", "GMT", 1);
788 tzset();
789
790 for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
791 struct testcase t = testcases[i];
792 int r = strtimespec_iso(&ts, t.flags, buf, sizeof(buf));
793 if (r) {
794 fprintf(stderr, "Could not format '%s'\n", t.expected);
795 rc = EXIT_FAILURE;
796 }
797
798 if (strcmp(buf, t.expected)) {
799 fprintf(stderr, "#%02zu %-20s != %-20s\n", i, buf, t.expected);
800 rc = EXIT_FAILURE;
801 }
802 }
803
804 return rc;
805 }
806
807 static int run_unittest_format_relative(void)
808 {
809 int rc = EXIT_SUCCESS;
810 char buf[FORMAT_TIMESTAMP_MAX];
811 static const struct testcase {
812 struct timespec ts;
813 const char * const expected;
814 } testcases[] = {
815 {{}, "" },
816 {{ 1 }, "1s" },
817 {{ 10 }, "10s" },
818 {{ 100 }, "1m 40s" },
819 {{ 1000 }, "16m 40s" },
820 {{ 10000 }, "2h 46m 40s" },
821 {{ 100000 }, "1d 3h 46m 40s" },
822 {{ 1000000 }, "11d 13h 46m 40s" },
823 {{ 10000000 }, "115d 17h 46m 40s" },
824 {{ 100000000 }, "3y 61d 15h 46m 40s" },
825 {{ 60 }, "1m" },
826 {{ 3600 }, "1h" },
827
828 {{ 1, 1 }, "1s 1ns" },
829 {{ 0, 1 }, "1ns" },
830 {{ 0, 1000000 }, "1ms" },
831 {{ 0, 1000001 }, "1000001ns" },
832 };
833
834 setenv("TZ", "GMT", 1);
835 tzset();
836
837 for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
838 struct testcase t = testcases[i];
839 int r = strtimespec_relative(&t.ts, buf, sizeof(buf));
840 if (r) {
841 fprintf(stderr, "Could not format '%s'\n", t.expected);
842 rc = EXIT_FAILURE;
843 }
844
845 if (strcmp(buf, t.expected)) {
846 fprintf(stderr, "#%02zu '%-20s' != '%-20s'\n", i, buf, t.expected);
847 rc = EXIT_FAILURE;
848 }
849 }
850
851 return rc;
852 }
853
854 int main(int argc, char *argv[])
855 {
856 struct timespec ts = { 0 };
857 char buf[ISO_BUFSIZ];
858 int r;
859
860 if (argc < 2) {
861 fprintf(stderr, "usage: %s [<time> [<usec>]] | [--timestamp <str>] | [--unittest-timestamp]\n", argv[0]);
862 exit(EXIT_FAILURE);
863 }
864
865 if (strcmp(argv[1], "--unittest-timestamp") == 0)
866 return run_unittest_timestamp();
867 else if (strcmp(argv[1], "--unittest-format") == 0)
868 return run_unittest_format();
869 else if (strcmp(argv[1], "--unittest-format-relative") == 0)
870 return run_unittest_format_relative();
871
872 if (strcmp(argv[1], "--timestamp") == 0) {
873 usec_t usec = 0;
874
875 r = parse_timestamp(argv[2], &usec);
876 if (r)
877 errx(EXIT_FAILURE, "Can not parse '%s': %s", argv[2], strerror(-r));
878 ts.tv_sec = (time_t) (usec / USEC_PER_SEC);
879 ts.tv_nsec = (usec % USEC_PER_SEC) * NSEC_PER_USEC;
880 } else {
881 ts.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>");
882 if (argc == 3)
883 ts.tv_nsec = strtos64_or_err(argv[2], "failed to parse <usec>")
884 * NSEC_PER_USEC;
885 }
886
887 strtimespec_iso(&ts, ISO_DATE, buf, sizeof(buf));
888 printf("Date: '%s'\n", buf);
889
890 strtimespec_iso(&ts, ISO_TIME, buf, sizeof(buf));
891 printf("Time: '%s'\n", buf);
892
893 strtimespec_iso(&ts, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T,
894 buf, sizeof(buf));
895 printf("Full: '%s'\n", buf);
896
897 strtimespec_iso(&ts, ISO_TIMESTAMP_DOT, buf, sizeof(buf));
898 printf("Zone: '%s'\n", buf);
899
900 strtimespec_relative(&ts, buf, sizeof(buf));
901 printf("Rel: '%s'\n", buf);
902
903 return EXIT_SUCCESS;
904 }
905
906 #endif /* TEST_PROGRAM_TIMEUTILS */