chiark / gitweb /
util-lib: make timestamp generation and parsing reversible (#3869)
[elogind.git] / src / basic / time-util.c
index 52a0382c74b385ffadf3644add040e8bea2592d7..cf32c04086364f59bfcf4f43e7b66fc8adce3245 100644 (file)
@@ -1,5 +1,3 @@
-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
 /***
   This file is part of systemd.
 
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
-#include <time.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
 #include <string.h>
-#include <sys/timex.h>
+#include <sys/stat.h>
+#include <sys/time.h>
 #include <sys/timerfd.h>
-
-#include "util.h"
-#include "time-util.h"
+#include <sys/timex.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "log.h"
+#include "macro.h"
+#include "parse-util.h"
 #include "path-util.h"
+#include "string-util.h"
 #include "strv.h"
+#include "time-util.h"
+
+#if 0 /// UNNEEDED by elogind
+static nsec_t timespec_load_nsec(const struct timespec *ts);
+#endif // 0
+
+static clockid_t map_clock_id(clockid_t c) {
+
+        /* Some more exotic archs (s390, ppc, …) lack the "ALARM" flavour of the clocks. Thus, clock_gettime() will
+         * fail for them. Since they are essentially the same as their non-ALARM pendants (their only difference is
+         * when timers are set on them), let's just map them accordingly. This way, we can get the correct time even on
+         * those archs. */
+
+        switch (c) {
+
+        case CLOCK_BOOTTIME_ALARM:
+                return CLOCK_BOOTTIME;
+
+        case CLOCK_REALTIME_ALARM:
+                return CLOCK_REALTIME;
+
+        default:
+                return c;
+        }
+}
 
 usec_t now(clockid_t clock_id) {
         struct timespec ts;
 
-        assert_se(clock_gettime(clock_id, &ts) == 0);
+        assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
 
         return timespec_load(&ts);
 }
 
-/// UNNEEDED by elogind
-#if 0
+#if 0 /// UNNEEDED by elogind
 nsec_t now_nsec(clockid_t clock_id) {
         struct timespec ts;
 
-        assert_se(clock_gettime(clock_id, &ts) == 0);
+        assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
 
         return timespec_load_nsec(&ts);
 }
@@ -57,6 +91,16 @@ dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
         return ts;
 }
 
+triple_timestamp* triple_timestamp_get(triple_timestamp *ts) {
+        assert(ts);
+
+        ts->realtime = now(CLOCK_REALTIME);
+        ts->monotonic = now(CLOCK_MONOTONIC);
+        ts->boottime = clock_boottime_supported() ? now(CLOCK_BOOTTIME) : USEC_INFINITY;
+
+        return ts;
+}
+
 dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
         int64_t delta;
         assert(ts);
@@ -69,18 +113,30 @@ dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
         ts->realtime = u;
 
         delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
-        ts->monotonic = now(CLOCK_MONOTONIC);
+        ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta);
 
-        if ((int64_t) ts->monotonic > delta)
-                ts->monotonic -= delta;
-        else
-                ts->monotonic = 0;
+        return ts;
+}
+
+#if 0 /// UNNEEDED by elogind
+triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) {
+        int64_t delta;
+
+        assert(ts);
+
+        if (u == USEC_INFINITY || u <= 0) {
+                ts->realtime = ts->monotonic = ts->boottime = u;
+                return ts;
+        }
+
+        ts->realtime = u;
+        delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
+        ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta);
+        ts->boottime = clock_boottime_supported() ? usec_sub(now(CLOCK_BOOTTIME), delta) : USEC_INFINITY;
 
         return ts;
 }
 
-/// UNNEEDED by elogind
-#if 0
 dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
         int64_t delta;
         assert(ts);
@@ -92,12 +148,7 @@ dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
 
         ts->monotonic = u;
         delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u;
-
-        ts->realtime = now(CLOCK_REALTIME);
-        if ((int64_t) ts->realtime > delta)
-                ts->realtime -= delta;
-        else
-                ts->realtime = 0;
+        ts->realtime = usec_sub(now(CLOCK_REALTIME), delta);
 
         return ts;
 }
@@ -109,30 +160,40 @@ dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, us
                 ts->realtime = ts->monotonic = USEC_INFINITY;
                 return ts;
         }
