Merge pull request #1569 from mustrumr/date-parse-additions

Date parse additions
This commit is contained in:
Lennart Poettering 2015-10-15 13:09:26 +02:00
commit 590a23de52
7 changed files with 260 additions and 118 deletions

View file

@ -117,10 +117,11 @@
<refsect1>
<title>Parsing Timestamps</title>
<para>When parsing systemd will accept a similar timestamp syntax,
but excluding any timezone specification (this limitation might be
removed eventually). The weekday specification is optional, but
when the weekday is specified it must either be in the abbreviated
<para>When parsing systemd will accept a similar syntax, but expects
no timezone specification, unless it is given as the literal string
"UTC". In this case the time is considered in UTC time, otherwise in
the local timezone. The weekday specification is optional, but when
the weekday is specified it must either be in the abbreviated
(<literal>Wed</literal>) or non-abbreviated
(<literal>Wednesday</literal>) English language form (case does
not matter), and is not subject to the locale choice of the user.
@ -157,22 +158,29 @@
00:00.</para>
<para>Examples for valid timestamps and their normalized form
(assuming the current time was 2012-11-23 18:15:22):</para>
(assuming the current time was 2012-11-23 18:15:22 and the timezone
was UTC+8, for example TZ=Asia/Shanghai):</para>
<programlisting>Fri 2012-11-23 11:12:13 → Fri 2012-11-23 11:12:13
2012-11-23 11:12:13 → Fri 2012-11-23 11:12:13
2012-11-23 → Fri 2012-11-23 00:00:00
12-11-23 → Fri 2012-11-23 00:00:00
11:12:13 → Fri 2012-11-23 11:12:13
11:12 → Fri 2012-11-23 11:12:00
now → Fri 2012-11-23 18:15:22
today → Fri 2012-11-23 00:00:00
yesterday → Fri 2012-11-22 00:00:00
tomorrow → Fri 2012-11-24 00:00:00
+3h30min → Fri 2012-11-23 21:45:22
-5s → Fri 2012-11-23 18:15:17
11min ago → Fri 2012-11-23 18:04:22
@1395716396 → Tue 2014-03-25 03:59:56</programlisting>
2012-11-23 11:12:13 UTC → Fri 2012-11-23 19:12:13
2012-11-23 → Fri 2012-11-23 00:00:00
12-11-23 → Fri 2012-11-23 00:00:00
11:12:13 → Fri 2012-11-23 11:12:13
11:12:13.9900009 → Fri 2012-11-23 11:12:13
format_timestamp_us: Fri 2012-11-23 11:12:13.990000
11:12 → Fri 2012-11-23 11:12:00
now → Fri 2012-11-23 18:15:22
today → Fri 2012-11-23 00:00:00
today UTC → Fri 2012-11-23 16:00:00
yesterday → Fri 2012-11-22 00:00:00
tomorrow → Fri 2012-11-24 00:00:00
+3h30min → Fri 2012-11-23 21:45:22
+3h30min UTC → -EINVAL
-5s → Fri 2012-11-23 18:15:17
11min ago → Fri 2012-11-23 18:04:22
11min ago UTC → -EINVAL
@1395716396 → Tue 2014-03-25 03:59:56</programlisting>
<para>Note that timestamps printed by systemd will not be parsed
correctly by systemd, as the timezone specification is not
@ -226,7 +234,8 @@
second component is not specified, <literal>:00</literal> is
assumed.</para>
<para>Timezone names may not be specified.</para>
<para>A timezone specification is not expected, unless it is given
as the literal string "UTC", similarly to timestamps.</para>
<para>The special expressions
<literal>minutely</literal>,
@ -242,7 +251,7 @@
<literal>*-*-01 00:00:00</literal>,
<literal>Mon *-*-* 00:00:00</literal>,
<literal>*-01-01 00:00:00</literal>,
<literal>*-01,04,07,10-01 00:00:0</literal> and
<literal>*-01,04,07,10-01 00:00:00</literal> and
<literal>*-01,07-01 00:00:00</literal> respectively.
</para>
@ -251,31 +260,33 @@
<programlisting> Sat,Thu,Mon-Wed,Sat-Sun → Mon-Thu,Sat,Sun *-*-* 00:00:00
Mon,Sun 12-*-* 2,1:23 → Mon,Sun 2012-*-* 01,02:23:00
Wed *-1 → Wed *-*-01 00:00:00
Wed-Wed,Wed *-1 → Wed *-*-01 00:00:00
Wed, 17:48 → Wed *-*-* 17:48:00
Wed *-1 → Wed *-*-01 00:00:00
Wed-Wed,Wed *-1 → Wed *-*-01 00:00:00
Wed, 17:48 → Wed *-*-* 17:48:00
Wed-Sat,Tue 12-10-15 1:2:3 → Tue-Sat 2012-10-15 01:02:03
*-*-7 0:0:0 → *-*-07 00:00:00
10-15 → *-10-15 00:00:00
*-*-7 0:0:0 → *-*-07 00:00:00
10-15 → *-10-15 00:00:00
monday *-12-* 17:00 → Mon *-12-* 17:00:00
Mon,Fri *-*-3,1,2 *:30:45 → Mon,Fri *-*-01,02,03 *:30:45
12,14,13,12:20,10,30 → *-*-* 12,13,14:10,20,30:00
mon,fri *-1/2-1,3 *:30:45 → Mon,Fri *-01/2-01,03 *:30:45
03-05 08:05:40 → *-03-05 08:05:40
08:05:40 → *-*-* 08:05:40
05:40 → *-*-* 05:40:00
03-05 08:05:40 → *-03-05 08:05:40
08:05:40 → *-*-* 08:05:40
05:40 → *-*-* 05:40:00
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
2003-03-05 → 2003-03-05 00:00:00
03-05 → *-03-05 00:00:00
hourly → *-*-* *:00:00
daily → *-*-* 00:00:00
monthly → *-*-01 00:00:00
weekly → Mon *-*-* 00:00:00
yearly → *-01-01 00:00:00
annually → *-01-01 00:00:00
*:2/3 → *-*-* *:02/3:00</programlisting>
Sat,Sun 08:05:40 → Sat,Sun *-*-* 08:05:40
2003-03-05 05:40 → 2003-03-05 05:40:00
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
hourly → *-*-* *:00:00
daily → *-*-* 00:00:00
daily UTC → *-*-* 00:00:00 UTC
monthly → *-*-01 00:00:00
weekly → Mon *-*-* 00:00:00
yearly → *-01-01 00:00:00
annually → *-01-01 00:00:00
*:2/3 → *-*-* *:02/3:00</programlisting>
<para>Calendar events are used by timer units, see
<citerefentry><refentrytitle>systemd.timer</refentrytitle><manvolnum>5</manvolnum></citerefentry>

