]>
Commit | Line | Data |
---|---|---|
79feaa60 KZ |
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 | */ | |
2659a49e SK |
15 | #include <assert.h> |
16 | #include <ctype.h> | |
b72a75e9 | 17 | #include <stdlib.h> |
2659a49e SK |
18 | #include <string.h> |
19 | #include <time.h> | |
33c7ffa3 | 20 | #include <sys/time.h> |
ce3355cc | 21 | #include <inttypes.h> |
2659a49e SK |
22 | |
23 | #include "c.h" | |
929f939e | 24 | #include "nls.h" |
60f25dea KZ |
25 | #include "strutils.h" |
26 | #include "timeutils.h" | |
2659a49e SK |
27 | |
28 | #define WHITESPACE " \t\n\r" | |
29 | ||
30 | #define streq(a,b) (strcmp((a),(b)) == 0) | |
31 | ||
2659a49e SK |
32 | static int parse_sec(const char *t, usec_t *usec) |
33 | { | |
9d826076 | 34 | static const struct { |
2659a49e SK |
35 | const char *suffix; |
36 | usec_t usec; | |
9d826076 | 37 | } table[] = { |
2659a49e SK |
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 */ | |
9d826076 | 66 | }; |
2659a49e SK |
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 | ||
c061d1a7 TW |
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 | ||
15a1f536 | 164 | static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec) |
2659a49e | 165 | { |
9d826076 | 166 | static const struct { |
2659a49e SK |
167 | const char *name; |
168 | const int nr; | |
9d826076 | 169 | } day_nr[] = { |
2659a49e SK |
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 }, | |
9d826076 | 184 | }; |
2659a49e SK |
185 | |
186 | const char *k; | |
187 | struct tm tm, copy; | |
c061d1a7 | 188 | usec_t plus = 0, minus = 0, ret = 0; |
2659a49e SK |
189 | int r, weekday = -1; |
190 | unsigned i; | |
191 | ||
192 | /* | |
193 | * Allowed syntaxes: | |
194 | * | |
c061d1a7 TW |
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) | |
2659a49e | 203 | * now |
c061d1a7 TW |
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) | |
2659a49e SK |
207 | * +5min |
208 | * -5days | |
209 | * | |
c061d1a7 TW |
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 | * | |
2659a49e SK |
217 | */ |
218 | ||
219 | assert(t); | |
220 | assert(usec); | |
221 | ||
2659a49e SK |
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; | |
fef9b04d PU |
256 | } else if (t[0] == '@') { |
257 | k = strptime(t + 1, "%s", &tm); | |
258 | if (k && *k == 0) | |
259 | goto finish; | |
c061d1a7 TW |
260 | else if (k && parse_subseconds(k, &ret) == 0) |
261 | goto finish; | |
2659a49e | 262 | |
fef9b04d | 263 | return -EINVAL; |
2659a49e SK |
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); | |
06264d63 | 272 | free(z); |
2659a49e SK |
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; | |
c061d1a7 TW |
298 | else if (k && parse_subseconds(k, &ret) == 0) |
299 | goto finish; | |
2659a49e SK |
300 | |
301 | tm = copy; | |
302 | k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); | |
303 | if (k && *k == 0) | |
304 | goto finish; | |
c061d1a7 TW |
305 | else if (k && parse_subseconds(k, &ret) == 0) |
306 | goto finish; | |
2659a49e | 307 | |
79995631 KZ |
308 | tm = copy; |
309 | k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm); | |
310 | if (k && *k == 0) | |
311 | goto finish; | |
c061d1a7 TW |
312 | else if (k && parse_subseconds(k, &ret) == 0) |
313 | goto finish; | |
79995631 | 314 | |
2659a49e SK |
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; | |
c061d1a7 TW |
347 | else if (k && parse_subseconds(k, &ret) == 0) |
348 | goto finish; | |
2659a49e SK |
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 | ||
75ab9bf1 SK |
357 | tm = copy; |
358 | k = strptime(t, "%Y%m%d%H%M%S", &tm); | |
2abb8394 | 359 | if (k && *k == 0) |
75ab9bf1 | 360 | goto finish; |
c061d1a7 TW |
361 | else if (k && parse_subseconds(k, &ret) == 0) |
362 | goto finish; | |
75ab9bf1 | 363 | |
2659a49e SK |
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 | ||
c061d1a7 | 374 | ret += (usec_t) x * USEC_PER_SEC; |
2659a49e SK |
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 | } | |
3c201431 | 386 | |
15a1f536 TW |
387 | int parse_timestamp(const char *t, usec_t *usec) |
388 | { | |
389 | return parse_timestamp_reference(time(NULL), t, usec); | |
390 | } | |
391 | ||
9fd0a7a9 WP |
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) | |
ad524980 | 400 | return 0; |
9fd0a7a9 WP |
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(<m); | |
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))) | |
421 | return 0; | |
422 | } | |
423 | ||
424 | if (! gmtime_r(<, >m)) | |
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 | ||
136f4874 | 451 | static int format_iso_time(const struct tm *tm, uint32_t nsec, int flags, char *buf, size_t bufsz) |
3c201431 | 452 | { |
136f4874 | 453 | uint32_t usec = nsec / NSEC_PER_USEC; |
33c7ffa3 KZ |
454 | char *p = buf; |
455 | int len; | |
3c201431 | 456 | |
4111bb3a | 457 | if (flags & ISO_DATE) { |
2d9a0b0a WP |
458 | len = snprintf(p, bufsz, "%4ld-%.2d-%.2d", |
459 | tm->tm_year + (long) 1900, | |
460 | tm->tm_mon + 1, tm->tm_mday); | |
33c7ffa3 | 461 | if (len < 0 || (size_t) len > bufsz) |
6cdc7b9c | 462 | goto err; |
33c7ffa3 KZ |
463 | bufsz -= len; |
464 | p += len; | |
465 | } | |
466 | ||
4111bb3a | 467 | if ((flags & ISO_DATE) && (flags & ISO_TIME)) { |
33c7ffa3 | 468 | if (bufsz < 1) |
6cdc7b9c | 469 | goto err; |
4111bb3a | 470 | *p++ = (flags & ISO_T) ? 'T' : ' '; |
33c7ffa3 KZ |
471 | bufsz--; |
472 | } | |
3c201431 | 473 | |
4111bb3a | 474 | if (flags & ISO_TIME) { |
33c7ffa3 | 475 | len = snprintf(p, bufsz, "%02d:%02d:%02d", tm->tm_hour, |
2d9a0b0a | 476 | tm->tm_min, tm->tm_sec); |
33c7ffa3 | 477 | if (len < 0 || (size_t) len > bufsz) |
6cdc7b9c | 478 | goto err; |
33c7ffa3 KZ |
479 | bufsz -= len; |
480 | p += len; | |
481 | } | |
3c201431 | 482 | |
136f4874 TW |
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); | |
33c7ffa3 | 498 | if (len < 0 || (size_t) len > bufsz) |
6cdc7b9c | 499 | goto err; |
33c7ffa3 KZ |
500 | bufsz -= len; |
501 | p += len; | |
502 | ||
4111bb3a | 503 | } else if (flags & ISO_COMMAUSEC) { |
136f4874 | 504 | len = snprintf(p, bufsz, ",%06"PRIu32, usec); |
33c7ffa3 | 505 | if (len < 0 || (size_t) len > bufsz) |
6cdc7b9c | 506 | goto err; |
33c7ffa3 KZ |
507 | bufsz -= len; |
508 | p += len; | |
509 | } | |
3c201431 | 510 | |
4111bb3a | 511 | if (flags & ISO_TIMEZONE) { |
9fd0a7a9 WP |
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) | |
6cdc7b9c | 517 | goto err; |
9fd0a7a9 | 518 | } |
33c7ffa3 | 519 | return 0; |
6cdc7b9c WP |
520 | err: |
521 | warnx(_("format_iso_time: buffer overflow.")); | |
522 | return -1; | |
3c201431 KZ |
523 | } |
524 | ||
b9abaae3 TW |
525 | /* timespec to ISO 8601 */ |
526 | int strtimespec_iso(const struct timespec *ts, int flags, char *buf, size_t bufsz) | |
3c201431 | 527 | { |
c1616946 | 528 | struct tm tm; |
ee475ab2 | 529 | struct tm *rc; |
c1616946 | 530 | |
4111bb3a | 531 | if (flags & ISO_GMTIME) |
b9abaae3 | 532 | rc = gmtime_r(&ts->tv_sec, &tm); |
c1616946 | 533 | else |
b9abaae3 | 534 | rc = localtime_r(&ts->tv_sec, &tm); |
ee475ab2 WP |
535 | |
536 | if (rc) | |
b9abaae3 | 537 | return format_iso_time(&tm, ts->tv_nsec, flags, buf, bufsz); |
ee475ab2 | 538 | |
b9abaae3 | 539 | warnx(_("time %"PRId64" is out of range."), (int64_t)(ts->tv_sec)); |
ee475ab2 | 540 | return -1; |
3c201431 KZ |
541 | } |
542 | ||
b9abaae3 TW |
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 | ||
33c7ffa3 | 554 | /* struct tm to ISO 8601 */ |
971244fd | 555 | int strtm_iso(const struct tm *tm, int flags, char *buf, size_t bufsz) |
3c201431 | 556 | { |
33c7ffa3 | 557 | return format_iso_time(tm, 0, flags, buf, bufsz); |
3c201431 KZ |
558 | } |
559 | ||
33c7ffa3 KZ |
560 | /* time_t to ISO 8601 */ |
561 | int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz) | |
3c201431 | 562 | { |
c1616946 | 563 | struct tm tm; |
ee475ab2 | 564 | struct tm *rc; |
c1616946 | 565 | |
4111bb3a | 566 | if (flags & ISO_GMTIME) |
ee475ab2 | 567 | rc = gmtime_r(t, &tm); |
c1616946 | 568 | else |
ee475ab2 WP |
569 | rc = localtime_r(t, &tm); |
570 | ||
571 | if (rc) | |
572 | return format_iso_time(&tm, 0, flags, buf, bufsz); | |
573 | ||
ce3355cc | 574 | warnx(_("time %"PRId64" is out of range."), (int64_t)*t); |
ee475ab2 | 575 | return -1; |
3c201431 KZ |
576 | } |
577 | ||
eee665bb | 578 | /* relative time functions */ |
d393c00c SK |
579 | static inline int time_is_thisyear(struct tm const *const tm, |
580 | struct tm const *const tmnow) | |
eee665bb | 581 | { |
d393c00c | 582 | return tm->tm_year == tmnow->tm_year; |
eee665bb KZ |
583 | } |
584 | ||
d393c00c SK |
585 | static inline int time_is_today(struct tm const *const tm, |
586 | struct tm const *const tmnow) | |
eee665bb | 587 | { |
d393c00c SK |
588 | return (tm->tm_yday == tmnow->tm_yday && |
589 | time_is_thisyear(tm, tmnow)); | |
eee665bb KZ |
590 | } |
591 | ||
592 | int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz) | |
593 | { | |
d393c00c | 594 | struct tm tm, tmnow; |
eee665bb KZ |
595 | int rc = 0; |
596 | ||
d393c00c SK |
597 | if (now->tv_sec == 0) |
598 | gettimeofday(now, NULL); | |
599 | ||
600 | localtime_r(t, &tm); | |
601 | localtime_r(&now->tv_sec, &tmnow); | |
eee665bb | 602 | |
d393c00c | 603 | if (time_is_today(&tm, &tmnow)) { |
eee665bb KZ |
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 | ||
d393c00c | 609 | } else if (time_is_thisyear(&tm, &tmnow)) { |
eee665bb KZ |
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 | } | |
3c201431 | 619 | |
00ddcaf1 TW |
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 | ||
b72a75e9 SK |
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 | ||
3c201431 KZ |
695 | #ifdef TEST_PROGRAM_TIMEUTILS |
696 | ||
d9cc65cb TW |
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[] = { | |
c061d1a7 TW |
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 }, | |
d9cc65cb TW |
723 | }; |
724 | ||
73fb8efe | 725 | setenv("TZ", "GMT", 1); |
d9cc65cb TW |
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 | ||
5c20665a TW |
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 | ||
00ddcaf1 TW |
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 | ||
3c201431 KZ |
834 | int main(int argc, char *argv[]) |
835 | { | |
6ebca9f1 | 836 | struct timespec ts = { 0 }; |
4111bb3a | 837 | char buf[ISO_BUFSIZ]; |
3c201431 KZ |
838 | |
839 | if (argc < 2) { | |
d9cc65cb | 840 | fprintf(stderr, "usage: %s [<time> [<usec>]] | [--timestamp <str>] | [--unittest-timestamp]\n", argv[0]); |
3c201431 KZ |
841 | exit(EXIT_FAILURE); |
842 | } | |
843 | ||
5c20665a | 844 | if (strcmp(argv[1], "--unittest-timestamp") == 0) |
d9cc65cb | 845 | return run_unittest_timestamp(); |
5c20665a TW |
846 | else if (strcmp(argv[1], "--unittest-format") == 0) |
847 | return run_unittest_format(); | |
00ddcaf1 TW |
848 | else if (strcmp(argv[1], "--unittest-format-relative") == 0) |
849 | return run_unittest_format_relative(); | |
d9cc65cb | 850 | |
79995631 | 851 | if (strcmp(argv[1], "--timestamp") == 0) { |
7ed57952 | 852 | usec_t usec = 0; |
79995631 KZ |
853 | |
854 | parse_timestamp(argv[2], &usec); | |
6ebca9f1 TW |
855 | ts.tv_sec = (time_t) (usec / USEC_PER_SEC); |
856 | ts.tv_nsec = (usec % USEC_PER_SEC) * NSEC_PER_USEC; | |
79995631 | 857 | } else { |
6ebca9f1 | 858 | ts.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>"); |
79995631 | 859 | if (argc == 3) |
6ebca9f1 TW |
860 | ts.tv_nsec = strtos64_or_err(argv[2], "failed to parse <usec>") |
861 | * NSEC_PER_USEC; | |
79995631 | 862 | } |
3c201431 | 863 | |
6ebca9f1 | 864 | strtimespec_iso(&ts, ISO_DATE, buf, sizeof(buf)); |
33c7ffa3 | 865 | printf("Date: '%s'\n", buf); |
3c201431 | 866 | |
6ebca9f1 | 867 | strtimespec_iso(&ts, ISO_TIME, buf, sizeof(buf)); |
33c7ffa3 | 868 | printf("Time: '%s'\n", buf); |
3c201431 | 869 | |
6ebca9f1 | 870 | strtimespec_iso(&ts, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T, |
4111bb3a | 871 | buf, sizeof(buf)); |
33c7ffa3 | 872 | printf("Full: '%s'\n", buf); |
3c201431 | 873 | |
6ebca9f1 | 874 | strtimespec_iso(&ts, ISO_TIMESTAMP_DOT, buf, sizeof(buf)); |
33c7ffa3 | 875 | printf("Zone: '%s'\n", buf); |
3c201431 | 876 | |
00ddcaf1 TW |
877 | strtimespec_relative(&ts, buf, sizeof(buf)); |
878 | printf("Rel: '%s'\n", buf); | |
879 | ||
3c201431 KZ |
880 | return EXIT_SUCCESS; |
881 | } | |
882 | ||
e8f7acb0 | 883 | #endif /* TEST_PROGRAM_TIMEUTILS */ |