-        ts->realtime = now(CLOCK_REALTIME);
-        ts->monotonic = now(CLOCK_MONOTONIC);
 
+        dual_timestamp_get(ts);
         delta = (int64_t) now(clock_boottime_or_monotonic()) - (int64_t) u;
-
-        if ((int64_t) ts->realtime > delta)
-                ts->realtime -= delta;
-        else
-                ts->realtime = 0;
-
-        if ((int64_t) ts->monotonic > delta)
-                ts->monotonic -= delta;
-        else
-                ts->monotonic = 0;
+        ts->realtime = usec_sub(ts->realtime, delta);
+        ts->monotonic = usec_sub(ts->monotonic, delta);
 
         return ts;
 }
 #endif // 0
 
+usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
+
+        switch (clock) {
+
+        case CLOCK_REALTIME:
+        case CLOCK_REALTIME_ALARM:
+                return ts->realtime;
+
+        case CLOCK_MONOTONIC:
+                return ts->monotonic;
+
+        case CLOCK_BOOTTIME:
+        case CLOCK_BOOTTIME_ALARM:
+                return ts->boottime;
+
+        default:
+                return USEC_INFINITY;
+        }
+}
+
 usec_t timespec_load(const struct timespec *ts) {
         assert(ts);
 
-        if (ts->tv_sec == (time_t) -1 &&
-            ts->tv_nsec == (long) -1)
+        if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1)
                 return USEC_INFINITY;
 
         if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
@@ -143,17 +204,19 @@ usec_t timespec_load(const struct timespec *ts) {
                 (usec_t) ts->tv_nsec / NSEC_PER_USEC;
 }
 
-nsec_t timespec_load_nsec(const struct timespec *ts) {
+#if 0 /// UNNEEDED by elogind
+static nsec_t timespec_load_nsec(const struct timespec *ts) {
         assert(ts);
 
-        if (ts->tv_sec == (time_t) -1 &&
-            ts->tv_nsec == (long) -1)
+        if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1)
                 return NSEC_INFINITY;
 
-        return
-                (nsec_t) ts->tv_sec * NSEC_PER_SEC +
-                (nsec_t) ts->tv_nsec;
+        if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC)
+                return NSEC_INFINITY;
+
+        return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec;
 }
+#endif // 0
 
 struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
         assert(ts);
@@ -199,72 +262,117 @@ struct timeval *timeval_store(struct timeval *tv, usec_t u) {
         return tv;
 }
 
