]>
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 | ||
10e07837 TW |
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 | ||
15a1f536 | 181 | static int parse_timestamp_reference(time_t x, const char *t, usec_t *usec) |
2659a49e | 182 | { |
9d826076 | 183 | static const struct { |
2659a49e SK |
184 | const char *name; |
185 | const int nr; | |
9d826076 | 186 | } day_nr[] = { |
2659a49e SK |
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 }, | |
9d826076 | 201 | }; |
2659a49e SK |
202 | |
203 | const char *k; | |
204 | struct tm tm, copy; | |
c061d1a7 | 205 | usec_t plus = 0, minus = 0, ret = 0; |
2659a49e SK |
206 | int r, weekday = -1; |
207 | unsigned i; | |
208 | ||
209 | /* | |
210 | * Allowed syntaxes: | |
211 | * | |
c061d1a7 TW |
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) | |
2659a49e | 220 | * now |
c061d1a7 TW |
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) | |
2659a49e SK |
224 | * +5min |
225 | * -5days | |
226 | * | |
c061d1a7 TW |
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 | * | |
2659a49e SK |
234 | */ |
235 | ||
236 | assert(t); | |
237 | assert(usec); | |
238 | ||
2659a49e SK |
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; | |
fef9b04d | 273 | } else if (t[0] == '@') { |
10e07837 | 274 | k = parse_epoch_seconds(t + 1, &tm); |
fef9b04d PU |
275 | if (k && *k == 0) |
276 | goto finish; | |
c061d1a7 TW |
277 | else if (k && parse_subseconds(k, &ret) == 0) |
278 | goto finish; | |
2659a49e | 279 | |
fef9b04d | 280 | return -EINVAL; |
2659a49e SK |
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); | |
06264d63 | 289 | free(z); |
2659a49e SK |
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; | |
c061d1a7 TW |
315 | else if (k && parse_subseconds(k, &ret) == 0) |
316 | goto finish; | |
2659a49e SK |
317 | |
318 | tm = copy; | |
319 | k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); | |
320 | if (k && *k == 0) | |
321 | goto finish; | |
c061d1a7 TW |
322 | else if (k && parse_subseconds(k, &ret) == 0) |
323 | goto finish; | |
2659a49e | 324 | |
79995631 KZ |
325 | tm = copy; |
326 | k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm); | |
327 | if (k && *k == 0) | |
328 | goto finish; | |
c061d1a7 TW |
329 | else if (k && parse_subseconds(k, &ret) == 0) |
330 | goto finish; | |
79995631 | 331 | |
2659a49e SK |
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; | |
c061d1a7 TW |
364 | else if (k && parse_subseconds(k, &ret) == 0) |
365 | goto finish; | |
2659a49e SK |
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 | ||
75ab9bf1 SK |
374 | tm = copy; |
375 | k = strptime(t, "%Y%m%d%H%M%S", &tm); | |
2abb8394 | 376 | if (k && *k == 0) |
75ab9bf1 | 377 | goto finish; |
c061d1a7 TW |
378 | else if (k && parse_subseconds(k, &ret) == 0) |
379 | goto finish; | |
75ab9bf1 | 380 | |
2659a49e SK |
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 | ||
c061d1a7 | 391 | ret += (usec_t) x * USEC_PER_SEC; |
2659a49e | 392 | |
7eb08ae1 TW |
393 | if (minus > ret) |
394 | return -ERANGE; | |
395 | if ((ret + plus) < ret) | |
396 | return -ERANGE; | |
397 | ||
2659a49e | 398 | ret += plus; |
7eb08ae1 | 399 | ret -= minus; |
2659a49e SK |
400 | |
401 | *usec = ret; | |
402 | ||
403 | return 0; | |
404 | } | |
3c201431 | 405 | |
15a1f536 TW |
406 | int parse_timestamp(const char *t, usec_t *usec) |
407 | { | |
408 | return parse_timestamp_reference(time(NULL), t, usec); | |
409 | } | |
410 | ||
9fd0a7a9 WP |
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) | |
ad524980 | 419 | return 0; |
9fd0a7a9 WP |
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(<m); | |
431 | /* Check if mktime returning -1 is an error or a valid time_t */ | |
432 | if (lt == (time_t) -1) { | |
433 | if (! localtime_r(<, &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(<, >m)) | |
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 | ||
136f4874 | 470 | static int format_iso_time(const struct tm *tm, uint32_t nsec, int flags, char *buf, size_t bufsz) |
3c201431 | 471 | { |
136f4874 | 472 | uint32_t usec = nsec / NSEC_PER_USEC; |
33c7ffa3 KZ |
473 | char *p = buf; |
474 | int len; | |
3c201431 | 475 | |
4111bb3a | 476 | if (flags & ISO_DATE) { |
2d9a0b0a WP |
477 | len = snprintf(p, bufsz, "%4ld-%.2d-%.2d", |
478 | tm->tm_year + (long) 1900, | |
479 | tm->tm_mon + 1, tm->tm_mday); | |
33c7ffa3 | 480 | if (len < 0 || (size_t) len > bufsz) |
6cdc7b9c | 481 | goto err; |
33c7ffa3 KZ |
482 | bufsz -= len; |
483 | p += len; | |
484 | } | |
485 | ||
4111bb3a | 486 | if ((flags & ISO_DATE) && (flags & ISO_TIME)) { |
33c7ffa3 | 487 | if (bufsz < 1) |
6cdc7b9c | 488 | goto err; |
4111bb3a | 489 | *p++ = (flags & ISO_T) ? 'T' : ' '; |
33c7ffa3 KZ |
490 | bufsz--; |
491 | } | |
3c201431 | 492 | |
4111bb3a | 493 | if (flags & ISO_TIME) { |
33c7ffa3 | 494 | len = snprintf(p, bufsz, "%02d:%02d:%02d", tm->tm_hour, |
2d9a0b0a | 495 | tm->tm_min, tm->tm_sec); |
33c7ffa3 | 496 | if (len < 0 || (size_t) len > bufsz) |
6cdc7b9c | 497 | goto err; |
33c7ffa3 KZ |
498 | bufsz -= len; |
499 | p += len; | |
500 | } | |
3c201431 | 501 | |
136f4874 TW |
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); | |
33c7ffa3 | 517 | if (len < 0 || (size_t) len > bufsz) |
6cdc7b9c | 518 | goto err; |
33c7ffa3 KZ |
519 | bufsz -= len; |
520 | p += len; | |
521 | ||
4111bb3a | 522 | } else if (flags & ISO_COMMAUSEC) { |
136f4874 | 523 | len = snprintf(p, bufsz, ",%06"PRIu32, usec); |
33c7ffa3 | 524 | if (len < 0 || (size_t) len > bufsz) |
6cdc7b9c | 525 | goto err; |
33c7ffa3 KZ |
526 | bufsz -= len; |
527 | p += len; | |
528 | } | |
3c201431 | 529 | |
4111bb3a | 530 | if (flags & ISO_TIMEZONE) { |
9fd0a7a9 WP |
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) | |
6cdc7b9c | 536 | goto err; |
9fd0a7a9 | 537 | } |
33c7ffa3 | 538 | return 0; |
6cdc7b9c WP |
539 | err: |
540 | warnx(_("format_iso_time: buffer overflow.")); | |
541 | return -1; | |
3c201431 KZ |
542 | } |
543 | ||
b9abaae3 TW |
544 | /* timespec to ISO 8601 */ |
545 | int strtimespec_iso(const struct timespec *ts, int flags, char *buf, size_t bufsz) | |
3c201431 | 546 | { |
c1616946 | 547 | struct tm tm; |
ee475ab2 | 548 | struct tm *rc; |
c1616946 | 549 | |
4111bb3a | 550 | if (flags & ISO_GMTIME) |
b9abaae3 | 551 | rc = gmtime_r(&ts->tv_sec, &tm); |
c1616946 | 552 | else |
b9abaae3 | 553 | rc = localtime_r(&ts->tv_sec, &tm); |
ee475ab2 WP |
554 | |
555 | if (rc) | |
b9abaae3 | 556 | return format_iso_time(&tm, ts->tv_nsec, flags, buf, bufsz); |
ee475ab2 | 557 | |
b9abaae3 | 558 | warnx(_("time %"PRId64" is out of range."), (int64_t)(ts->tv_sec)); |
ee475ab2 | 559 | return -1; |
3c201431 KZ |
560 | } |
561 | ||
b9abaae3 TW |
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 | ||
33c7ffa3 | 573 | /* struct tm to ISO 8601 */ |
971244fd | 574 | int strtm_iso(const struct tm *tm, int flags, char *buf, size_t bufsz) |
3c201431 | 575 | { |
33c7ffa3 | 576 | return format_iso_time(tm, 0, flags, buf, bufsz); |
3c201431 KZ |
577 | } |
578 | ||
33c7ffa3 KZ |
579 | /* time_t to ISO 8601 */ |
580 | int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz) | |
3c201431 | 581 | { |
c1616946 | 582 | struct tm tm; |
ee475ab2 | 583 | struct tm *rc; |
c1616946 | 584 | |
4111bb3a | 585 | if (flags & ISO_GMTIME) |
ee475ab2 | 586 | rc = gmtime_r(t, &tm); |
c1616946 | 587 | else |
ee475ab2 WP |
588 | rc = localtime_r(t, &tm); |
589 | ||
590 | if (rc) | |
591 | return format_iso_time(&tm, 0, flags, buf, bufsz); | |
592 | ||
ce3355cc | 593 | warnx(_("time %"PRId64" is out of range."), (int64_t)*t); |
ee475ab2 | 594 | return -1; |
3c201431 KZ |
595 | } |
596 | ||
eee665bb | 597 | /* relative time functions */ |
d393c00c SK |
598 | static inline int time_is_thisyear(struct tm const *const tm, |
599 | struct tm const *const tmnow) | |
eee665bb | 600 | { |
d393c00c | 601 | return tm->tm_year == tmnow->tm_year; |
eee665bb KZ |
602 | } |
603 | ||
d393c00c SK |
604 | static inline int time_is_today(struct tm const *const tm, |
605 | struct tm const *const tmnow) | |
eee665bb | 606 | { |
d393c00c SK |
607 | return (tm->tm_yday == tmnow->tm_yday && |
608 | time_is_thisyear(tm, tmnow)); | |
eee665bb KZ |
609 | } |
610 | ||
611 | int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz) | |
612 | { | |
d393c00c | 613 | struct tm tm, tmnow; |
eee665bb KZ |
614 | int rc = 0; |
615 | ||
d393c00c SK |
616 | if (now->tv_sec == 0) |
617 | gettimeofday(now, NULL); | |
618 | ||
619 | localtime_r(t, &tm); | |
620 | localtime_r(&now->tv_sec, &tmnow); | |
eee665bb | 621 | |
d393c00c | 622 | if (time_is_today(&tm, &tmnow)) { |
eee665bb KZ |
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 | ||
d393c00c | 628 | } else if (time_is_thisyear(&tm, &tmnow)) { |
eee665bb KZ |
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 | } | |
3c201431 | 638 | |
00ddcaf1 TW |
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 | ||
b72a75e9 SK |
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 | ||
3c201431 KZ |
714 | #ifdef TEST_PROGRAM_TIMEUTILS |
715 | ||
d9cc65cb TW |
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[] = { | |
c061d1a7 TW |
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 }, | |
7d31644a | 729 | { "@0" , 0 }, |
c061d1a7 TW |
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 }, | |
d9cc65cb TW |
743 | }; |
744 | ||
73fb8efe | 745 | setenv("TZ", "GMT", 1); |
d9cc65cb TW |
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 | ||
5c20665a TW |
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 | ||
00ddcaf1 TW |
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 | ||
3c201431 KZ |
854 | int main(int argc, char *argv[]) |
855 | { | |
6ebca9f1 | 856 | struct timespec ts = { 0 }; |
4111bb3a | 857 | char buf[ISO_BUFSIZ]; |
ea4d6078 | 858 | int r; |
3c201431 KZ |
859 | |
860 | if (argc < 2) { | |
d9cc65cb | 861 | fprintf(stderr, "usage: %s [<time> [<usec>]] | [--timestamp <str>] | [--unittest-timestamp]\n", argv[0]); |
3c201431 KZ |
862 | exit(EXIT_FAILURE); |
863 | } | |
864 | ||
5c20665a | 865 | if (strcmp(argv[1], "--unittest-timestamp") == 0) |
d9cc65cb | 866 | return run_unittest_timestamp(); |
5c20665a TW |
867 | else if (strcmp(argv[1], "--unittest-format") == 0) |
868 | return run_unittest_format(); | |
00ddcaf1 TW |
869 | else if (strcmp(argv[1], "--unittest-format-relative") == 0) |
870 | return run_unittest_format_relative(); | |
d9cc65cb | 871 | |
79995631 | 872 | if (strcmp(argv[1], "--timestamp") == 0) { |
7ed57952 | 873 | usec_t usec = 0; |
79995631 | 874 | |
ea4d6078 TW |
875 | r = parse_timestamp(argv[2], &usec); |
876 | if (r) | |
877 | errx(EXIT_FAILURE, "Can not parse '%s': %s", argv[2], strerror(-r)); | |
6ebca9f1 TW |
878 | ts.tv_sec = (time_t) (usec / USEC_PER_SEC); |
879 | ts.tv_nsec = (usec % USEC_PER_SEC) * NSEC_PER_USEC; | |
79995631 | 880 | } else { |
6ebca9f1 | 881 | ts.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>"); |
79995631 | 882 | if (argc == 3) |
6ebca9f1 TW |
883 | ts.tv_nsec = strtos64_or_err(argv[2], "failed to parse <usec>") |
884 | * NSEC_PER_USEC; | |
79995631 | 885 | } |
3c201431 | 886 | |
6ebca9f1 | 887 | strtimespec_iso(&ts, ISO_DATE, buf, sizeof(buf)); |
33c7ffa3 | 888 | printf("Date: '%s'\n", buf); |
3c201431 | 889 | |
6ebca9f1 | 890 | strtimespec_iso(&ts, ISO_TIME, buf, sizeof(buf)); |
33c7ffa3 | 891 | printf("Time: '%s'\n", buf); |
3c201431 | 892 | |
6ebca9f1 | 893 | strtimespec_iso(&ts, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T, |
4111bb3a | 894 | buf, sizeof(buf)); |
33c7ffa3 | 895 | printf("Full: '%s'\n", buf); |
3c201431 | 896 | |
6ebca9f1 | 897 | strtimespec_iso(&ts, ISO_TIMESTAMP_DOT, buf, sizeof(buf)); |
33c7ffa3 | 898 | printf("Zone: '%s'\n", buf); |
3c201431 | 899 | |
00ddcaf1 TW |
900 | strtimespec_relative(&ts, buf, sizeof(buf)); |
901 | printf("Rel: '%s'\n", buf); | |
902 | ||
3c201431 KZ |
903 | return EXIT_SUCCESS; |
904 | } | |
905 | ||
e8f7acb0 | 906 | #endif /* TEST_PROGRAM_TIMEUTILS */ |