2 * SPDX-License-Identifier: LGPL-2.1-or-later
4 * First set of functions in this file are part of systemd, and were
5 * copied to util-linux at August 2013.
7 * Copyright 2010 Lennart Poettering
8 * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
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)
26 #include "timeutils.h"
28 #define WHITESPACE " \t\n\r"
30 #define streq(a,b) (strcmp((a),(b)) == 0)
32 static int parse_sec(const char *t
, usec_t
*usec
)
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
},
65 { "", USEC_PER_SEC
}, /* default is sec */
70 int something
= FALSE
;
81 p
+= strspn(p
, WHITESPACE
);
91 l
= strtoll(p
, &e
, 10);
103 z
= strtoll(b
, &e
, 10);
118 e
+= strspn(e
, WHITESPACE
);
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
;
127 r
+= (usec_t
) l
*table
[i
].usec
+ k
;
128 p
= e
+ strlen(table
[i
].suffix
);
134 if (i
>= ARRAY_SIZE(table
))
144 static int parse_subseconds(const char *t
, usec_t
*usec
)
147 int factor
= USEC_PER_SEC
/ 10;
149 if (*t
!= '.' && *t
!= ',')
153 if (!isdigit(*t
) || factor
< 1)
156 ret
+= ((usec_t
) *t
- '0') * factor
;
164 static int parse_timestamp_reference(time_t x
, const char *t
, usec_t
*usec
)
166 static const struct {
188 usec_t plus
= 0, minus
= 0, ret
= 0;
195 * 2012-09-22 16:34:22 !
196 * 2012-09-22T16:34:22 !
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)
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)
210 * Syntaxes marked with '!' also optionally allow up to six digits of
211 * subsecond granularity, separated by '.' or ',':
213 * 2012-09-22 16:34:22.12
214 * 2012-09-22 16:34:22.123456
222 localtime_r(&x
, &tm
);
228 else if (streq(t
, "today")) {
229 tm
.tm_sec
= tm
.tm_min
= tm
.tm_hour
= 0;
232 } else if (streq(t
, "yesterday")) {
234 tm
.tm_sec
= tm
.tm_min
= tm
.tm_hour
= 0;
237 } else if (streq(t
, "tomorrow")) {
239 tm
.tm_sec
= tm
.tm_min
= tm
.tm_hour
= 0;
242 } else if (t
[0] == '+') {
244 r
= parse_sec(t
+ 1, &plus
);
249 } else if (t
[0] == '-') {
251 r
= parse_sec(t
+ 1, &minus
);
256 } else if (t
[0] == '@') {
257 k
= strptime(t
+ 1, "%s", &tm
);
260 else if (k
&& parse_subseconds(k
, &ret
) == 0)
264 } else if (endswith(t
, " ago")) {
267 z
= strndup(t
, strlen(t
) - 4);
271 r
= parse_sec(z
, &minus
);
279 for (i
= 0; i
< ARRAY_SIZE(day_nr
); i
++) {
282 if (!startswith_no_case(t
, day_nr
[i
].name
))
285 skip
= strlen(day_nr
[i
].name
);
289 weekday
= day_nr
[i
].nr
;
295 k
= strptime(t
, "%y-%m-%d %H:%M:%S", &tm
);
298 else if (k
&& parse_subseconds(k
, &ret
) == 0)
302 k
= strptime(t
, "%Y-%m-%d %H:%M:%S", &tm
);
305 else if (k
&& parse_subseconds(k
, &ret
) == 0)
309 k
= strptime(t
, "%Y-%m-%dT%H:%M:%S", &tm
);
312 else if (k
&& parse_subseconds(k
, &ret
) == 0)
316 k
= strptime(t
, "%y-%m-%d %H:%M", &tm
);
323 k
= strptime(t
, "%Y-%m-%d %H:%M", &tm
);
330 k
= strptime(t
, "%y-%m-%d", &tm
);
332 tm
.tm_sec
= tm
.tm_min
= tm
.tm_hour
= 0;
337 k
= strptime(t
, "%Y-%m-%d", &tm
);
339 tm
.tm_sec
= tm
.tm_min
= tm
.tm_hour
= 0;
344 k
= strptime(t
, "%H:%M:%S", &tm
);
347 else if (k
&& parse_subseconds(k
, &ret
) == 0)
351 k
= strptime(t
, "%H:%M", &tm
);
358 k
= strptime(t
, "%Y%m%d%H%M%S", &tm
);
361 else if (k
&& parse_subseconds(k
, &ret
) == 0)
371 if (weekday
>= 0 && tm
.tm_wday
!= weekday
)
374 ret
+= (usec_t
) x
* USEC_PER_SEC
;
387 int parse_timestamp(const char *t
, usec_t
*usec
)
389 return parse_timestamp_reference(time(NULL
), t
, usec
);
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
397 int get_gmtoff(const struct tm
*tp
)
399 if (tp
->tm_isdst
< 0)
403 return tp
->tm_gmtoff
;
412 /* Check if mktime returning -1 is an error or a valid time_t */
413 if (lt
== (time_t) -1) {
414 if (! localtime_r(<
, &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
)))
424 if (! gmtime_r(<
, >m
))
427 /* Calculate the GMT offset, that is, the difference between the
428 * TP argument (ltm) and GMT (gtm).
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.
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
);
442 int years
= ltm
.tm_year
- gtm
.tm_year
;
443 int days
= (365 * years
+ intervening_leap_days
444 + (ltm
.tm_yday
- gtm
.tm_yday
));
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
));
451 static int format_iso_time(const struct tm
*tm
, uint32_t nsec
, int flags
, char *buf
, size_t bufsz
)
453 uint32_t usec
= nsec
/ NSEC_PER_USEC
;
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
)
467 if ((flags
& ISO_DATE
) && (flags
& ISO_TIME
)) {
470 *p
++ = (flags
& ISO_T
) ? 'T' : ' ';
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
)
483 if (flags
& ISO_DOTNSEC
) {
484 len
= snprintf(p
, bufsz
, ".%09"PRIu32
, nsec
);
485 if (len
< 0 || (size_t) len
> bufsz
)
490 } else if (flags
& ISO_COMMANSEC
) {
491 len
= snprintf(p
, bufsz
, ",%09"PRIu32
, nsec
);
492 if (len
< 0 || (size_t) len
> bufsz
)
496 } else if (flags
& ISO_DOTUSEC
) {
497 len
= snprintf(p
, bufsz
, ".%06"PRIu32
, usec
);
498 if (len
< 0 || (size_t) len
> bufsz
)
503 } else if (flags
& ISO_COMMAUSEC
) {
504 len
= snprintf(p
, bufsz
, ",%06"PRIu32
, usec
);
505 if (len
< 0 || (size_t) len
> bufsz
)
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
)
521 warnx(_("format_iso_time: buffer overflow."));
525 /* timespec to ISO 8601 */
526 int strtimespec_iso(const struct timespec
*ts
, int flags
, char *buf
, size_t bufsz
)
531 if (flags
& ISO_GMTIME
)
532 rc
= gmtime_r(&ts
->tv_sec
, &tm
);
534 rc
= localtime_r(&ts
->tv_sec
, &tm
);
537 return format_iso_time(&tm
, ts
->tv_nsec
, flags
, buf
, bufsz
);
539 warnx(_("time %"PRId64
" is out of range."), (int64_t)(ts
->tv_sec
));
543 /* timeval to ISO 8601 */
544 int strtimeval_iso(const struct timeval
*tv
, int flags
, char *buf
, size_t bufsz
)
546 struct timespec ts
= {
547 .tv_sec
= tv
->tv_sec
,
548 .tv_nsec
= tv
->tv_usec
* NSEC_PER_USEC
,
551 return strtimespec_iso(&ts
, flags
, buf
, bufsz
);
554 /* struct tm to ISO 8601 */
555 int strtm_iso(const struct tm
*tm
, int flags
, char *buf
, size_t bufsz
)
557 return format_iso_time(tm
, 0, flags
, buf
, bufsz
);
560 /* time_t to ISO 8601 */
561 int strtime_iso(const time_t *t
, int flags
, char *buf
, size_t bufsz
)
566 if (flags
& ISO_GMTIME
)
567 rc
= gmtime_r(t
, &tm
);
569 rc
= localtime_r(t
, &tm
);
572 return format_iso_time(&tm
, 0, flags
, buf
, bufsz
);
574 warnx(_("time %"PRId64
" is out of range."), (int64_t)*t
);
578 /* relative time functions */
579 static inline int time_is_thisyear(struct tm
const *const tm
,
580 struct tm
const *const tmnow
)
582 return tm
->tm_year
== tmnow
->tm_year
;
585 static inline int time_is_today(struct tm
const *const tm
,
586 struct tm
const *const tmnow
)
588 return (tm
->tm_yday
== tmnow
->tm_yday
&&
589 time_is_thisyear(tm
, tmnow
));
592 int strtime_short(const time_t *t
, struct timeval
*now
, int flags
, char *buf
, size_t bufsz
)
597 if (now
->tv_sec
== 0)
598 gettimeofday(now
, NULL
);
601 localtime_r(&now
->tv_sec
, &tmnow
);
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
)
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
);
613 rc
= strftime(buf
, bufsz
, "%b%d", &tm
);
615 rc
= strftime(buf
, bufsz
, "%Y-%b%d", &tm
);
617 return rc
<= 0 ? -1 : 0;
620 int strtimespec_relative(const struct timespec
*ts
, char *buf
, size_t bufsz
)
622 time_t secs
= ts
->tv_sec
;
629 static const struct {
630 const char * const suffix
;
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
},
641 for (i
= 0; i
< ARRAY_SIZE(table
); i
++) {
642 if (secs
>= table
[i
].secs
) {
643 rc
= snprintf(buf
, bufsz
,
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
)
653 secs
%= table
[i
].secs
;
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
)
664 rc
= snprintf(buf
, bufsz
, "%*llums",
665 parts
? 4 : 0, ts
->tv_nsec
/ NSEC_PER_MSEC
);
666 if (rc
< 0 || (size_t) rc
> bufsz
)
673 warnx(_("format_reltime: buffer overflow."));
678 time_t timegm(struct tm
*tm
)
680 const char *zone
= getenv("TZ");
687 setenv("TZ", zone
, 1);
693 #endif /* HAVE_TIMEGM */
695 #ifdef TEST_PROGRAM_TIMEUTILS
697 static int run_unittest_timestamp(void)
699 int rc
= EXIT_SUCCESS
;
700 time_t reference
= 1674180427;
701 static const struct testcase
{
702 const char * const input
;
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 },
725 setenv("TZ", "GMT", 1);
728 for (size_t i
= 0; i
< ARRAY_SIZE(testcases
); i
++) {
729 struct testcase t
= testcases
[i
];
731 int r
= parse_timestamp_reference(reference
, t
.input
, &result
);
733 fprintf(stderr
, "Could not parse '%s'\n", t
.input
);
737 if (result
!= t
.expected
) {
738 fprintf(stderr
, "#%02zu %-25s: %"PRId64
" != %"PRId64
"\n",
739 i
, t
.input
, result
, t
.expected
);
747 static int run_unittest_format(void)
749 int rc
= EXIT_SUCCESS
;
750 const struct timespec ts
= {
751 .tv_sec
= 1674180427,
754 char buf
[FORMAT_TIMESTAMP_MAX
];
755 static const struct testcase
{
757 const char * const expected
;
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" },
767 setenv("TZ", "GMT", 1);
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
));
774 fprintf(stderr
, "Could not format '%s'\n", t
.expected
);
778 if (strcmp(buf
, t
.expected
)) {
779 fprintf(stderr
, "#%02zu %-20s != %-20s\n", i
, buf
, t
.expected
);
787 static int run_unittest_format_relative(void)
789 int rc
= EXIT_SUCCESS
;
790 char buf
[FORMAT_TIMESTAMP_MAX
];
791 static const struct testcase
{
793 const char * const expected
;
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" },
808 {{ 1, 1 }, "1s 1ns" },
810 {{ 0, 1000000 }, "1ms" },
811 {{ 0, 1000001 }, "1000001ns" },
814 setenv("TZ", "GMT", 1);
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
));
821 fprintf(stderr
, "Could not format '%s'\n", t
.expected
);
825 if (strcmp(buf
, t
.expected
)) {
826 fprintf(stderr
, "#%02zu '%-20s' != '%-20s'\n", i
, buf
, t
.expected
);
834 int main(int argc
, char *argv
[])
836 struct timespec ts
= { 0 };
837 char buf
[ISO_BUFSIZ
];
840 fprintf(stderr
, "usage: %s [<time> [<usec>]] | [--timestamp <str>] | [--unittest-timestamp]\n", argv
[0]);
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();
851 if (strcmp(argv
[1], "--timestamp") == 0) {
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
;
858 ts
.tv_sec
= strtos64_or_err(argv
[1], "failed to parse <time>");
860 ts
.tv_nsec
= strtos64_or_err(argv
[2], "failed to parse <usec>")
864 strtimespec_iso(&ts
, ISO_DATE
, buf
, sizeof(buf
));
865 printf("Date: '%s'\n", buf
);
867 strtimespec_iso(&ts
, ISO_TIME
, buf
, sizeof(buf
));
868 printf("Time: '%s'\n", buf
);
870 strtimespec_iso(&ts
, ISO_DATE
| ISO_TIME
| ISO_COMMAUSEC
| ISO_T
,
872 printf("Full: '%s'\n", buf
);
874 strtimespec_iso(&ts
, ISO_TIMESTAMP_DOT
, buf
, sizeof(buf
));
875 printf("Zone: '%s'\n", buf
);
877 strtimespec_relative(&ts
, buf
, sizeof(buf
));
878 printf("Rel: '%s'\n", buf
);
883 #endif /* TEST_PROGRAM_TIMEUTILS */