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