chiark / gitweb /
time: add suppot for fractional time specifications
[elogind.git] / src / shared / time-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <time.h>
23 #include <string.h>
24
25 #include "util.h"
26 #include "time-util.h"
27
28 usec_t now(clockid_t clock_id) {
29         struct timespec ts;
30
31         assert_se(clock_gettime(clock_id, &ts) == 0);
32
33         return timespec_load(&ts);
34 }
35
36 dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
37         assert(ts);
38
39         ts->realtime = now(CLOCK_REALTIME);
40         ts->monotonic = now(CLOCK_MONOTONIC);
41
42         return ts;
43 }
44
45 dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
46         int64_t delta;
47         assert(ts);
48
49         if (u == (usec_t) -1) {
50                 ts->realtime = ts->monotonic = (usec_t) -1;
51                 return ts;
52         }
53
54         ts->realtime = u;
55
56         if (u == 0)
57                 ts->monotonic = 0;
58         else {
59                 delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
60
61                 ts->monotonic = now(CLOCK_MONOTONIC);
62
63                 if ((int64_t) ts->monotonic > delta)
64                         ts->monotonic -= delta;
65                 else
66                         ts->monotonic = 0;
67         }
68
69         return ts;
70 }
71
72 dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
73         int64_t delta;
74         assert(ts);
75
76         if (u == (usec_t) -1) {
77                 ts->realtime = ts->monotonic = (usec_t) -1;
78                 return ts;
79         }
80
81         ts->monotonic = u;
82         delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u;
83
84         ts->realtime = now(CLOCK_REALTIME);
85         if ((int64_t) ts->realtime > delta)
86                 ts->realtime -= delta;
87         else
88                 ts->realtime = 0;
89
90         return ts;
91 }
92
93 usec_t timespec_load(const struct timespec *ts) {
94         assert(ts);
95
96         if (ts->tv_sec == (time_t) -1 &&
97             ts->tv_nsec == (long) -1)
98                 return (usec_t) -1;
99
100         if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
101                 return (usec_t) -1;
102
103         return
104                 (usec_t) ts->tv_sec * USEC_PER_SEC +
105                 (usec_t) ts->tv_nsec / NSEC_PER_USEC;
106 }
107
108 struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
109         assert(ts);
110
111         if (u == (usec_t) -1) {
112                 ts->tv_sec = (time_t) -1;
113                 ts->tv_nsec = (long) -1;
114                 return ts;
115         }
116
117         ts->tv_sec = (time_t) (u / USEC_PER_SEC);
118         ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
119
120         return ts;
121 }
122
123 usec_t timeval_load(const struct timeval *tv) {
124         assert(tv);
125
126         if (tv->tv_sec == (time_t) -1 &&
127             tv->tv_usec == (suseconds_t) -1)
128                 return (usec_t) -1;
129
130         if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC)
131                 return (usec_t) -1;
132
133         return
134                 (usec_t) tv->tv_sec * USEC_PER_SEC +
135                 (usec_t) tv->tv_usec;
136 }
137
138 struct timeval *timeval_store(struct timeval *tv, usec_t u) {
139         assert(tv);
140
141         if (u == (usec_t) -1) {
142                 tv->tv_sec = (time_t) -1;
143                 tv->tv_usec = (suseconds_t) -1;
144                 return tv;
145         }
146
147         tv->tv_sec = (time_t) (u / USEC_PER_SEC);
148         tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
149
150         return tv;
151 }
152
153 char *format_timestamp(char *buf, size_t l, usec_t t) {
154         struct tm tm;
155         time_t sec;
156
157         assert(buf);
158         assert(l > 0);
159
160         if (t <= 0)
161                 return NULL;
162
163         sec = (time_t) (t / USEC_PER_SEC);
164
165         if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) <= 0)
166                 return NULL;
167
168         return buf;
169 }
170
171 char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
172         usec_t n, d;
173
174         n = now(CLOCK_REALTIME);
175
176         if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t)
177                 return NULL;
178
179         d = n - t;
180
181         if (d >= USEC_PER_YEAR)
182                 snprintf(buf, l, "%llu years %llu months ago",
183                          (unsigned long long) (d / USEC_PER_YEAR),
184                          (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH));
185         else if (d >= USEC_PER_MONTH)
186                 snprintf(buf, l, "%llu months %llu days ago",
187                          (unsigned long long) (d / USEC_PER_MONTH),
188                          (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY));
189         else if (d >= USEC_PER_WEEK)
190                 snprintf(buf, l, "%llu weeks %llu days ago",
191                          (unsigned long long) (d / USEC_PER_WEEK),
192                          (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY));
193         else if (d >= 2*USEC_PER_DAY)
194                 snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY));
195         else if (d >= 25*USEC_PER_HOUR)
196                 snprintf(buf, l, "1 day %lluh ago",
197                          (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR));
198         else if (d >= 6*USEC_PER_HOUR)
199                 snprintf(buf, l, "%lluh ago",
200                          (unsigned long long) (d / USEC_PER_HOUR));
201         else if (d >= USEC_PER_HOUR)
202                 snprintf(buf, l, "%lluh %llumin ago",
203                          (unsigned long long) (d / USEC_PER_HOUR),
204                          (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE));
205         else if (d >= 5*USEC_PER_MINUTE)
206                 snprintf(buf, l, "%llumin ago",
207                          (unsigned long long) (d / USEC_PER_MINUTE));
208         else if (d >= USEC_PER_MINUTE)
209                 snprintf(buf, l, "%llumin %llus ago",
210                          (unsigned long long) (d / USEC_PER_MINUTE),
211                          (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC));
212         else if (d >= USEC_PER_SEC)
213                 snprintf(buf, l, "%llus ago",
214                          (unsigned long long) (d / USEC_PER_SEC));
215         else if (d >= USEC_PER_MSEC)
216                 snprintf(buf, l, "%llums ago",
217                          (unsigned long long) (d / USEC_PER_MSEC));
218         else if (d > 0)
219                 snprintf(buf, l, "%lluus ago",
220                          (unsigned long long) d);
221         else
222                 snprintf(buf, l, "now");
223
224         buf[l-1] = 0;
225         return buf;
226 }
227
228 char *format_timespan(char *buf, size_t l, usec_t t) {
229         static const struct {
230                 const char *suffix;
231                 usec_t usec;
232         } table[] = {
233                 { "y", USEC_PER_YEAR },
234                 { "month", USEC_PER_MONTH },
235                 { "w", USEC_PER_WEEK },
236                 { "d", USEC_PER_DAY },
237                 { "h", USEC_PER_HOUR },
238                 { "min", USEC_PER_MINUTE },
239                 { "s", USEC_PER_SEC },
240                 { "ms", USEC_PER_MSEC },
241                 { "us", 1 },
242         };
243
244         unsigned i;
245         char *p = buf;
246
247         assert(buf);
248         assert(l > 0);
249
250         if (t == (usec_t) -1)
251                 return NULL;
252
253         if (t == 0) {
254                 snprintf(p, l, "0");
255                 p[l-1] = 0;
256                 return p;
257         }
258
259         /* The result of this function can be parsed with parse_sec */
260
261         for (i = 0; i < ELEMENTSOF(table); i++) {
262                 int k;
263                 size_t n;
264
265                 if (t < table[i].usec)
266                         continue;
267
268                 if (l <= 1)
269                         break;
270
271                 k = snprintf(p, l, "%s%llu%s", p > buf ? " " : "", (unsigned long long) (t / table[i].usec), table[i].suffix);
272                 n = MIN((size_t) k, l);
273
274                 l -= n;
275                 p += n;
276
277                 t %= table[i].usec;
278         }
279
280         *p = 0;
281
282         return buf;
283 }
284
285 void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
286
287         assert(f);
288         assert(name);
289         assert(t);
290
291         if (!dual_timestamp_is_set(t))
292                 return;
293
294         fprintf(f, "%s=%llu %llu\n",
295                 name,
296                 (unsigned long long) t->realtime,
297                 (unsigned long long) t->monotonic);
298 }
299
300 void dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
301         unsigned long long a, b;
302
303         assert(value);
304         assert(t);
305
306         if (sscanf(value, "%lli %llu", &a, &b) != 2)
307                 log_debug("Failed to parse finish timestamp value %s", value);
308         else {
309                 t->realtime = a;
310                 t->monotonic = b;
311         }
312 }
313
314 int parse_timestamp(const char *t, usec_t *usec) {
315         static const struct {
316                 const char *name;
317                 const int nr;
318         } day_nr[] = {
319                 { "Sunday",    0 },
320                 { "Sun",       0 },
321                 { "Monday",    1 },
322                 { "Mon",       1 },
323                 { "Tuesday",   2 },
324                 { "Tue",       2 },
325                 { "Wednesday", 3 },
326                 { "Wed",       3 },
327                 { "Thursday",  4 },
328                 { "Thu",       4 },
329                 { "Friday",    5 },
330                 { "Fri",       5 },
331                 { "Saturday",  6 },
332                 { "Sat",       6 },
333         };
334
335         const char *k;
336         struct tm tm, copy;
337         time_t x;
338         usec_t plus = 0, minus = 0, ret;
339         int r, weekday = -1;
340         unsigned i;
341
342         /*
343          * Allowed syntaxes:
344          *
345          *   2012-09-22 16:34:22
346          *   2012-09-22 16:34     (seconds will be set to 0)
347          *   2012-09-22           (time will be set to 00:00:00)
348          *   16:34:22             (date will be set to today)
349          *   16:34                (date will be set to today, seconds to 0)
350          *   now
351          *   yesterday            (time is set to 00:00:00)
352          *   today                (time is set to 00:00:00)
353          *   tomorrow             (time is set to 00:00:00)
354          *   +5min
355          *   -5days
356          *
357          */
358
359         assert(t);
360         assert(usec);
361
362         x = time(NULL);
363         assert_se(localtime_r(&x, &tm));
364         tm.tm_isdst = -1;
365
366         if (streq(t, "now"))
367                 goto finish;
368
369         else if (streq(t, "today")) {
370                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
371                 goto finish;
372
373         } else if (streq(t, "yesterday")) {
374                 tm.tm_mday --;
375                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
376                 goto finish;
377
378         } else if (streq(t, "tomorrow")) {
379                 tm.tm_mday ++;
380                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
381                 goto finish;
382
383         } else if (t[0] == '+') {
384
385                 r = parse_sec(t+1, &plus);
386                 if (r < 0)
387                         return r;
388
389                 goto finish;
390         } else if (t[0] == '-') {
391
392                 r = parse_sec(t+1, &minus);
393                 if (r < 0)
394                         return r;
395
396                 goto finish;
397
398         } else if (endswith(t, " ago")) {
399                 _cleanup_free_ char *z;
400
401                 z = strndup(t, strlen(t) - 4);
402                 if (!z)
403                         return -ENOMEM;
404
405                 r = parse_sec(z, &minus);
406                 if (r < 0)
407                         return r;
408
409                 goto finish;
410         }
411
412         for (i = 0; i < ELEMENTSOF(day_nr); i++) {
413                 size_t skip;
414
415                 if (!startswith_no_case(t, day_nr[i].name))
416                         continue;
417
418                 skip = strlen(day_nr[i].name);
419                 if (t[skip] != ' ')
420                         continue;
421
422                 weekday = day_nr[i].nr;
423                 t += skip + 1;
424                 break;
425         }
426
427         copy = tm;
428         k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
429         if (k && *k == 0)
430                 goto finish;
431
432         tm = copy;
433         k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
434         if (k && *k == 0)
435                 goto finish;
436
437         tm = copy;
438         k = strptime(t, "%y-%m-%d %H:%M", &tm);
439         if (k && *k == 0) {
440                 tm.tm_sec = 0;
441                 goto finish;
442         }
443
444         tm = copy;
445         k = strptime(t, "%Y-%m-%d %H:%M", &tm);
446         if (k && *k == 0) {
447                 tm.tm_sec = 0;
448                 goto finish;
449         }
450
451         tm = copy;
452         k = strptime(t, "%y-%m-%d", &tm);
453         if (k && *k == 0) {
454                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
455                 goto finish;
456         }
457
458         tm = copy;
459         k = strptime(t, "%Y-%m-%d", &tm);
460         if (k && *k == 0) {
461                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
462                 goto finish;
463         }
464
465         tm = copy;
466         k = strptime(t, "%H:%M:%S", &tm);
467         if (k && *k == 0)
468                 goto finish;
469
470         tm = copy;
471         k = strptime(t, "%H:%M", &tm);
472         if (k && *k == 0) {
473                 tm.tm_sec = 0;
474                 goto finish;
475         }
476
477         return -EINVAL;
478
479 finish:
480         x = mktime(&tm);
481         if (x == (time_t) -1)
482                 return -EINVAL;
483
484         if (weekday >= 0 && tm.tm_wday != weekday)
485                 return -EINVAL;
486
487         ret = (usec_t) x * USEC_PER_SEC;
488
489         ret += plus;
490         if (ret > minus)
491                 ret -= minus;
492         else
493                 ret = 0;
494
495         *usec = ret;
496
497         return 0;
498 }
499
500 int parse_sec(const char *t, usec_t *usec) {
501         static const struct {
502                 const char *suffix;
503                 usec_t usec;
504         } table[] = {
505                 { "seconds", USEC_PER_SEC },
506                 { "second", USEC_PER_SEC },
507                 { "sec", USEC_PER_SEC },
508                 { "s", USEC_PER_SEC },
509                 { "minutes", USEC_PER_MINUTE },
510                 { "minute", USEC_PER_MINUTE },
511                 { "min", USEC_PER_MINUTE },
512                 { "months", USEC_PER_MONTH },
513                 { "month", USEC_PER_MONTH },
514                 { "msec", USEC_PER_MSEC },
515                 { "ms", USEC_PER_MSEC },
516                 { "m", USEC_PER_MINUTE },
517                 { "hours", USEC_PER_HOUR },
518                 { "hour", USEC_PER_HOUR },
519                 { "hr", USEC_PER_HOUR },
520                 { "h", USEC_PER_HOUR },
521                 { "days", USEC_PER_DAY },
522                 { "day", USEC_PER_DAY },
523                 { "d", USEC_PER_DAY },
524                 { "weeks", USEC_PER_WEEK },
525                 { "week", USEC_PER_WEEK },
526                 { "w", USEC_PER_WEEK },
527                 { "years", USEC_PER_YEAR },
528                 { "year", USEC_PER_YEAR },
529                 { "y", USEC_PER_YEAR },
530                 { "usec", 1ULL },
531                 { "us", 1ULL },
532                 { "", USEC_PER_SEC }, /* default is sec */
533         };
534
535         const char *p;
536         usec_t r = 0;
537         bool something = false;
538
539         assert(t);
540         assert(usec);
541
542         p = t;
543         for (;;) {
544                 long long l, z = 0;
545                 char *e;
546                 unsigned i, n = 0;
547
548                 p += strspn(p, WHITESPACE);
549
550                 if (*p == 0) {
551                         if (!something)
552                                 return -EINVAL;
553
554                         break;
555                 }
556
557                 errno = 0;
558                 l = strtoll(p, &e, 10);
559
560                 if (errno > 0)
561                         return -errno;
562
563                 if (l < 0)
564                         return -ERANGE;
565
566                 if (*e == '.') {
567                         char *b = e + 1;
568
569                         errno = 0;
570                         z = strtoll(b, &e, 10);
571                         if (errno > 0)
572                                 return -errno;
573
574                         if (z < 0)
575                                 return -ERANGE;
576
577                         if (e == b)
578                                 return -EINVAL;
579
580                         n = e - b;
581
582                 } else if (e == p)
583                         return -EINVAL;
584
585                 e += strspn(e, WHITESPACE);
586
587                 for (i = 0; i < ELEMENTSOF(table); i++)
588                         if (startswith(e, table[i].suffix)) {
589                                 usec_t k = (usec_t) z * table[i].usec;
590
591                                 for (; n > 0; n--)
592                                         k /= 10;
593
594                                 r += (usec_t) l * table[i].usec + k;
595                                 p = e + strlen(table[i].suffix);
596
597                                 something = true;
598                                 break;
599                         }
600
601                 if (i >= ELEMENTSOF(table))
602                         return -EINVAL;
603
604         }
605
606         *usec = r;
607
608         return 0;
609 }
610
611 int parse_nsec(const char *t, nsec_t *nsec) {
612         static const struct {
613                 const char *suffix;
614                 nsec_t nsec;
615         } table[] = {
616                 { "seconds", NSEC_PER_SEC },
617                 { "second", NSEC_PER_SEC },
618                 { "sec", NSEC_PER_SEC },
619                 { "s", NSEC_PER_SEC },
620                 { "minutes", NSEC_PER_MINUTE },
621                 { "minute", NSEC_PER_MINUTE },
622                 { "min", NSEC_PER_MINUTE },
623                 { "months", NSEC_PER_MONTH },
624                 { "month", NSEC_PER_MONTH },
625                 { "msec", NSEC_PER_MSEC },
626                 { "ms", NSEC_PER_MSEC },
627                 { "m", NSEC_PER_MINUTE },
628                 { "hours", NSEC_PER_HOUR },
629                 { "hour", NSEC_PER_HOUR },
630                 { "hr", NSEC_PER_HOUR },
631                 { "h", NSEC_PER_HOUR },
632                 { "days", NSEC_PER_DAY },
633                 { "day", NSEC_PER_DAY },
634                 { "d", NSEC_PER_DAY },
635                 { "weeks", NSEC_PER_WEEK },
636                 { "week", NSEC_PER_WEEK },
637                 { "w", NSEC_PER_WEEK },
638                 { "years", NSEC_PER_YEAR },
639                 { "year", NSEC_PER_YEAR },
640                 { "y", NSEC_PER_YEAR },
641                 { "usec", NSEC_PER_USEC },
642                 { "us", NSEC_PER_USEC },
643                 { "nsec", 1ULL },
644                 { "ns", 1ULL },
645                 { "", 1ULL }, /* default is nsec */
646         };
647
648         const char *p;
649         nsec_t r = 0;
650         bool something = false;
651
652         assert(t);
653         assert(nsec);
654
655         p = t;
656         for (;;) {
657                 long long l, z = 0;
658                 char *e;
659                 unsigned i, n = 0;
660
661                 p += strspn(p, WHITESPACE);
662
663                 if (*p == 0) {
664                         if (!something)
665                                 return -EINVAL;
666
667                         break;
668                 }
669
670                 errno = 0;
671                 l = strtoll(p, &e, 10);
672
673                 if (errno > 0)
674                         return -errno;
675
676                 if (l < 0)
677                         return -ERANGE;
678
679                 if (*e == '.') {
680                         char *b = e + 1;
681
682                         errno = 0;
683                         z = strtoll(b, &e, 10);
684                         if (errno > 0)
685                                 return -errno;
686
687                         if (z < 0)
688                                 return -ERANGE;
689
690                         if (e == b)
691                                 return -EINVAL;
692
693                         n = e - b;
694
695                 } else if (e == p)
696                         return -EINVAL;
697
698                 e += strspn(e, WHITESPACE);
699
700                 for (i = 0; i < ELEMENTSOF(table); i++)
701                         if (startswith(e, table[i].suffix)) {
702                                 nsec_t k = (nsec_t) z * table[i].nsec;
703
704                                 for (; n > 0; n--)
705                                         k /= 10;
706
707                                 r += (nsec_t) l * table[i].nsec + k;
708                                 p = e + strlen(table[i].suffix);
709
710                                 something = true;
711                                 break;
712                         }
713
714                 if (i >= ELEMENTSOF(table))
715                         return -EINVAL;
716
717         }
718
719         *nsec = r;
720
721         return 0;
722 }