calendarspec: sub-second support, v3
authorHristo Venev <hristo@venev.name>
Mon, 16 Nov 2015 08:15:05 +0000 (10:15 +0200)
committerHristo Venev <hristo@venev.name>
Tue, 17 Nov 2015 21:52:09 +0000 (23:52 +0200)
src/basic/calendarspec.c
src/basic/calendarspec.h
src/basic/parse-util.c
src/basic/parse-util.h
src/basic/time-util.c

index 157ae1f..8f60561 100644 (file)
@@ -25,6 +25,7 @@
 #include "alloc-util.h"
 #include "calendarspec.h"
 #include "fileio.h"
+#include "parse-util.h"
 #include "string-util.h"
 
 #define BITS_WEEKDAYS   127
@@ -49,7 +50,7 @@ void calendar_spec_free(CalendarSpec *c) {
         free_chain(c->day);
         free_chain(c->hour);
         free_chain(c->minute);
-        free_chain(c->second);
+        free_chain(c->microsecond);
 
         free(c);
 }
@@ -135,7 +136,7 @@ int calendar_spec_normalize(CalendarSpec *c) {
         sort_chain(&c->day);
         sort_chain(&c->hour);
         sort_chain(&c->minute);
-        sort_chain(&c->second);
+        sort_chain(&c->microsecond);
 
         return 0;
 }
@@ -177,7 +178,7 @@ _pure_ bool calendar_spec_valid(CalendarSpec *c) {
         if (!chain_valid(c->minute, 0, 59))
                 return false;
 
-        if (!chain_valid(c->second, 0, 59))
+        if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1))
                 return false;
 
         return true;
@@ -232,7 +233,7 @@ static void format_weekdays(FILE *f, const CalendarSpec *c) {
         }
 }
 
-static void format_chain(FILE *f, int space, const CalendarComponent *c) {
+static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) {
         assert(f);
 
         if (!c) {
@@ -241,14 +242,25 @@ static void format_chain(FILE *f, int space, const CalendarComponent *c) {
         }
 
         assert(c->value >= 0);
-        fprintf(f, "%0*i", space, c->value);
-
-        if (c->repeat > 0)
-                fprintf(f, "/%i", c->repeat);
+        if (!usec)
+                fprintf(f, "%0*i", space, c->value);
+        else if (c->value % USEC_PER_SEC == 0)
+                fprintf(f, "%0*i", space, (int) (c->value / USEC_PER_SEC));
+        else
+                fprintf(f, "%0*i.%06i", space, (int) (c->value / USEC_PER_SEC), (int) (c->value % USEC_PER_SEC));
+
+        if (c->repeat > 0) {
+                if (!usec)
+                        fprintf(f, "/%i", c->repeat);
+                else if (c->repeat % USEC_PER_SEC == 0)
+                        fprintf(f, "/%i", (int) (c->repeat / USEC_PER_SEC));
+                else
+                        fprintf(f, "/%i.%06i", (int) (c->repeat / USEC_PER_SEC), (int) (c->repeat % USEC_PER_SEC));
+        }
 
         if (c->next) {
                 fputc(',', f);
-                format_chain(f, space, c->next);
+                format_chain(f, space, c->next, usec);
         }
 }
 
@@ -270,17 +282,17 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) {
                 fputc(' ', f);
         }
 
-        format_chain(f, 4, c->year);
+        format_chain(f, 4, c->year, false);
         fputc('-', f);
-        format_chain(f, 2, c->month);
+        format_chain(f, 2, c->month, false);
         fputc('-', f);
-        format_chain(f, 2, c->day);
+        format_chain(f, 2, c->day, false);
         fputc(' ', f);
-        format_chain(f, 2, c->hour);
+        format_chain(f, 2, c->hour, false);
         fputc(':', f);
-        format_chain(f, 2, c->minute);
+        format_chain(f, 2, c->minute, false);
         fputc(':', f);
-        format_chain(f, 2, c->second);
+        format_chain(f, 2, c->microsecond, true);
 
         if (c->utc)
                 fputs(" UTC", f);
@@ -391,35 +403,70 @@ static int parse_weekdays(const char **p, CalendarSpec *c) {
         }
 }
 
