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