-static char *format_timestamp_internal(char *buf, size_t l, usec_t t, bool utc) {
+static char *format_timestamp_internal(
+                char *buf,
+                size_t l,
+                usec_t t,
+                bool utc,
+                bool us) {
+
+        /* The weekdays in non-localized (English) form. We use this instead of the localized form, so that our
+         * generated timestamps may be parsed with parse_timestamp(), and always read the same. */
+        static const char * const weekdays[] = {
+                [0] = "Sun",
+                [1] = "Mon",
+                [2] = "Tue",
+                [3] = "Wed",
+                [4] = "Thu",
+                [5] = "Fri",
+                [6] = "Sat",
+        };
+
         struct tm tm;
         time_t sec;
+        size_t n;
 
         assert(buf);
-        assert(l > 0);
 
+        if (l <
+            3 +                  /* week day */
+            1 + 10 +             /* space and date */
+            1 + 8 +              /* space and time */
+            (us ? 1 + 6 : 0) +   /* "." and microsecond part */
+            1 + 1 +              /* space and shortest possible zone */
+            1)
+                return NULL; /* Not enough space even for the shortest form. */
         if (t <= 0 || t == USEC_INFINITY)
-                return NULL;
+                return NULL; /* Timestamp is unset */
 
-        sec = (time_t) (t / USEC_PER_SEC);
+        sec = (time_t) (t / USEC_PER_SEC); /* Round down */
+        if ((usec_t) sec != (t / USEC_PER_SEC))
+                return NULL; /* overflow? */
 
-        if (utc)
-                gmtime_r(&sec, &tm);
-        else
-                localtime_r(&sec, &tm);
-        if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", &tm) <= 0)
+        if (!localtime_or_gmtime_r(&sec, &tm, utc))
                 return NULL;
 
+        /* Start with the week day */
+        assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
+        memcpy(buf, weekdays[tm.tm_wday], 4);
+
+        /* Add the main components */
+        if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0)
+                return NULL; /* Doesn't fit */
+
+        /* Append the microseconds part, if that's requested */
+        if (us) {
+                n = strlen(buf);
+                if (n + 8 > l)
+                        return NULL; /* Microseconds part doesn't fit. */
+
+                sprintf(buf + n, ".%06llu", (unsigned long long) (t % USEC_PER_SEC));
+        }
+
+        /* Append the timezone */
+        n = strlen(buf);
+        if (utc) {
+                /* If this is UTC then let's explicitly use the "UTC" string here, because gmtime_r() normally uses the
+                 * obsolete "GMT" instead. */
+                if (n + 5 > l)
+                        return NULL; /* "UTC" doesn't fit. */
+
+                strcpy(buf + n, " UTC");
+
+        } else if (!isempty(tm.tm_zone)) {
+                size_t tn;
+
+                /* An explicit timezone is specified, let's use it, if it fits */
+                tn = strlen(tm.tm_zone);
+                if (n + 1 + tn + 1 > l) {
+                        /* The full time zone does not fit in. Yuck. */
+
+                        if (n + 1 + _POSIX_TZNAME_MAX + 1 > l)
+                                return NULL; /* Not even enough space for the POSIX minimum (of 6)? In that case, complain that it doesn't fit */
+
+                        /* So the time zone doesn't fit in fully, but the caller passed enough space for the POSIX
+                         * minimum time zone length. In this case suppress the timezone entirely, in order not to dump
+                         * an overly long, hard to read string on the user. This should be safe, because the user will
+                         * assume the local timezone anyway if none is shown. And so does parse_timestamp(). */
+                } else {
+                        buf[n++] = ' ';
+                        strcpy(buf + n, tm.tm_zone);
+                }
+        }
+
         return buf;
 }
 
 char *format_timestamp(char *buf, size_t l, usec_t t) {
-        return format_timestamp_internal(buf, l, t, false);
+        return format_timestamp_internal(buf, l, t, false, false);
 }
 
-/// UNNEEDED by elogind
-#if 0
+#if 0 /// UNNEEDED by elogind
 char *format_timestamp_utc(char *buf, size_t l, usec_t t) {
-        return format_timestamp_internal(buf, l, t, true);
+        return format_timestamp_internal(buf, l, t, true, false);
 }
 #endif // 0
 
-static char *format_timestamp_internal_us(char *buf, size_t l, usec_t t, bool utc) {
-        struct tm tm;
-        time_t sec;
-
-        assert(buf);
-        assert(l > 0);
-
-        if (t <= 0 || t == USEC_INFINITY)
-                return NULL;
-
-        sec = (time_t) (t / USEC_PER_SEC);
-        if (utc)
-                gmtime_r(&sec, &tm);
-        else
-                localtime_r(&sec, &tm);
-
-        if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm) <= 0)
-                return NULL;
-        snprintf(buf + strlen(buf), l - strlen(buf), ".%06llu", (unsigned long long) (t % USEC_PER_SEC));
-        if (strftime(buf + strlen(buf), l - strlen(buf), " %Z", &tm) <= 0)
-                return NULL;
-
-        return buf;
-}
-
 char *format_timestamp_us(char *buf, size_t l, usec_t t) {
-        return format_timestamp_internal_us(buf, l, t, false);
+        return format_timestamp_internal(buf, l, t, false, true);
 }
 
-/// UNNEEDED by elogind
-#if 0
+#if 0 /// UNNEEDED by elogind
 char *format_timestamp_us_utc(char *buf, size_t l, usec_t t) {
-        return format_timestamp_internal_us(buf, l, t, true);
+        return format_timestamp_internal(buf, l, t, true, true);
 }
 #endif // 0
 
@@ -336,15 +444,15 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
                 const char *suffix;
                 usec_t usec;
         } table[] = {
-                { "y", USEC_PER_YEAR },
-                { "month", USEC_PER_MONTH },
-                { "w", USEC_PER_WEEK },
-                { "d", USEC_PER_DAY },
-                { "h", USEC_PER_HOUR },
-                { "min", USEC_PER_MINUTE },
-                { "s", USEC_PER_SEC },
-                { "ms", USEC_PER_MSEC },
-                { "us", 1 },
+                { "y",     USEC_PER_YEAR   },
+                { "month", USEC_PER_MONTH  },
+                { "w",     USEC_PER_WEEK   },
+                { "d",     USEC_PER_DAY    },
+                { "h",     USEC_PER_HOUR   },
+                { "min",   USEC_PER_MINUTE },
+                { "s",     USEC_PER_SEC    },
+                { "ms",    USEC_PER_MSEC   },
+                { "us",    1               },
         };
 
         unsigned i;
