Merge pull request #1912 from mustrumr/calendarspec-sub-second-v3

Calendarspec sub second v3
This commit is contained in:
Lennart Poettering 2015-11-18 11:31:42 +01:00
commit aa2fb804a1
7 changed files with 218 additions and 110 deletions

View file

@ -229,6 +229,10 @@
the value and all values plus multiples of the repetition value
are matched.</para>
<para>The seconds component may contain decimal fractions both in
the value and the repetition. All fractions are rounded to 6
decimal places.</para>
<para>Either time or date specification may be omitted, in which
case the current day and 00:00:00 is implied, respectively. If the
second component is not specified, <literal>:00</literal> is
@ -276,6 +280,7 @@ Wed-Sat,Tue 12-10-15 1:2:3 → Tue-Sat 2012-10-15 01:02:03
Sat,Sun 12-05 08:05:40 → Sat,Sun *-12-05 08:05:40
Sat,Sun 08:05:40 → Sat,Sun *-*-* 08:05:40
2003-03-05 05:40 → 2003-03-05 05:40:00
05:40:23.4200004/3.1700005 → 05:40:23.420000/3.170001
2003-03-05 05:40 UTC → 2003-03-05 05:40:00 UTC
2003-03-05 → 2003-03-05 00:00:00
03-05 → *-03-05 00:00:00

View 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 (!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)
fprintf(f, "/%i", c->repeat);
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) {
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, &ee, 10);
if (errno > 0)
return -errno;
if (ee == *p)
return -EINVAL;
if ((unsigned long) (int) value != value)
return -ERANGE;
e = ee;
if (usec) {
if (value * USEC_PER_SEC / USEC_PER_SEC != value)
return -ERANGE;
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;
char *e = NULL, *ee = NULL;
CalendarComponent *cc;
int r;
const char *e;
assert(p);
assert(c);
errno = 0;
value = strtoul(*p, &e, 10);
if (errno > 0)
return -errno;
if (e == *p)
return -EINVAL;
if ((unsigned long) (int) value != value)
return -ERANGE;
e = *p;
r = parse_component_decimal(&e, usec, &value);
if (r < 0)
return r;
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)
return -ERANGE;
e++;
r = parse_component_decimal(&e, usec, &repeat);
if (r < 0)
return r;
e = ee;
if (repeat == 0)
return -ERANGE;
}
if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
@ -438,39 +485,12 @@ 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) {
const char *t;
CalendarComponent *cc = NULL;
int r;
assert(p);
assert(c);
t = *p;
if (t[0] == '*') {
*p = t + 1;
*c = NULL;
return 0;
}
r = prepend_component(&t, &cc);
if (r < 0) {
free_chain(cc);
return r;
}
*p = t;
*c = cc;
return 0;
}
static int const_chain(int value, CalendarComponent **c) {
CalendarComponent *cc = NULL;
@ -489,6 +509,40 @@ static int const_chain(int value, CalendarComponent **c) {
return 0;
}
static int parse_chain(const char **p, bool usec, CalendarComponent **c) {
const char *t;
CalendarComponent *cc = NULL;
int r;
assert(p);
assert(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;
return 0;
}
r = prepend_component(&t, usec, &cc);
if (r < 0) {
free_chain(cc);
return r;
}
*p = t;
*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;
}

View file

@ -44,7 +44,7 @@ typedef struct CalendarSpec {
CalendarComponent *hour;
CalendarComponent *minute;
CalendarComponent *second;
CalendarComponent *microsecond;
} CalendarSpec;
void calendar_spec_free(CalendarSpec *c);

View 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;
}

View 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);

View 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;
x_usec = add;
/* val has l digits, make them 6 */
for (; l < 6; l++)
val *= 10;
for (; l > 6; l--)
val /= 10;
x_usec = val;
}
from_tm:

View file

@ -75,7 +75,7 @@ static void test_next(const char *input, const char *new_tz, usec_t after, usec_
u = after;
r = calendar_spec_next_usec(c, after, &u);
printf("At: %s\n", r < 0 ? strerror(-r) : format_timestamp(buf, sizeof(buf), u));
printf("At: %s\n", r < 0 ? strerror(-r) : format_timestamp_us(buf, sizeof(buf), u));
if (expect != (usec_t)-1)
assert_se(r >= 0 && u == expect);
else
@ -123,6 +123,9 @@ int main(int argc, char* argv[]) {
test_one("annually", "*-01-01 00:00:00");
test_one("*:2/3", "*-*-* *:02/3:00");
test_one("2015-10-25 01:00:00 uTc", "2015-10-25 01:00:00 UTC");
test_one("2016-03-27 03:17:00.4200005", "2016-03-27 03:17:00.420001");
test_one("2016-03-27 03:17:00/0.42", "2016-03-27 03:17:00/0.420000");
test_one("2016-03-27 03:17:00/0.42", "2016-03-27 03:17:00/0.420000");
test_next("2016-03-27 03:17:00", "", 12345, 1459048620000000);
test_next("2016-03-27 03:17:00", "CET", 12345, 1459041420000000);
@ -131,11 +134,19 @@ int main(int argc, char* argv[]) {
test_next("2016-03-27 03:17:00 UTC", "", 12345, 1459048620000000);
test_next("2016-03-27 03:17:00 UTC", "CET", 12345, 1459048620000000);
test_next("2016-03-27 03:17:00 UTC", "EET", 12345, 1459048620000000);
test_next("2016-03-27 03:17:00.420000001 UTC", "EET", 12345, 1459048620420000);
test_next("2016-03-27 03:17:00.4200005 UTC", "EET", 12345, 1459048620420001);
test_next("2015-11-13 09:11:23.42", "EET", 12345, 1447398683420000);
test_next("2015-11-13 09:11:23.42/1.77", "EET", 1447398683420000, 1447398685190000);
test_next("2015-11-13 09:11:23.42/1.77", "EET", 1447398683419999, 1447398683420000);
assert_se(calendar_spec_from_string("test", &c) < 0);
assert_se(calendar_spec_from_string("", &c) < 0);
assert_se(calendar_spec_from_string("7", &c) < 0);
assert_se(calendar_spec_from_string("121212:1:2", &c) < 0);
assert_se(calendar_spec_from_string("2000-03-05.23 00:00:00", &c) < 0);
assert_se(calendar_spec_from_string("2000-03-05 00:00.1:00", &c) < 0);
assert_se(calendar_spec_from_string("00:00:00/0.00000001", &c) < 0);
return 0;
}