View file

@ -279,6 +279,9 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) {
fputc(':', f);
format_chain(f, 2, c->second);
if (c->utc)
fputs(" UTC", f);
r = fflush_and_check(f);
if (r < 0) {
free(buf);
@ -657,6 +660,10 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
if (!c)
return -ENOMEM;
c->utc = endswith_no_case(p, "UTC");
if (c->utc)
p = strndupa(p, strlen(p) - strlen(" UTC"));
if (strcaseeq(p, "minutely")) {
r = const_chain(0, &c->second);
if (r < 0)
@ -859,13 +866,13 @@ static int find_matching_component(const CalendarComponent *c, int *val) {
return r;
}
static bool tm_out_of_bounds(const struct tm *tm) {
static bool tm_out_of_bounds(const struct tm *tm, bool utc) {
struct tm t;
assert(tm);
t = *tm;
if (mktime(&t) == (time_t) -1)
if (mktime_or_timegm(&t, utc) == (time_t) -1)
return true;
/* Did any normalization take place? If so, it was out of bounds before */
@ -878,7 +885,7 @@ static bool tm_out_of_bounds(const struct tm *tm) {
t.tm_sec != tm->tm_sec;
}
static bool matches_weekday(int weekdays_bits, const struct tm *tm) {
static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
struct tm t;
int k;
@ -886,7 +893,7 @@ static bool matches_weekday(int weekdays_bits, const struct tm *tm) {
return true;
t = *tm;
if (mktime(&t) == (time_t) -1)
if (mktime_or_timegm(&t, utc) == (time_t) -1)
return false;
k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
@ -904,7 +911,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm) {
for (;;) {
/* Normalize the current date */
mktime(&c);
mktime_or_timegm(&c, spec->utc);
c.tm_isdst = -1;
c.tm_year += 1900;
@ -916,7 +923,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm) {
c.tm_mday = 1;
c.tm_hour = c.tm_min = c.tm_sec = 0;
}
if (r < 0 || tm_out_of_bounds(&c))
if (r < 0 || tm_out_of_bounds(&c, spec->utc))
return r;
c.tm_mon += 1;
@ -927,7 +934,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm) {
c.tm_mday = 1;
c.tm_hour = c.tm_min = c.tm_sec = 0;
}
if (r < 0 || tm_out_of_bounds(&c)) {
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
c.tm_year ++;
c.tm_mon = 0;
c.tm_mday = 1;
@ -938,14 +945,14 @@ static int find_next(const CalendarSpec *spec, struct tm *tm) {
r = find_matching_component(spec->day, &c.tm_mday);
if (r > 0)
c.tm_hour = c.tm_min = c.tm_sec = 0;
if (r < 0 || tm_out_of_bounds(&c)) {
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;
continue;
}
if (!matches_weekday(spec->weekdays_bits, &c)) {
if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) {
c.tm_mday++;
c.tm_hour = c.tm_min = c.tm_sec = 0;
continue;
@ -954,7 +961,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm) {
r = find_matching_component(spec->hour, &c.tm_hour);
if (r > 0)
c.tm_min = c.tm_sec = 0;
if (r < 0 || tm_out_of_bounds(&c)) {
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
c.tm_mday ++;
c.tm_hour = c.tm_min = c.tm_sec = 0;
continue;
@ -963,14 +970,14 @@ static int find_next(const CalendarSpec *spec, struct tm *tm) {
r = find_matching_component(spec->minute, &c.tm_min);
if (r > 0)
c.tm_sec = 0;
if (r < 0 || tm_out_of_bounds(&c)) {
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
c.tm_hour ++;
c.tm_min = c.tm_sec = 0;
continue;
}
r = find_matching_component(spec->second, &c.tm_sec);
if (r < 0 || tm_out_of_bounds(&c)) {
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
c.tm_min ++;
c.tm_sec = 0;
continue;
@ -991,13 +998,13 @@ int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next)
assert(next);
t = (time_t) (usec / USEC_PER_SEC) + 1;
assert_se(localtime_r(&t, &tm));
assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc));
r = find_next(spec, &tm);
if (r < 0)
return r;
t = mktime(&tm);
t = mktime_or_timegm(&tm, spec->utc);
if (t == (time_t) -1)
return -EINVAL;

View file

@ -36,6 +36,7 @@ typedef struct CalendarComponent {
typedef struct CalendarSpec {
int weekdays_bits;
bool utc;
CalendarComponent *year;
CalendarComponent *month;

View file

@ -19,7 +19,6 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <time.h>
#include <string.h>
#include <sys/timex.h>
#include <sys/timerfd.h>
@ -205,11 +204,8 @@ static char *format_timestamp_internal(char *buf, size_t l, usec_t t, bool utc)
return NULL;
sec = (time_t) (t / USEC_PER_SEC);
localtime_or_gmtime_r(&sec, &tm, utc);
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)
return NULL;
@ -235,10 +231,7 @@ static char *format_timestamp_internal_us(char *buf, size_t l, usec_t t, bool ut
return NULL;
sec = (time_t) (t / USEC_PER_SEC);
if (utc)
gmtime_r(&sec, &tm);
else
localtime_r(&sec, &tm);
localtime_or_gmtime_r(&sec, &tm, utc);
if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm) <= 0)
return NULL;
@ -484,9 +477,10 @@ int parse_timestamp(const char *t, usec_t *usec) {
};
const char *k;
bool utc;
struct tm tm, copy;
time_t x;
usec_t plus = 0, minus = 0, ret;
usec_t x_usec, plus = 0, minus = 0, ret;
int r, weekday = -1;
unsigned i;
@ -511,28 +505,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 (t[0] == '@')
return parse_sec(t + 1, usec);
ret = now(CLOCK_REALTIME);
if (streq(t, "now"))
goto finish;
else if (streq(t, "today")) {
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
goto finish;
} else if (streq(t, "yesterday")) {
tm.tm_mday --;
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
goto finish;
} else if (streq(t, "tomorrow")) {
tm.tm_mday ++;
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
goto finish;
} else if (t[0] == '+') {
else if (t[0] == '+') {
r = parse_sec(t+1, &plus);
if (r < 0)
return r;
@ -546,35 +527,51 @@ 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")) {
t = strndupa(t, strlen(t) - strlen(" ago"));
else if (endswith(t, " ago")) {
_cleanup_free_ char *z;
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;
t = strndupa(t, strlen(t) - strlen(" left"));
z = strndup(t, strlen(t) - 4);
if (!z)
return -ENOMEM;
r = parse_sec(z, &plus);
r = parse_sec(t, &plus);
if (r < 0)
return r;
goto finish;
}
utc = endswith_no_case(t, " UTC");
if (utc)
t = strndupa(t, strlen(t) - strlen(" UTC"));
x = ret / USEC_PER_SEC;
x_usec = 0;
assert_se(localtime_or_gmtime_r(&x, &tm, utc));
tm.tm_isdst = -1;
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;
@ -592,66 +589,106 @@ 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:
{
char *end;
unsigned long long val;
size_t l;
k++;
if (*k < '0' || *k > '9')
return -EINVAL;
/* base 10 instead of base 0, .09 is not base 8 */
errno = 0;
val = strtoull(k, &end, 10);
if (*end || errno)
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 = val;
}
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;
@ -1072,3 +1109,11 @@ 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);
}
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);
}

View file

@ -21,8 +21,9 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <stdio.h>
#include <inttypes.h>
#include <stdio.h>
#include <time.h>
typedef uint64_t usec_t;
typedef uint64_t nsec_t;
@ -117,3 +118,6 @@ clockid_t clock_boottime_or_monotonic(void);
"xstrftime: " #buf "[] must be big enough")
int get_timezone(char **timezone);
time_t mktime_or_timegm(struct tm *tm, bool utc);
struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc);

View file

@ -50,6 +50,44 @@ static void test_one(const char *input, const char *output) {
assert_se(streq(q, p));
}
static void test_next(const char *input, const char *new_tz, usec_t after, usec_t expect) {
CalendarSpec *c;
usec_t u;
char *old_tz;
char buf[FORMAT_TIMESTAMP_MAX];
int r;
old_tz = getenv("TZ");
if (old_tz)
old_tz = strdupa(old_tz);
if (new_tz)
assert_se(setenv("TZ", new_tz, 1) >= 0);
else
assert_se(unsetenv("TZ") >= 0);
tzset();
assert_se(calendar_spec_from_string(input, &c) >= 0);
printf("\"%s\"\n", input);
u = after;
r = calendar_spec_next_usec(c, after, &u);
printf("At: %s\n", r < 0 ? strerror(-r) : format_timestamp(buf, sizeof(buf), u));
if (expect != (usec_t)-1)
assert_se(r >= 0 && u == expect);
else
assert(r == -ENOENT);
calendar_spec_free(c);
if (old_tz)
assert_se(setenv("TZ", old_tz, 1) >= 0);
else
assert_se(unsetenv("TZ") >= 0);
tzset();
}
int main(int argc, char* argv[]) {
CalendarSpec *c;
@ -82,6 +120,15 @@ int main(int argc, char* argv[]) {
test_one("semi-annually", "*-01,07-01 00:00:00");
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_next("2016-03-27 03:17:00", "", 12345, 1459048620000000);
test_next("2016-03-27 03:17:00", "CET", 12345, 1459041420000000);
test_next("2016-03-27 03:17:00", "EET", 12345, -1);
test_next("2016-03-27 03:17:00 UTC", NULL, 12345, 1459048620000000);
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);
assert_se(calendar_spec_from_string("test", &c) < 0);
assert_se(calendar_spec_from_string("", &c) < 0);

View file

@ -23,12 +23,12 @@
#include "util.h"
static void test_one(const char *p) {
static void test_should_pass(const char *p) {
usec_t t, q;
char buf[FORMAT_TIMESTAMP_MAX], buf_relative[FORMAT_TIMESTAMP_RELATIVE_MAX];
assert_se(parse_timestamp(p, &t) >= 0);
format_timestamp(buf, sizeof(buf), t);
format_timestamp_us(buf, sizeof(buf), t);
log_info("%s", buf);
/* Chop off timezone */
@ -42,23 +42,50 @@ static void test_one(const char *p) {
assert_se(parse_timestamp(buf, &q) >= 0);
}
static void test_should_fail(const char *p) {
usec_t t;
assert_se(parse_timestamp(p, &t) < 0);
}
static void test_one(const char *p) {
_cleanup_free_ char *with_utc;
log_info("Test: %s", p);
with_utc = strjoin(p, " UTC", NULL);
test_should_pass(p);
test_should_pass(with_utc);
}
static void test_one_noutc(const char *p) {
_cleanup_free_ char *with_utc;
log_info("Test: %s", p);
with_utc = strjoin(p, " UTC", NULL);
test_should_pass(p);
test_should_fail(with_utc);
}
int main(int argc, char *argv[]) {
test_one("17:41");
test_one("18:42:44");
test_one("18:42:44.0");
test_one("18:42:44.999999999999");
test_one("12-10-02 12:13:14");
test_one("12-10-2 12:13:14");
test_one("12-10-03 12:13");
test_one("2012-12-30 18:42");
test_one("2012-10-02");
test_one("Tue 2012-10-02");
test_one("now");
test_one_noutc("now");
test_one("yesterday");
test_one("today");
test_one("tomorrow");
test_one("+2d");
test_one("+2y 4d");
test_one("5months ago");
test_one("@1395716396");
test_one_noutc("+2d");
test_one_noutc("+2y 4d");
test_one_noutc("5months ago");
test_one_noutc("@1395716396");
test_one_noutc("today UTC");
return 0;
}