@@ -441,8 +549,7 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
         return buf;
 }
 
-/// UNNEEDED by elogind
-#if 0
+#if 0 /// UNNEEDED by elogind
 void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
 
         assert(f);
@@ -465,7 +572,7 @@ int dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
         assert(t);
 
         if (sscanf(value, "%llu %llu", &a, &b) != 2) {
-                log_debug("Failed to parse finish timestamp value %s.", value);
+                log_debug("Failed to parse dual timestamp value \"%s\": %m", value);
                 return -EINVAL;
         }
 
@@ -475,6 +582,20 @@ int dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
         return 0;
 }
 
+#endif // 0
+int timestamp_deserialize(const char *value, usec_t *timestamp) {
+        int r;
+
+        assert(value);
+
+        r = safe_atou64(value, timestamp);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse timestamp value \"%s\": %m", value);
+
+        return r;
+}
+
+#if 0 /// UNNEEDED by elogind
 int parse_timestamp(const char *t, usec_t *usec) {
         static const struct {
                 const char *name;
@@ -496,11 +617,11 @@ int parse_timestamp(const char *t, usec_t *usec) {
                 { "Sat",       6 },
         };
 
-        const char *k;
+        const char *k, *utc, *tzn = NULL;
         struct tm tm, copy;
         time_t x;
-        usec_t plus = 0, minus = 0, ret;
-        int r, weekday = -1;
+        usec_t x_usec, plus = 0, minus = 0, ret;
+        int r, weekday = -1, dst = -1;
         unsigned i;
 
         /*
@@ -524,28 +645,15 @@ int parse_timestamp(const char *t, usec_t *usec) {
         assert(t);
         assert(usec);
 
-        x = time(NULL);
-        assert_se(localtime_r(&x, &tm));
-        tm.tm_isdst = -1;
-
-        if (streq(t, "now"))
-                goto finish;
-
-        else if (streq(t, "today")) {
-                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
-                goto finish;
+        if (t[0] == '@')
+                return parse_sec(t + 1, usec);
 
-        } else if (streq(t, "yesterday")) {
-                tm.tm_mday --;
-                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
-                goto finish;
+        ret = now(CLOCK_REALTIME);
 
-        } else if (streq(t, "tomorrow")) {
-                tm.tm_mday ++;
-                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+        if (streq(t, "now"))
                 goto finish;
 
-        else if (t[0] == '+') {
+        else if (t[0] == '+') {
                 r = parse_sec(t+1, &plus);
                 if (r < 0)
                         return r;
@@ -559,35 +667,90 @@ int parse_timestamp(const char *t, usec_t *usec) {
 
                 goto finish;
 
-        } else if (t[0] == '@')
-                return parse_sec(t + 1, usec);
-
-        else if (endswith(t, " ago")) {
-                _cleanup_free_ char *z;
+        } else if ((k = endswith(t, " ago"))) {
+                t = strndupa(t, k - t);
 
-                z = strndup(t, strlen(t) - 4);
-                if (!z)
-                        return -ENOMEM;
-
-                r = parse_sec(z, &minus);
+                r = parse_sec(t, &minus);
                 if (r < 0)
                         return r;
 
                 goto finish;
-        } else if (endswith(t, " left")) {
-                _cleanup_free_ char *z;
 
-                z = strndup(t, strlen(t) - 4);
-                if (!z)
-                        return -ENOMEM;
+        } else if ((k = endswith(t, " left"))) {
+                t = strndupa(t, k - t);
 
-                r = parse_sec(z, &plus);
+                r = parse_sec(t, &plus);
                 if (r < 0)
                         return r;
 
                 goto finish;
         }
 
+        /* See if the timestamp is suffixed with UTC */
+        utc = endswith_no_case(t, " UTC");
+        if (utc)
+                t = strndupa(t, utc - t);
+        else {
+                const char *e = NULL;
+                int j;
+
+                tzset();
+
+                /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only
+                 * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because
+                 * there are no nice APIs available to cover this. By accepting the local time zone strings, we make
+                 * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't
+                 * support arbitrary timezone specifications.  */
+
+                for (j = 0; j <= 1; j++) {
+
+                        if (isempty(tzname[j]))
+                                continue;
+
+                        e = endswith_no_case(t, tzname[j]);
+                        if (!e)
+                                continue;
+                        if (e == t)
+                                continue;
+                        if (e[-1] != ' ')
+                                continue;
+
+                        break;
+                }
+
+                if (IN_SET(j, 0, 1)) {
+                        /* Found one of the two timezones specified. */
+                        t = strndupa(t, e - t - 1);
+                        dst = j;
+                        tzn = tzname[j];
+                }
+        }
+
+        x = (time_t) (ret / USEC_PER_SEC);
+        x_usec = 0;
+
+        if (!localtime_or_gmtime_r(&x, &tm, utc))
+                return -EINVAL;
+
+        tm.tm_isdst = dst;
+        if (tzn)
+                tm.tm_zone = tzn;
+
+        if (streq(t, "today")) {
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto from_tm;
+
+        } else if (streq(t, "yesterday")) {
+                tm.tm_mday--;
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto from_tm;
+
+        } else if (streq(t, "tomorrow")) {
+                tm.tm_mday++;
+                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
+                goto from_tm;
+        }
+
         for (i = 0; i < ELEMENTSOF(day_nr); i++) {
                 size_t skip;
 
@@ -605,66 +768,94 @@ int parse_timestamp(const char *t, usec_t *usec) {
 
         copy = tm;
         k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
-        if (k && *k == 0)
-                goto finish;
+        if (k) {
+                if (*k == '.')
+                        goto parse_usec;
+                else if (*k == 0)
+                        goto from_tm;
+        }
 
         tm = copy;
         k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
-        if (k && *k == 0)
-                goto finish;
+        if (k) {
+                if (*k == '.')
+                        goto parse_usec;
+                else if (*k == 0)
+                        goto from_tm;
+        }
 
         tm = copy;
         k = strptime(t, "%y-%m-%d %H:%M", &tm);
         if (k && *k == 0) {
                 tm.tm_sec = 0;
-                goto finish;
+                goto from_tm;
         }
 
         tm = copy;
         k = strptime(t, "%Y-%m-%d %H:%M", &tm);
         if (k && *k == 0) {
                 tm.tm_sec = 0;
-                goto finish;
+                goto from_tm;
         }
 
         tm = copy;
         k = strptime(t, "%y-%m-%d", &tm);
         if (k && *k == 0) {
                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
-                goto finish;
+                goto from_tm;
         }
 
         tm = copy;
         k = strptime(t, "%Y-%m-%d", &tm);
         if (k && *k == 0) {
                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
-                goto finish;
+                goto from_tm;
         }
 
         tm = copy;
         k = strptime(t, "%H:%M:%S", &tm);
-        if (k && *k == 0)
-                goto finish;
+        if (k) {
+                if (*k == '.')
+                        goto parse_usec;
+                else if (*k == 0)
+                        goto from_tm;
+        }
 
         tm = copy;
         k = strptime(t, "%H:%M", &tm);
         if (k && *k == 0) {
                 tm.tm_sec = 0;
-                goto finish;
+                goto from_tm;
         }
 
         return -EINVAL;
 
-finish:
-        x = mktime(&tm);
+parse_usec:
+        {
+                unsigned add;
+
+                k++;
+                r = parse_fractional_part_u(&k, 6, &add);
+                if (r < 0)
+                        return -EINVAL;
+
+                if (*k)
+                        return -EINVAL;
+
+                x_usec = add;
+        }
+
+from_tm:
+        x = mktime_or_timegm(&tm, utc);
         if (x == (time_t) -1)
                 return -EINVAL;
 
         if (weekday >= 0 && tm.tm_wday != weekday)
                 return -EINVAL;
 
-        ret = (usec_t) x * USEC_PER_SEC;
+        ret = (usec_t) x * USEC_PER_SEC + x_usec;
 
+finish:
         ret += plus;
         if (ret > minus)
                 ret -= minus;
@@ -677,47 +868,63 @@ finish:
 }
 #endif // 0
 
-int parse_sec(const char *t, usec_t *usec) {
+static char* extract_multiplier(char *p, usec_t *multiplier) {
         static const struct {
                 const char *suffix;
                 usec_t usec;
         } table[] = {
-                { "seconds", USEC_PER_SEC },
-                { "second", USEC_PER_SEC },
-                { "sec", USEC_PER_SEC },
-                { "s", USEC_PER_SEC },
+                { "seconds", USEC_PER_SEC    },
+                { "second",  USEC_PER_SEC    },
+                { "sec",     USEC_PER_SEC    },
+                { "s",       USEC_PER_SEC    },
                 { "minutes", USEC_PER_MINUTE },
-                { "minute", USEC_PER_MINUTE },
-                { "min", USEC_PER_MINUTE },
-                { "months", USEC_PER_MONTH },
-                { "month", USEC_PER_MONTH },
-                { "msec", USEC_PER_MSEC },
-                { "ms", USEC_PER_MSEC },
-                { "m", USEC_PER_MINUTE },
-                { "hours", USEC_PER_HOUR },
-                { "hour", USEC_PER_HOUR },
-                { "hr", USEC_PER_HOUR },
-                { "h", USEC_PER_HOUR },
-                { "days", USEC_PER_DAY },
-                { "day", USEC_PER_DAY },
-                { "d", USEC_PER_DAY },
-                { "weeks", USEC_PER_WEEK },
-                { "week", USEC_PER_WEEK },
-                { "w", USEC_PER_WEEK },
-                { "years", USEC_PER_YEAR },
-                { "year", USEC_PER_YEAR },
-                { "y", USEC_PER_YEAR },
-                { "usec", 1ULL },
-                { "us", 1ULL },
-                { "", USEC_PER_SEC }, /* default is sec */
+                { "minute",  USEC_PER_MINUTE },
+                { "min",     USEC_PER_MINUTE },
+                { "months",  USEC_PER_MONTH  },
+                { "month",   USEC_PER_MONTH  },
+                { "M",       USEC_PER_MONTH  },
+                { "msec",    USEC_PER_MSEC   },
+                { "ms",      USEC_PER_MSEC   },
+                { "m",       USEC_PER_MINUTE },
+                { "hours",   USEC_PER_HOUR   },
+                { "hour",    USEC_PER_HOUR   },
+                { "hr",      USEC_PER_HOUR   },
+                { "h",       USEC_PER_HOUR   },
+                { "days",    USEC_PER_DAY    },
+                { "day",     USEC_PER_DAY    },
+                { "d",       USEC_PER_DAY    },
+                { "weeks",   USEC_PER_WEEK   },
+                { "week",    USEC_PER_WEEK   },
+                { "w",       USEC_PER_WEEK   },
+                { "years",   USEC_PER_YEAR   },
+                { "year",    USEC_PER_YEAR   },
+                { "y",       USEC_PER_YEAR   },
+                { "usec",    1ULL            },
+                { "us",      1ULL            },
         };
+        unsigned i;
+
+        for (i = 0; i < ELEMENTSOF(table); i++) {
+                char *e;
 
+                e = startswith(p, table[i].suffix);
+                if (e) {
+                        *multiplier = table[i].usec;
+                        return e;
+                }
+        }
+
+        return p;
+}
+
+int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
         const char *p, *s;
         usec_t r = 0;
         bool something = false;
 
         assert(t);
         assert(usec);
+        assert(default_unit > 0);
 
         p = t;
 
@@ -735,7 +942,8 @@ int parse_sec(const char *t, usec_t *usec) {
         for (;;) {
                 long long l, z = 0;
                 char *e;
-                unsigned i, n = 0;
+                unsigned n = 0;
+                usec_t multiplier = default_unit, k;
 
                 p += strspn(p, WHITESPACE);
 
@@ -748,10 +956,8 @@ int parse_sec(const char *t, usec_t *usec) {
 
                 errno = 0;
                 l = strtoll(p, &e, 10);
-
                 if (errno > 0)
                         return -errno;
-
                 if (l < 0)
                         return -ERANGE;
 
@@ -775,24 +981,16 @@ int parse_sec(const char *t, usec_t *usec) {
                         return -EINVAL;
 
                 e += strspn(e, WHITESPACE);
+                p = extract_multiplier(e, &multiplier);
 
-                for (i = 0; i < ELEMENTSOF(table); i++)
-                        if (startswith(e, table[i].suffix)) {
-                                usec_t k = (usec_t) z * table[i].usec;
-
-                                for (; n > 0; n--)
-                                        k /= 10;
-
-                                r += (usec_t) l * table[i].usec + k;
-                                p = e + strlen(table[i].suffix);
+                something = true;
 
-                                something = true;
-                                break;
-                        }
+                k = (usec_t) z * multiplier;
 
-                if (i >= ELEMENTSOF(table))
-                        return -EINVAL;
+                for (; n > 0; n--)
+                        k /= 10;
 
+                r += (usec_t) l * multiplier + k;
         }
 
         *usec = r;
@@ -800,6 +998,11 @@ int parse_sec(const char *t, usec_t *usec) {
         return 0;
 }
 
+int parse_sec(const char *t, usec_t *usec) {
+        return parse_time(t, usec, USEC_PER_SEC);
+}
+
+#if 0 /// UNNEEDED by elogind
 int parse_nsec(const char *t, nsec_t *nsec) {
         static const struct {
                 const char *suffix;
@@ -925,8 +1128,6 @@ int parse_nsec(const char *t, nsec_t *nsec) {
         return 0;
 }
 
-/// UNNEEDED by elogind
-#if 0
 bool ntp_synced(void) {
         struct timex txc = {};
 
@@ -1002,7 +1203,6 @@ int get_timezones(char ***ret) {
 
         return 0;
 }
-#endif // 0
 
 bool timezone_is_valid(const char *name) {
         bool slash = false;
@@ -1045,24 +1245,61 @@ bool timezone_is_valid(const char *name) {
         return true;
 }
 
-clockid_t clock_boottime_or_monotonic(void) {
-        static clockid_t clock = -1;
-        int fd;
+#endif // 0
+bool clock_boottime_supported(void) {
+        static int supported = -1;
 
-        if (clock != -1)
-                return clock;
+        /* Note that this checks whether CLOCK_BOOTTIME is available in general as well as available for timerfds()! */
 
-        fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK|TFD_CLOEXEC);
-        if (fd < 0)
-                clock = CLOCK_MONOTONIC;
-        else {
-                safe_close(fd);
-                clock = CLOCK_BOOTTIME;
+        if (supported < 0) {
+                int fd;
+
+                fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK|TFD_CLOEXEC);
+                if (fd < 0)
+                        supported = false;
+                else {
+                        safe_close(fd);
+                        supported = true;
+                }
         }
 
-        return clock;
+        return supported;
+}
+
+#if 0 /// UNNEEDED by elogind
+clockid_t clock_boottime_or_monotonic(void) {
+        if (clock_boottime_supported())
+                return CLOCK_BOOTTIME;
+        else
+                return CLOCK_MONOTONIC;
 }
+#endif // 0
+
+bool clock_supported(clockid_t clock) {
+        struct timespec ts;
 
+        switch (clock) {
+
+        case CLOCK_MONOTONIC:
+        case CLOCK_REALTIME:
+                return true;
+
+        case CLOCK_BOOTTIME:
+                return clock_boottime_supported();
+
+        case CLOCK_BOOTTIME_ALARM:
+                if (!clock_boottime_supported())
+                        return false;
+
+                /* fall through, after checking the cached value for CLOCK_BOOTTIME. */
+
+        default:
+                /* For everything else, check properly */
+                return clock_gettime(clock, &ts) >= 0;
+        }
+}
+
+#if 0 /// UNNEEDED by elogind
 int get_timezone(char **tz) {
         _cleanup_free_ char *t = NULL;
         const char *e;
@@ -1089,3 +1326,28 @@ int get_timezone(char **tz) {
         *tz = z;
         return 0;
 }
+
+time_t mktime_or_timegm(struct tm *tm, bool utc) {
+        return utc ? timegm(tm) : mktime(tm);
+}
+#endif // 0
+
+struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) {
+        return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
+}
+
+#if 0 /// UNNEEDED by elogind
+unsigned long usec_to_jiffies(usec_t u) {
+        static thread_local unsigned long hz = 0;
+        long r;
+
+        if (hz == 0) {
+                r = sysconf(_SC_CLK_TCK);
+
+                assert(r > 0);
+                hz = (unsigned long) r;
+        }
+
+        return DIV_ROUND_UP(u , USEC_PER_SEC / hz);
+}
+#endif // 0