-static int prepend_component(const char **p, CalendarComponent **c) {
-        unsigned long value, repeat = 0;
-        char *e = NULL, *ee = NULL;
-        CalendarComponent *cc;
-
-        assert(p);
-        assert(c);
+static int parse_component_decimal(const char **p, bool usec, unsigned long *res) {
+        unsigned long value;
+        const char *e = NULL;
+        char *ee = NULL;
+        int r;
 
         errno = 0;
-        value = strtoul(*p, &e, 10);
+        value = strtoul(*p, &ee, 10);
         if (errno > 0)
                 return -errno;
-        if (e == *p)
+        if (ee == *p)
                 return -EINVAL;
         if ((unsigned long) (int) value != value)
                 return -ERANGE;
+        e = ee;
 
-        if (*e == '/') {
-                repeat = strtoul(e+1, &ee, 10);
-                if (errno > 0)
-                        return -errno;
-                if (ee == e+1)
-                        return -EINVAL;
-                if ((unsigned long) (int) repeat != repeat)
-                        return -ERANGE;
-                if (repeat <= 0)
+        if (usec) {
+                if (value * USEC_PER_SEC / USEC_PER_SEC != value)
                         return -ERANGE;
 
-                e = ee;
+                value *= USEC_PER_SEC;
+                if (*e == '.') {
+                        unsigned add;
+
+                        e++;
+                        r = parse_fractional_part_u(&e, 6, &add);
+                        if (r < 0)
+                                return r;
+
+                        if (add + value < value)
+                                return -ERANGE;
+                        value += add;
+                }
+        }
+
+        *p = e;
+        *res = value;
+
+        return 0;
+}
+
+static int prepend_component(const char **p, bool usec, CalendarComponent **c) {
+        unsigned long value, repeat = 0;
+        CalendarComponent *cc;
+        int r;
+        const char *e;
+
+        assert(p);
+        assert(c);
+
+        e = *p;
+
+        r = parse_component_decimal(&e, usec, &value);
+        if (r < 0)
+                return r;
+
+        if (*e == '/') {
+                e++;
+                r = parse_component_decimal(&e, usec, &repeat);
+                if (r < 0)
+                        return r;
+
+                if (repeat == 0)
+                        return -ERANGE;
         }
 
         if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
@@ -438,13 +485,31 @@ static int prepend_component(const char **p, CalendarComponent **c) {
 
         if (*e ==',') {
                 *p += 1;
-                return prepend_component(p, c);
+                return prepend_component(p, usec, c);
         }
 
         return 0;
 }
 
-static int parse_chain(const char **p, CalendarComponent **c) {
+static int const_chain(int value, CalendarComponent **c) {
+        CalendarComponent *cc = NULL;
+
+        assert(c);
+
+        cc = new0(CalendarComponent, 1);
+        if (!cc)
+                return -ENOMEM;
+
+        cc->value = value;
+        cc->repeat = 0;
+        cc->next = *c;
+
+        *c = cc;
+
+        return 0;
+}
+
+static int parse_chain(const char **p, bool usec, CalendarComponent **c) {
         const char *t;
         CalendarComponent *cc = NULL;
         int r;
@@ -455,12 +520,19 @@ static int parse_chain(const char **p, CalendarComponent **c) {
         t = *p;
 
         if (t[0] == '*') {
+                if (usec) {
+                        r = const_chain(0, c);
+                        if (r < 0)
+                                return r;
+                        (*c)->repeat = USEC_PER_SEC;
+                } else
+                        *c = NULL;
+
                 *p = t + 1;
-                *c = NULL;
                 return 0;
         }
 
-        r = prepend_component(&t, &cc);
+        r = prepend_component(&t, usec, &cc);
         if (r < 0) {
                 free_chain(cc);
                 return r;
@@ -471,24 +543,6 @@ static int parse_chain(const char **p, CalendarComponent **c) {
         return 0;
 }
 
-static int const_chain(int value, CalendarComponent **c) {
-        CalendarComponent *cc = NULL;
-
-        assert(c);
-
-        cc = new0(CalendarComponent, 1);
-        if (!cc)
-                return -ENOMEM;
-
-        cc->value = value;
-        cc->repeat = 0;
-        cc->next = *c;
-
-        *c = cc;
-
-        return 0;
-}
-
 static int parse_date(const char **p, CalendarSpec *c) {
         const char *t;
         int r;
@@ -503,7 +557,7 @@ static int parse_date(const char **p, CalendarSpec *c) {
         if (*t == 0)
                 return 0;
 
-        r = parse_chain(&t, &first);
+        r = parse_chain(&t, false, &first);
         if (r < 0)
                 return r;
 
@@ -519,7 +573,7 @@ static int parse_date(const char **p, CalendarSpec *c) {
         }
 
         t++;
-        r = parse_chain(&t, &second);
+        r = parse_chain(&t, false, &second);
         if (r < 0) {
                 free_chain(first);
                 return r;
@@ -540,7 +594,7 @@ static int parse_date(const char **p, CalendarSpec *c) {
         }
 
         t++;
-        r = parse_chain(&t, &third);
+        r = parse_chain(&t, false, &third);
         if (r < 0) {
                 free_chain(first);
                 free_chain(second);
@@ -582,7 +636,7 @@ static int parse_calendar_time(const char **p, CalendarSpec *c) {
                 goto finish;
         }
 
-        r = parse_chain(&t, &h);
+        r = parse_chain(&t, false, &h);
         if (r < 0)
                 goto fail;
 
@@ -592,7 +646,7 @@ static int parse_calendar_time(const char **p, CalendarSpec *c) {
         }
 
         t++;
-        r = parse_chain(&t, &m);
+        r = parse_chain(&t, false, &m);
         if (r < 0)
                 goto fail;
 
@@ -610,7 +664,7 @@ static int parse_calendar_time(const char **p, CalendarSpec *c) {
         }
 
         t++;
-        r = parse_chain(&t, &s);
+        r = parse_chain(&t, true, &s);
         if (r < 0)
                 goto fail;
 
@@ -639,7 +693,8 @@ finish:
         *p = t;
         c->hour = h;
         c->minute = m;
-        c->second = s;
+        c->microsecond = s;
+
         return 0;
 
 fail:
@@ -671,7 +726,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
         }
 
         if (strcaseeq(p, "minutely")) {
-                r = const_chain(0, &c->second);
+                r = const_chain(0, &c->microsecond);
                 if (r < 0)
                         goto fail;
 
@@ -679,7 +734,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                 r = const_chain(0, &c->minute);
                 if (r < 0)
                         goto fail;
-                r = const_chain(0, &c->second);
+                r = const_chain(0, &c->microsecond);
                 if (r < 0)
                         goto fail;
 
@@ -690,7 +745,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                 r = const_chain(0, &c->minute);
                 if (r < 0)
                         goto fail;
-                r = const_chain(0, &c->second);
+                r = const_chain(0, &c->microsecond);
                 if (r < 0)
                         goto fail;
 
@@ -704,7 +759,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                 r = const_chain(0, &c->minute);
                 if (r < 0)
                         goto fail;
-                r = const_chain(0, &c->second);
+                r = const_chain(0, &c->microsecond);
                 if (r < 0)
                         goto fail;
 
@@ -724,7 +779,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                 r = const_chain(0, &c->minute);
                 if (r < 0)
                         goto fail;
-                r = const_chain(0, &c->second);
+                r = const_chain(0, &c->microsecond);
                 if (r < 0)
                         goto fail;
 
@@ -738,7 +793,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                 r = const_chain(0, &c->minute);
                 if (r < 0)
                         goto fail;
-                r = const_chain(0, &c->second);
+                r = const_chain(0, &c->microsecond);
                 if (r < 0)
                         goto fail;
 
@@ -765,7 +820,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                 r = const_chain(0, &c->minute);
                 if (r < 0)
                         goto fail;
-                r = const_chain(0, &c->second);
+                r = const_chain(0, &c->microsecond);
                 if (r < 0)
                         goto fail;
 
@@ -789,7 +844,7 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
                 r = const_chain(0, &c->minute);
                 if (r < 0)
                         goto fail;
-                r = const_chain(0, &c->second);
+                r = const_chain(0, &c->microsecond);
                 if (r < 0)
                         goto fail;
 
@@ -906,14 +961,16 @@ static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
         return (weekdays_bits & (1 << k));
 }
 
-static int find_next(const CalendarSpec *spec, struct tm *tm) {
+static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
         struct tm c;
+        int tm_usec;
         int r;
 
         assert(spec);
         assert(tm);
 
         c = *tm;
+        tm_usec = *usec;
 
         for (;;) {
                 /* Normalize the current date */
@@ -927,7 +984,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm) {
                 if (r > 0) {
                         c.tm_mon = 0;
                         c.tm_mday = 1;
-                        c.tm_hour = c.tm_min = c.tm_sec = 0;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
                 }
                 if (r < 0 || tm_out_of_bounds(&c, spec->utc))
                         return r;
@@ -938,29 +995,29 @@ static int find_next(const CalendarSpec *spec, struct tm *tm) {
 
                 if (r > 0) {
                         c.tm_mday = 1;
-                        c.tm_hour = c.tm_min = c.tm_sec = 0;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
                 }
                 if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
                         c.tm_year ++;
                         c.tm_mon = 0;
                         c.tm_mday = 1;
-                        c.tm_hour = c.tm_min = c.tm_sec = 0;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
                         continue;
                 }
 
                 r = find_matching_component(spec->day, &c.tm_mday);
                 if (r > 0)
-                        c.tm_hour = c.tm_min = c.tm_sec = 0;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
                 if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
                         c.tm_mon ++;
                         c.tm_mday = 1;
-                        c.tm_hour = c.tm_min = c.tm_sec = 0;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
                         continue;
                 }
 
                 if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) {
                         c.tm_mday++;
-                        c.tm_hour = c.tm_min = c.tm_sec = 0;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
                         continue;
                 }
 
@@ -969,7 +1026,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm) {
                         c.tm_min = c.tm_sec = 0;
                 if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
                         c.tm_mday ++;
-                        c.tm_hour = c.tm_min = c.tm_sec = 0;
+                        c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
                         continue;
                 }
 
@@ -978,19 +1035,23 @@ static int find_next(const CalendarSpec *spec, struct tm *tm) {
                         c.tm_sec = 0;
                 if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
                         c.tm_hour ++;
-                        c.tm_min = c.tm_sec = 0;
+                        c.tm_min = c.tm_sec = tm_usec = 0;
                         continue;
                 }
 
-                r = find_matching_component(spec->second, &c.tm_sec);
+                c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec;
+                r = find_matching_component(spec->microsecond, &c.tm_sec);
+                tm_usec = c.tm_sec % USEC_PER_SEC;
+                c.tm_sec /= USEC_PER_SEC;
+
                 if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
                         c.tm_min ++;
-                        c.tm_sec = 0;
+                        c.tm_sec = tm_usec = 0;
                         continue;
                 }
 
-
                 *tm = c;
+                *usec = tm_usec;
                 return 0;
         }
 }
@@ -999,14 +1060,17 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next)
         struct tm tm;
         time_t t;
         int r;
+        usec_t tm_usec;
 
         assert(spec);
         assert(next);
 
-        t = (time_t) (usec / USEC_PER_SEC) + 1;
+        usec++;
+        t = (time_t) (usec / USEC_PER_SEC);
         assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc));
+        tm_usec = usec % USEC_PER_SEC;
 
-        r = find_next(spec, &tm);
+        r = find_next(spec, &tm, &tm_usec);
         if (r < 0)
                 return r;
 
@@ -1014,6 +1078,6 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next)
         if (t == (time_t) -1)
                 return -EINVAL;
 
-        *next = (usec_t) t * USEC_PER_SEC;
+        *next = (usec_t) t * USEC_PER_SEC + tm_usec;
         return 0;
 }
index 56dc02f..d703a1b 100644 (file)
@@ -44,7 +44,7 @@ typedef struct CalendarSpec {
 
         CalendarComponent *hour;
         CalendarComponent *minute;
-        CalendarComponent *second;
+        CalendarComponent *microsecond;
 } CalendarSpec;
 
 void calendar_spec_free(CalendarSpec *c);
index 151067e..3ae99d9 100644 (file)
@@ -490,3 +490,39 @@ int safe_atod(const char *s, double *ret_d) {
         *ret_d = (double) d;
         return 0;
 }
+
+int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) {
+        size_t i;
+        unsigned val = 0;
+        const char *s;
+
+        s = *p;
+
+        /* accept any number of digits, strtoull is limted to 19 */
+        for(i=0; i < digits; i++,s++) {
+                if (*s < '0' || *s > '9') {
+                        if (i == 0)
+                                return -EINVAL;
+
+                        /* too few digits, pad with 0 */
+                        for (; i < digits; i++)
+                                val *= 10;
+
+                        break;
+                }
+
+                val *= 10;
+                val += *s - '0';
+        }
+
+        /* maybe round up */
+        if (*s >= '5' && *s <= '9')
+                val++;
+
+        s += strspn(s, DIGITS);
+
+        *p = s;
+        *res = val;
+
+        return 0;
+}
index 408690d..125de53 100644 (file)
@@ -90,3 +90,5 @@ static inline int safe_atoli(const char *s, long int *ret_u) {
 #endif
 
 int safe_atod(const char *s, double *ret_d);
+
+int parse_fractional_part_u(const char **s, size_t digits, unsigned *res);
index 647763a..b9da699 100644 (file)
@@ -27,6 +27,7 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
+#include "parse-util.h"
 #include "path-util.h"
 #include "string-util.h"
 #include "strv.h"
@@ -658,29 +659,18 @@ int parse_timestamp(const char *t, usec_t *usec) {
 
 parse_usec:
         {
-                char *end;
-                unsigned long long val;
-                size_t l;
+                unsigned add;
 
                 k++;
-                if (*k < '0' || *k > '9')
+                r = parse_fractional_part_u(&k, 6, &add);
+                if (r < 0)
                         return -EINVAL;
 
-                /* base 10 instead of base 0, .09 is not base 8 */
-                errno = 0;
-                val = strtoull(k, &end, 10);
-                if (*end || errno)
+                if (*k)
                         return -EINVAL;
 
-                l = end-k;
-
-                /* val has l digits, make them 6 */
-                for (; l < 6; l++)
-                        val *= 10;
-                for (; l > 6; l--)
-                        val /= 10;
+                x_usec = add;
 
-                x_usec = val;
         }
 
 from_tm: