chiark / gitweb /
Always use errno > 0 to help gcc
[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_usec */
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_usec(t+1, &plus);
386                 if (r < 0)
387                         return r;
388
389                 goto finish;
390         } else if (t[0] == '-') {
391
392                 r = parse_usec(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_usec(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_usec(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
538         assert(t);
539         assert(usec);
540
541         p = t;
542         do {
543                 long long l;
544                 char *e;
545                 unsigned i;
546
547                 errno = 0;
548                 l = strtoll(p, &e, 10);
549
550                 if (errno > 0)
551                         return -errno;
552
553                 if (l < 0)
554                         return -ERANGE;
555
556                 if (e == p)
557                         return -EINVAL;
558
559                 e += strspn(e, WHITESPACE);
560
561                 for (i = 0; i < ELEMENTSOF(table); i++)
562                         if (startswith(e, table[i].suffix)) {
563                                 r += (usec_t) l * table[i].usec;
564                                 p = e + strlen(table[i].suffix);
565                                 break;
566                         }
567
568                 if (i >= ELEMENTSOF(table))
569                         return -EINVAL;
570
571         } while (*p != 0);
572
573         *usec = r;
574
575         return 0;
576 }
577
578 int parse_nsec(const char *t, nsec_t *nsec) {
579         static const struct {
580                 const char *suffix;
581                 nsec_t nsec;
582         } table[] = {
583                 { "seconds", NSEC_PER_SEC },
584                 { "second", NSEC_PER_SEC },
585                 { "sec", NSEC_PER_SEC },
586                 { "s", NSEC_PER_SEC },
587                 { "minutes", NSEC_PER_MINUTE },
588                 { "minute", NSEC_PER_MINUTE },
589                 { "min", NSEC_PER_MINUTE },
590                 { "months", NSEC_PER_MONTH },
591                 { "month", NSEC_PER_MONTH },
592                 { "msec", NSEC_PER_MSEC },
593                 { "ms", NSEC_PER_MSEC },
594                 { "m", NSEC_PER_MINUTE },
595                 { "hours", NSEC_PER_HOUR },
596                 { "hour", NSEC_PER_HOUR },
597                 { "hr", NSEC_PER_HOUR },
598                 { "h", NSEC_PER_HOUR },
599                 { "days", NSEC_PER_DAY },
600                 { "day", NSEC_PER_DAY },
601                 { "d", NSEC_PER_DAY },
602                 { "weeks", NSEC_PER_WEEK },
603                 { "week", NSEC_PER_WEEK },
604                 { "w", NSEC_PER_WEEK },
605                 { "years", NSEC_PER_YEAR },
606                 { "year", NSEC_PER_YEAR },
607                 { "y", NSEC_PER_YEAR },
608                 { "usec", NSEC_PER_USEC },
609                 { "us", NSEC_PER_USEC },
610                 { "nsec", 1ULL },
611                 { "ns", 1ULL },
612                 { "", 1ULL }, /* default is nsec */
613         };
614
615         const char *p;
616         nsec_t r = 0;
617
618         assert(t);
619         assert(nsec);
620
621         p = t;
622         do {
623                 long long l;
624                 char *e;
625                 unsigned i;
626
627                 errno = 0;
628                 l = strtoll(p, &e, 10);
629
630                 if (errno > 0)
631                         return -errno;
632
633                 if (l < 0)
634                         return -ERANGE;
635
636                 if (e == p)
637                         return -EINVAL;
638
639                 e += strspn(e, WHITESPACE);
640
641                 for (i = 0; i < ELEMENTSOF(table); i++)
642                         if (startswith(e, table[i].suffix)) {
643                                 r += (nsec_t) l * table[i].nsec;
644                                 p = e + strlen(table[i].suffix);
645                                 break;
646                         }
647
648                 if (i >= ELEMENTSOF(table))
649                         return -EINVAL;
650
651         } while (*p != 0);
652
653         *nsec = r;
654
655         return 0;
656 }