]> git.ipfire.org Git - thirdparty/util-linux.git/blame - lib/timeutils.c
Merge branch 'fix-map-current-user-shortopt' of https://github.com/mat8913/util-linux
[thirdparty/util-linux.git] / lib / timeutils.c
CommitLineData
2659a49e
SK
1/***
2 First set of functions in this file are part of systemd, and were
3 copied to util-linux at August 2013.
4
5 Copyright 2010 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with util-linux; If not, see <http://www.gnu.org/licenses/>.
19***/
20
21#include <assert.h>
22#include <ctype.h>
b72a75e9 23#include <stdlib.h>
2659a49e
SK
24#include <string.h>
25#include <time.h>
33c7ffa3 26#include <sys/time.h>
2659a49e
SK
27
28#include "c.h"
929f939e 29#include "nls.h"
60f25dea
KZ
30#include "strutils.h"
31#include "timeutils.h"
2659a49e
SK
32
33#define WHITESPACE " \t\n\r"
34
35#define streq(a,b) (strcmp((a),(b)) == 0)
36
2659a49e
SK
37static int parse_sec(const char *t, usec_t *usec)
38{
9d826076 39 static const struct {
2659a49e
SK
40 const char *suffix;
41 usec_t usec;
9d826076 42 } table[] = {
2659a49e
SK
43 { "seconds", USEC_PER_SEC },
44 { "second", USEC_PER_SEC },
45 { "sec", USEC_PER_SEC },
46 { "s", USEC_PER_SEC },
47 { "minutes", USEC_PER_MINUTE },
48 { "minute", USEC_PER_MINUTE },
49 { "min", USEC_PER_MINUTE },
50 { "months", USEC_PER_MONTH },
51 { "month", USEC_PER_MONTH },
52 { "msec", USEC_PER_MSEC },
53 { "ms", USEC_PER_MSEC },
54 { "m", USEC_PER_MINUTE },
55 { "hours", USEC_PER_HOUR },
56 { "hour", USEC_PER_HOUR },
57 { "hr", USEC_PER_HOUR },
58 { "h", USEC_PER_HOUR },
59 { "days", USEC_PER_DAY },
60 { "day", USEC_PER_DAY },
61 { "d", USEC_PER_DAY },
62 { "weeks", USEC_PER_WEEK },
63 { "week", USEC_PER_WEEK },
64 { "w", USEC_PER_WEEK },
65 { "years", USEC_PER_YEAR },
66 { "year", USEC_PER_YEAR },
67 { "y", USEC_PER_YEAR },
68 { "usec", 1ULL },
69 { "us", 1ULL },
70 { "", USEC_PER_SEC }, /* default is sec */
9d826076 71 };
2659a49e
SK
72
73 const char *p;
74 usec_t r = 0;
75 int something = FALSE;
76
77 assert(t);
78 assert(usec);
79
80 p = t;
81 for (;;) {
82 long long l, z = 0;
83 char *e;
84 unsigned i, n = 0;
85
86 p += strspn(p, WHITESPACE);
87
88 if (*p == 0) {
89 if (!something)
90 return -EINVAL;
91
92 break;
93 }
94
95 errno = 0;
96 l = strtoll(p, &e, 10);
97
98 if (errno > 0)
99 return -errno;
100
101 if (l < 0)
102 return -ERANGE;
103
104 if (*e == '.') {
105 char *b = e + 1;
106
107 errno = 0;
108 z = strtoll(b, &e, 10);
109 if (errno > 0)
110 return -errno;
111
112 if (z < 0)
113 return -ERANGE;
114
115 if (e == b)
116 return -EINVAL;
117
118 n = e - b;
119
120 } else if (e == p)
121 return -EINVAL;
122
123 e += strspn(e, WHITESPACE);
124
125 for (i = 0; i < ARRAY_SIZE(table); i++)
126 if (startswith(e, table[i].suffix)) {
127 usec_t k = (usec_t) z * table[i].usec;
128
129 for (; n > 0; n--)
130 k /= 10;
131
132 r += (usec_t) l *table[i].usec + k;
133 p = e + strlen(table[i].suffix);
134
135 something = TRUE;
136 break;
137 }
138
139 if (i >= ARRAY_SIZE(table))
140 return -EINVAL;
141
142 }
143
144 *usec = r;
145
146 return 0;
147}
148
149int parse_timestamp(const char *t, usec_t *usec)
150{
9d826076 151 static const struct {
2659a49e
SK
152 const char *name;
153 const int nr;
9d826076 154 } day_nr[] = {
2659a49e
SK
155 { "Sunday", 0 },
156 { "Sun", 0 },
157 { "Monday", 1 },
158 { "Mon", 1 },
159 { "Tuesday", 2 },
160 { "Tue", 2 },
161 { "Wednesday", 3 },
162 { "Wed", 3 },
163 { "Thursday", 4 },
164 { "Thu", 4 },
165 { "Friday", 5 },
166 { "Fri", 5 },
167 { "Saturday", 6 },
168 { "Sat", 6 },
9d826076 169 };
2659a49e
SK
170
171 const char *k;
172 struct tm tm, copy;
173 time_t x;
174 usec_t plus = 0, minus = 0, ret;
175 int r, weekday = -1;
176 unsigned i;
177
178 /*
179 * Allowed syntaxes:
180 *
181 * 2012-09-22 16:34:22
79995631 182 * 2012-09-22T16:34:22
2659a49e
SK
183 * 2012-09-22 16:34 (seconds will be set to 0)
184 * 2012-09-22 (time will be set to 00:00:00)
185 * 16:34:22 (date will be set to today)
186 * 16:34 (date will be set to today, seconds to 0)
187 * now
188 * yesterday (time is set to 00:00:00)
189 * today (time is set to 00:00:00)
190 * tomorrow (time is set to 00:00:00)
191 * +5min
192 * -5days
193 *
194 */
195
196 assert(t);
197 assert(usec);
198
199 x = time(NULL);
200 localtime_r(&x, &tm);
201 tm.tm_isdst = -1;
202
203 if (streq(t, "now"))
204 goto finish;
205
206 else if (streq(t, "today")) {
207 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
208 goto finish;
209
210 } else if (streq(t, "yesterday")) {
211 tm.tm_mday--;
212 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
213 goto finish;
214
215 } else if (streq(t, "tomorrow")) {
216 tm.tm_mday++;
217 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
218 goto finish;
219
220 } else if (t[0] == '+') {
221
222 r = parse_sec(t + 1, &plus);
223 if (r < 0)
224 return r;
225
226 goto finish;
227 } else if (t[0] == '-') {
228
229 r = parse_sec(t + 1, &minus);
230 if (r < 0)
231 return r;
232
233 goto finish;
234
235 } else if (endswith(t, " ago")) {
236 char *z;
237
238 z = strndup(t, strlen(t) - 4);
239 if (!z)
240 return -ENOMEM;
241
242 r = parse_sec(z, &minus);
06264d63 243 free(z);
2659a49e
SK
244 if (r < 0)
245 return r;
246
247 goto finish;
248 }
249
250 for (i = 0; i < ARRAY_SIZE(day_nr); i++) {
251 size_t skip;
252
253 if (!startswith_no_case(t, day_nr[i].name))
254 continue;
255
256 skip = strlen(day_nr[i].name);
257 if (t[skip] != ' ')
258 continue;
259
260 weekday = day_nr[i].nr;
261 t += skip + 1;
262 break;
263 }
264
265 copy = tm;
266 k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
267 if (k && *k == 0)
268 goto finish;
269
270 tm = copy;
271 k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
272 if (k && *k == 0)
273 goto finish;
274
79995631
KZ
275 tm = copy;
276 k = strptime(t, "%Y-%m-%dT%H:%M:%S", &tm);
277 if (k && *k == 0)
278 goto finish;
279
2659a49e
SK
280 tm = copy;
281 k = strptime(t, "%y-%m-%d %H:%M", &tm);
282 if (k && *k == 0) {
283 tm.tm_sec = 0;
284 goto finish;
285 }
286
287 tm = copy;
288 k = strptime(t, "%Y-%m-%d %H:%M", &tm);
289 if (k && *k == 0) {
290 tm.tm_sec = 0;
291 goto finish;
292 }
293
294 tm = copy;
295 k = strptime(t, "%y-%m-%d", &tm);
296 if (k && *k == 0) {
297 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
298 goto finish;
299 }
300
301 tm = copy;
302 k = strptime(t, "%Y-%m-%d", &tm);
303 if (k && *k == 0) {
304 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
305 goto finish;
306 }
307
308 tm = copy;
309 k = strptime(t, "%H:%M:%S", &tm);
310 if (k && *k == 0)
311 goto finish;
312
313 tm = copy;
314 k = strptime(t, "%H:%M", &tm);
315 if (k && *k == 0) {
316 tm.tm_sec = 0;
317 goto finish;
318 }
319
75ab9bf1
SK
320 tm = copy;
321 k = strptime(t, "%Y%m%d%H%M%S", &tm);
322 if (k && *k == 0) {
323 tm.tm_sec = 0;
324 goto finish;
325 }
326
2659a49e
SK
327 return -EINVAL;
328
329 finish:
330 x = mktime(&tm);
331 if (x == (time_t)-1)
332 return -EINVAL;
333
334 if (weekday >= 0 && tm.tm_wday != weekday)
335 return -EINVAL;
336
337 ret = (usec_t) x *USEC_PER_SEC;
338
339 ret += plus;
340 if (ret > minus)
341 ret -= minus;
342 else
343 ret = 0;
344
345 *usec = ret;
346
347 return 0;
348}
3c201431 349
9fd0a7a9
WP
350/* Returns the difference in seconds between its argument and GMT. If if TP is
351 * invalid or no DST information is available default to UTC, that is, zero.
352 * tzset is called so, for example, 'TZ="UTC" hwclock' will work as expected.
353 * Derived from glibc/time/strftime_l.c
354 */
355int get_gmtoff(const struct tm *tp)
356{
357 if (tp->tm_isdst < 0)
358 return 0;
359
360#if HAVE_TM_GMTOFF
361 return tp->tm_gmtoff;
362#else
363 struct tm tm;
364 struct tm gtm;
365 struct tm ltm = *tp;
366 time_t lt;
367
368 tzset();
369 lt = mktime(&ltm);
370 /* Check if mktime returning -1 is an error or a valid time_t */
371 if (lt == (time_t) -1) {
372 if (! localtime_r(&lt, &tm)
373 || ((ltm.tm_sec ^ tm.tm_sec)
374 | (ltm.tm_min ^ tm.tm_min)
375 | (ltm.tm_hour ^ tm.tm_hour)
376 | (ltm.tm_mday ^ tm.tm_mday)
377 | (ltm.tm_mon ^ tm.tm_mon)
378 | (ltm.tm_year ^ tm.tm_year)))
379 return 0;
380 }
381
382 if (! gmtime_r(&lt, &gtm))
383 return 0;
384
385 /* Calculate the GMT offset, that is, the difference between the
386 * TP argument (ltm) and GMT (gtm).
387 *
388 * Compute intervening leap days correctly even if year is negative.
389 * Take care to avoid int overflow in leap day calculations, but it's OK
390 * to assume that A and B are close to each other.
391 */
392 int a4 = (ltm.tm_year >> 2) + (1900 >> 2) - ! (ltm.tm_year & 3);
393 int b4 = (gtm.tm_year >> 2) + (1900 >> 2) - ! (gtm.tm_year & 3);
394 int a100 = a4 / 25 - (a4 % 25 < 0);
395 int b100 = b4 / 25 - (b4 % 25 < 0);
396 int a400 = a100 >> 2;
397 int b400 = b100 >> 2;
398 int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
399
400 int years = ltm.tm_year - gtm.tm_year;
401 int days = (365 * years + intervening_leap_days
402 + (ltm.tm_yday - gtm.tm_yday));
403
404 return (60 * (60 * (24 * days + (ltm.tm_hour - gtm.tm_hour))
405 + (ltm.tm_min - gtm.tm_min)) + (ltm.tm_sec - gtm.tm_sec));
406#endif
407}
408
33c7ffa3 409static int format_iso_time(struct tm *tm, suseconds_t usec, int flags, char *buf, size_t bufsz)
3c201431 410{
33c7ffa3
KZ
411 char *p = buf;
412 int len;
3c201431 413
4111bb3a 414 if (flags & ISO_DATE) {
2d9a0b0a
WP
415 len = snprintf(p, bufsz, "%4ld-%.2d-%.2d",
416 tm->tm_year + (long) 1900,
417 tm->tm_mon + 1, tm->tm_mday);
33c7ffa3 418 if (len < 0 || (size_t) len > bufsz)
6cdc7b9c 419 goto err;
33c7ffa3
KZ
420 bufsz -= len;
421 p += len;
422 }
423
4111bb3a 424 if ((flags & ISO_DATE) && (flags & ISO_TIME)) {
33c7ffa3 425 if (bufsz < 1)
6cdc7b9c 426 goto err;
4111bb3a 427 *p++ = (flags & ISO_T) ? 'T' : ' ';
33c7ffa3
KZ
428 bufsz--;
429 }
3c201431 430
4111bb3a 431 if (flags & ISO_TIME) {
33c7ffa3 432 len = snprintf(p, bufsz, "%02d:%02d:%02d", tm->tm_hour,
2d9a0b0a 433 tm->tm_min, tm->tm_sec);
33c7ffa3 434 if (len < 0 || (size_t) len > bufsz)
6cdc7b9c 435 goto err;
33c7ffa3
KZ
436 bufsz -= len;
437 p += len;
438 }
3c201431 439
4111bb3a 440 if (flags & ISO_DOTUSEC) {
33c7ffa3
KZ
441 len = snprintf(p, bufsz, ".%06ld", (long) usec);
442 if (len < 0 || (size_t) len > bufsz)
6cdc7b9c 443 goto err;
33c7ffa3
KZ
444 bufsz -= len;
445 p += len;
446
4111bb3a 447 } else if (flags & ISO_COMMAUSEC) {
33c7ffa3
KZ
448 len = snprintf(p, bufsz, ",%06ld", (long) usec);
449 if (len < 0 || (size_t) len > bufsz)
6cdc7b9c 450 goto err;
33c7ffa3
KZ
451 bufsz -= len;
452 p += len;
453 }
3c201431 454
4111bb3a 455 if (flags & ISO_TIMEZONE) {
9fd0a7a9
WP
456 int tmin = get_gmtoff(tm) / 60;
457 int zhour = tmin / 60;
458 int zmin = abs(tmin % 60);
459 len = snprintf(p, bufsz, "%+03d:%02d", zhour,zmin);
460 if (len < 0 || (size_t) len > bufsz)
6cdc7b9c 461 goto err;
9fd0a7a9 462 }
33c7ffa3 463 return 0;
6cdc7b9c
WP
464 err:
465 warnx(_("format_iso_time: buffer overflow."));
466 return -1;
3c201431
KZ
467}
468
33c7ffa3
KZ
469/* timeval to ISO 8601 */
470int strtimeval_iso(struct timeval *tv, int flags, char *buf, size_t bufsz)
3c201431 471{
c1616946 472 struct tm tm;
ee475ab2 473 struct tm *rc;
c1616946 474
4111bb3a 475 if (flags & ISO_GMTIME)
ee475ab2 476 rc = gmtime_r(&tv->tv_sec, &tm);
c1616946 477 else
ee475ab2
WP
478 rc = localtime_r(&tv->tv_sec, &tm);
479
480 if (rc)
481 return format_iso_time(&tm, tv->tv_usec, flags, buf, bufsz);
482
483 warnx(_("time %ld is out of range."), tv->tv_sec);
484 return -1;
3c201431
KZ
485}
486
33c7ffa3
KZ
487/* struct tm to ISO 8601 */
488int strtm_iso(struct tm *tm, int flags, char *buf, size_t bufsz)
3c201431 489{
33c7ffa3 490 return format_iso_time(tm, 0, flags, buf, bufsz);
3c201431
KZ
491}
492
33c7ffa3
KZ
493/* time_t to ISO 8601 */
494int strtime_iso(const time_t *t, int flags, char *buf, size_t bufsz)
3c201431 495{
c1616946 496 struct tm tm;
ee475ab2 497 struct tm *rc;
c1616946 498
4111bb3a 499 if (flags & ISO_GMTIME)
ee475ab2 500 rc = gmtime_r(t, &tm);
c1616946 501 else
ee475ab2
WP
502 rc = localtime_r(t, &tm);
503
504 if (rc)
505 return format_iso_time(&tm, 0, flags, buf, bufsz);
506
507 warnx(_("time %ld is out of range."), (long)t);
508 return -1;
3c201431
KZ
509}
510
eee665bb 511/* relative time functions */
d393c00c
SK
512static inline int time_is_thisyear(struct tm const *const tm,
513 struct tm const *const tmnow)
eee665bb 514{
d393c00c 515 return tm->tm_year == tmnow->tm_year;
eee665bb
KZ
516}
517
d393c00c
SK
518static inline int time_is_today(struct tm const *const tm,
519 struct tm const *const tmnow)
eee665bb 520{
d393c00c
SK
521 return (tm->tm_yday == tmnow->tm_yday &&
522 time_is_thisyear(tm, tmnow));
eee665bb
KZ
523}
524
525int strtime_short(const time_t *t, struct timeval *now, int flags, char *buf, size_t bufsz)
526{
d393c00c 527 struct tm tm, tmnow;
eee665bb
KZ
528 int rc = 0;
529
d393c00c
SK
530 if (now->tv_sec == 0)
531 gettimeofday(now, NULL);
532
533 localtime_r(t, &tm);
534 localtime_r(&now->tv_sec, &tmnow);
eee665bb 535
d393c00c 536 if (time_is_today(&tm, &tmnow)) {
eee665bb
KZ
537 rc = snprintf(buf, bufsz, "%02d:%02d", tm.tm_hour, tm.tm_min);
538 if (rc < 0 || (size_t) rc > bufsz)
539 return -1;
540 rc = 1;
541
d393c00c 542 } else if (time_is_thisyear(&tm, &tmnow)) {
eee665bb
KZ
543 if (flags & UL_SHORTTIME_THISYEAR_HHMM)
544 rc = strftime(buf, bufsz, "%b%d/%H:%M", &tm);
545 else
546 rc = strftime(buf, bufsz, "%b%d", &tm);
547 } else
548 rc = strftime(buf, bufsz, "%Y-%b%d", &tm);
549
550 return rc <= 0 ? -1 : 0;
551}
3c201431 552
b72a75e9
SK
553#ifndef HAVE_TIMEGM
554time_t timegm(struct tm *tm)
555{
556 const char *zone = getenv("TZ");
557 time_t ret;
558
559 setenv("TZ", "", 1);
560 tzset();
561 ret = mktime(tm);
562 if (zone)
563 setenv("TZ", zone, 1);
564 else
565 unsetenv("TZ");
566 tzset();
567 return ret;
568}
569#endif /* HAVE_TIMEGM */
570
3c201431
KZ
571#ifdef TEST_PROGRAM_TIMEUTILS
572
573int main(int argc, char *argv[])
574{
575 struct timeval tv = { 0 };
4111bb3a 576 char buf[ISO_BUFSIZ];
3c201431
KZ
577
578 if (argc < 2) {
79995631 579 fprintf(stderr, "usage: %s [<time> [<usec>]] | [--timestamp <str>]\n", argv[0]);
3c201431
KZ
580 exit(EXIT_FAILURE);
581 }
582
79995631
KZ
583 if (strcmp(argv[1], "--timestamp") == 0) {
584 usec_t usec;
585
586 parse_timestamp(argv[2], &usec);
587 tv.tv_sec = (time_t) (usec / 1000000);
588 tv.tv_usec = usec % 1000000;
589 } else {
590 tv.tv_sec = strtos64_or_err(argv[1], "failed to parse <time>");
591 if (argc == 3)
592 tv.tv_usec = strtos64_or_err(argv[2], "failed to parse <usec>");
593 }
3c201431 594
4111bb3a 595 strtimeval_iso(&tv, ISO_DATE, buf, sizeof(buf));
33c7ffa3 596 printf("Date: '%s'\n", buf);
3c201431 597
4111bb3a 598 strtimeval_iso(&tv, ISO_TIME, buf, sizeof(buf));
33c7ffa3 599 printf("Time: '%s'\n", buf);
3c201431 600
4111bb3a
WP
601 strtimeval_iso(&tv, ISO_DATE | ISO_TIME | ISO_COMMAUSEC | ISO_T,
602 buf, sizeof(buf));
33c7ffa3 603 printf("Full: '%s'\n", buf);
3c201431 604
4111bb3a 605 strtimeval_iso(&tv, ISO_TIMESTAMP_DOT, buf, sizeof(buf));
33c7ffa3 606 printf("Zone: '%s'\n", buf);
3c201431
KZ
607
608 return EXIT_SUCCESS;
609}
610
e8f7acb0 611#endif /* TEST_PROGRAM_TIMEUTILS */