1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-29 21:55:36 +03:00

Merge pull request #26409 from yuwata/parse-timestamp

fix parse_timestamp()
This commit is contained in:
Luca Boccassi 2023-02-24 11:45:03 +00:00 committed by GitHub
commit e1e6d5f353
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 592 additions and 172 deletions

View File

@ -171,6 +171,8 @@ dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) { dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) {
usec_t nowm; usec_t nowm;
assert(ts);
if (u == USEC_INFINITY) { if (u == USEC_INFINITY) {
ts->realtime = ts->monotonic = USEC_INFINITY; ts->realtime = ts->monotonic = USEC_INFINITY;
return ts; return ts;
@ -183,6 +185,7 @@ dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) {
} }
usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) { usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
assert(ts);
switch (clock) { switch (clock) {
@ -228,7 +231,7 @@ nsec_t timespec_load_nsec(const struct timespec *ts) {
return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec; return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec;
} }
struct timespec *timespec_store(struct timespec *ts, usec_t u) { struct timespec *timespec_store(struct timespec *ts, usec_t u) {
assert(ts); assert(ts);
if (u == USEC_INFINITY || if (u == USEC_INFINITY ||
@ -244,7 +247,7 @@ struct timespec *timespec_store(struct timespec *ts, usec_t u) {
return ts; return ts;
} }
struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n) { struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n) {
assert(ts); assert(ts);
if (n == NSEC_INFINITY || if (n == NSEC_INFINITY ||
@ -422,6 +425,8 @@ char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, bool implici
const char *s; const char *s;
usec_t n, d; usec_t n, d;
assert(buf);
if (!timestamp_is_set(t)) if (!timestamp_is_set(t))
return NULL; return NULL;
@ -605,7 +610,14 @@ char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
return buf; return buf;
} }
static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) { static int parse_timestamp_impl(
const char *t,
bool with_tz,
bool utc,
int isdst,
long gmtoff,
usec_t *ret) {
static const struct { static const struct {
const char *name; const char *name;
const int nr; const int nr;
@ -626,12 +638,12 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
{ "Sat", 6 }, { "Sat", 6 },
}; };
const char *k, *utc = NULL, *tzn = NULL; usec_t usec, plus = 0, minus = 0;
int r, weekday = -1;
unsigned fractional = 0;
const char *k;
struct tm tm, copy; struct tm tm, copy;
time_t x; time_t sec;
usec_t x_usec, plus = 0, minus = 0, ret;
int r, weekday = -1, dst = -1;
size_t i;
/* Allowed syntaxes: /* Allowed syntaxes:
* *
@ -652,98 +664,65 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
assert(t); assert(t);
if (t[0] == '@' && !with_tz) if (t[0] == '@' && !with_tz)
return parse_sec(t + 1, usec); return parse_sec(t + 1, ret);
ret = now(CLOCK_REALTIME); usec = now(CLOCK_REALTIME);
if (!with_tz) { if (!with_tz) {
if (streq(t, "now")) if (streq(t, "now"))
goto finish; goto finish;
else if (t[0] == '+') { if (t[0] == '+') {
r = parse_sec(t+1, &plus); r = parse_sec(t+1, &plus);
if (r < 0) if (r < 0)
return r; return r;
goto finish; goto finish;
}
} else if (t[0] == '-') { if (t[0] == '-') {
r = parse_sec(t+1, &minus); r = parse_sec(t+1, &minus);
if (r < 0) if (r < 0)
return r; return r;
goto finish; goto finish;
}
} else if ((k = endswith(t, " ago"))) { if ((k = endswith(t, " ago"))) {
t = strndupa_safe(t, k - t); _cleanup_free_ char *buf = NULL;
r = parse_sec(t, &minus); buf = strndup(t, k - t);
if (r < 0) if (!buf)
return r; return -ENOMEM;
goto finish; r = parse_sec(buf, &minus);
} else if ((k = endswith(t, " left"))) {
t = strndupa_safe(t, k - t);
r = parse_sec(t, &plus);
if (r < 0) if (r < 0)
return r; return r;
goto finish; goto finish;
} }
/* See if the timestamp is suffixed with UTC */ if ((k = endswith(t, " left"))) {
utc = endswith_no_case(t, " UTC"); _cleanup_free_ char *buf = NULL;
if (utc)
t = strndupa_safe(t, utc - t);
else {
const char *e = NULL;
int j;
tzset(); buf = strndup(t, k - t);
if (!buf)
return -ENOMEM;
/* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note r = parse_sec(buf, &plus);
* that we only support the local timezones here, nothing else. Not because we if (r < 0)
* wouldn't want to, but simply because there are no nice APIs available to cover return r;
* 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++) { goto finish;
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_safe(t, e - t - 1);
dst = j;
tzn = tzname[j];
}
} }
} }
x = (time_t) (ret / USEC_PER_SEC); sec = (time_t) (usec / USEC_PER_SEC);
x_usec = 0;
if (!localtime_or_gmtime_r(&x, &tm, utc)) if (!localtime_or_gmtime_r(&sec, &tm, utc))
return -EINVAL; return -EINVAL;
tm.tm_isdst = dst; tm.tm_isdst = isdst;
if (!with_tz && tzn)
tm.tm_zone = tzn;
if (streq(t, "today")) { if (streq(t, "today")) {
tm.tm_sec = tm.tm_min = tm.tm_hour = 0; tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
@ -760,18 +739,13 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
goto from_tm; goto from_tm;
} }
for (i = 0; i < ELEMENTSOF(day_nr); i++) { for (size_t i = 0; i < ELEMENTSOF(day_nr); i++) {
size_t skip; k = startswith_no_case(t, day_nr[i].name);
if (!k || *k != ' ')
if (!startswith_no_case(t, day_nr[i].name))
continue;
skip = strlen(day_nr[i].name);
if (t[skip] != ' ')
continue; continue;
weekday = day_nr[i].nr; weekday = day_nr[i].nr;
t += skip + 1; t = k + 1;
break; break;
} }
@ -850,65 +824,149 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
return -EINVAL; return -EINVAL;
parse_usec: parse_usec:
{ k++;
unsigned add; r = parse_fractional_part_u(&k, 6, &fractional);
if (r < 0)
k++; return -EINVAL;
r = parse_fractional_part_u(&k, 6, &add); if (*k != '\0')
if (r < 0) return -EINVAL;
return -EINVAL;
if (*k)
return -EINVAL;
x_usec = add;
}
from_tm: from_tm:
assert(plus == 0);
assert(minus == 0);
if (weekday >= 0 && tm.tm_wday != weekday) if (weekday >= 0 && tm.tm_wday != weekday)
return -EINVAL; return -EINVAL;
x = mktime_or_timegm(&tm, utc); if (gmtoff < 0) {
if (x < 0) plus = -gmtoff * USEC_PER_SEC;
/* If gmtoff is negative, the string maye be too old to be parsed as UTC.
* E.g. 1969-12-31 23:00:00 -06 == 1970-01-01 05:00:00 UTC
* We assumed that gmtoff is in the range of -24:00+24:00, hence the only date we need to
* handle here is 1969-12-31. So, let's shift the date with one day, then subtract the shift
* later. */
if (tm.tm_year == 69 && tm.tm_mon == 11 && tm.tm_mday == 31) {
/* Thu 1970-01-01-00:00:00 */
tm.tm_year = 70;
tm.tm_mon = 0;
tm.tm_mday = 1;
tm.tm_wday = 4;
tm.tm_yday = 0;
minus = USEC_PER_DAY;
}
} else
minus = gmtoff * USEC_PER_SEC;
sec = mktime_or_timegm(&tm, utc);
if (sec < 0)
return -EINVAL; return -EINVAL;
ret = (usec_t) x * USEC_PER_SEC + x_usec; usec = usec_add(sec * USEC_PER_SEC, fractional);
if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
return -EINVAL;
finish: finish:
if (ret + plus < ret) /* overflow? */ usec = usec_add(usec, plus);
return -EINVAL;
ret += plus; if (usec < minus)
if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
return -EINVAL; return -EINVAL;
if (ret >= minus) usec = usec_sub_unsigned(usec, minus);
ret -= minus;
else if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
return -EINVAL; return -EINVAL;
if (usec) if (ret)
*usec = ret; *ret = usec;
return 0; return 0;
} }
static int parse_timestamp_with_tz(const char *t, size_t len, bool utc, int isdst, long gmtoff, usec_t *ret) {
_cleanup_free_ char *buf = NULL;
assert(t);
assert(len > 0);
buf = strndup(t, len);
if (!buf)
return -ENOMEM;
return parse_timestamp_impl(buf, /* with_tz = */ true, utc, isdst, gmtoff, ret);
}
static int parse_timestamp_maybe_with_tz(const char *t, size_t len, bool valid_tz, usec_t *ret) {
assert(t);
assert(len > 0);
tzset();
for (int j = 0; j <= 1; j++) {
if (isempty(tzname[j]))
continue;
if (!streq(t + len + 1, tzname[j]))
continue;
/* The specified timezone matches tzname[] of the local timezone. */
return parse_timestamp_with_tz(t, len, /* utc = */ false, /* isdst = */ j, /* gmtoff = */ 0, ret);
}
if (valid_tz)
/* We know that the specified timezone is a valid zoneinfo (e.g. Asia/Tokyo). So, simply drop
* the timezone and parse the remaining string as a local time. */
return parse_timestamp_with_tz(t, len, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
return parse_timestamp_impl(t, /* with_tz = */ false, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
}
typedef struct ParseTimestampResult { typedef struct ParseTimestampResult {
usec_t usec; usec_t usec;
int return_value; int return_value;
} ParseTimestampResult; } ParseTimestampResult;
int parse_timestamp(const char *t, usec_t *usec) { int parse_timestamp(const char *t, usec_t *ret) {
char *last_space, *tz = NULL;
ParseTimestampResult *shared, tmp; ParseTimestampResult *shared, tmp;
const char *k, *tz, *space;
struct tm tm;
int r; int r;
last_space = strrchr(t, ' '); assert(t);
if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG))
tz = last_space + 1;
if (!tz || endswith_no_case(t, " UTC")) space = strrchr(t, ' ');
return parse_timestamp_impl(t, usec, false); if (!space)
return parse_timestamp_impl(t, /* with_tz = */ false, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
/* The string starts with space. */
if (space == t)
return -EINVAL;
/* Shortcut, parse the string as UTC. */
if (streq(space + 1, "UTC"))
return parse_timestamp_with_tz(t, space - t, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret);
/* If the timezone is compatible with RFC-822/ISO 8601 (e.g. +06, or -03:00) then parse the string as
* UTC and shift the result. */
k = strptime(space + 1, "%z", &tm);
if (k && *k == '\0') {
/* glibc accepts gmtoff more than 24 hours, but we refuse it. */
if ((usec_t) labs(tm.tm_gmtoff) > USEC_PER_DAY / USEC_PER_SEC)
return -EINVAL;
return parse_timestamp_with_tz(t, space - t, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret);
}
/* If the last word is not a timezone file (e.g. Asia/Tokyo), then let's check if it matches
* tzname[] of the local timezone, e.g. JST or CEST. */
if (!timezone_is_valid(space + 1, LOG_DEBUG))
return parse_timestamp_maybe_with_tz(t, space - t, /* valid_tz = */ false, ret);
/* Shortcut. If the current $TZ is equivalent to the specified timezone, it is not necessary to fork
* the process. */
tz = getenv("TZ");
if (tz && *tz == ':' && streq(tz + 1, space + 1))
return parse_timestamp_maybe_with_tz(t, space - t, /* valid_tz = */ true, ret);
/* Otherwise, to avoid polluting the current environment variables, let's fork the process and set
* the specified timezone in the child process. */
shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if (shared == MAP_FAILED) if (shared == MAP_FAILED)
@ -920,11 +978,10 @@ int parse_timestamp(const char *t, usec_t *usec) {
return r; return r;
} }
if (r == 0) { if (r == 0) {
bool with_tz = true; const char *colon_tz;
char *colon_tz;
/* tzset(3) says $TZ should be prefixed with ":" if we reference timezone files */ /* tzset(3) says $TZ should be prefixed with ":" if we reference timezone files */
colon_tz = strjoina(":", tz); colon_tz = strjoina(":", space + 1);
if (setenv("TZ", colon_tz, 1) != 0) { if (setenv("TZ", colon_tz, 1) != 0) {
shared->return_value = negative_errno(); shared->return_value = negative_errno();
@ -933,15 +990,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
tzset(); tzset();
/* If there is a timezone that matches the tzname fields, leave the parsing to the implementation. shared->return_value = parse_timestamp_maybe_with_tz(t, space - t, /* valid_tz = */ true, &shared->usec);
* Otherwise just cut it off. */
with_tz = !STR_IN_SET(tz, tzname[0], tzname[1]);
/* Cut off the timezone if we don't need it. */
if (with_tz)
t = strndupa_safe(t, last_space - t);
shared->return_value = parse_timestamp_impl(t, &shared->usec, with_tz);
_exit(EXIT_SUCCESS); _exit(EXIT_SUCCESS);
} }
@ -950,13 +999,13 @@ int parse_timestamp(const char *t, usec_t *usec) {
if (munmap(shared, sizeof *shared) != 0) if (munmap(shared, sizeof *shared) != 0)
return negative_errno(); return negative_errno();
if (tmp.return_value == 0 && usec) if (tmp.return_value == 0 && ret)
*usec = tmp.usec; *ret = tmp.usec;
return tmp.return_value; return tmp.return_value;
} }
static const char* extract_multiplier(const char *p, usec_t *multiplier) { static const char* extract_multiplier(const char *p, usec_t *ret) {
static const struct { static const struct {
const char *suffix; const char *suffix;
usec_t usec; usec_t usec;
@ -992,12 +1041,15 @@ static const char* extract_multiplier(const char *p, usec_t *multiplier) {
{ "µs", 1ULL }, { "µs", 1ULL },
}; };
assert(p);
assert(ret);
for (size_t i = 0; i < ELEMENTSOF(table); i++) { for (size_t i = 0; i < ELEMENTSOF(table); i++) {
char *e; char *e;
e = startswith(p, table[i].suffix); e = startswith(p, table[i].suffix);
if (e) { if (e) {
*multiplier = table[i].usec; *ret = table[i].usec;
return e; return e;
} }
} }
@ -1005,9 +1057,9 @@ static const char* extract_multiplier(const char *p, usec_t *multiplier) {
return p; return p;
} }
int parse_time(const char *t, usec_t *usec, usec_t default_unit) { int parse_time(const char *t, usec_t *ret, usec_t default_unit) {
const char *p, *s; const char *p, *s;
usec_t r = 0; usec_t usec = 0;
bool something = false; bool something = false;
assert(t); assert(t);
@ -1022,8 +1074,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
if (*s != 0) if (*s != 0)
return -EINVAL; return -EINVAL;
if (usec) if (ret)
*usec = USEC_INFINITY; *ret = USEC_INFINITY;
return 0; return 0;
} }
@ -1070,10 +1122,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
return -ERANGE; return -ERANGE;
k = (usec_t) l * multiplier; k = (usec_t) l * multiplier;
if (k >= USEC_INFINITY - r) if (k >= USEC_INFINITY - usec)
return -ERANGE; return -ERANGE;
r += k; usec += k;
something = true; something = true;
@ -1083,10 +1135,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) { for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
k = (usec_t) (*b - '0') * m; k = (usec_t) (*b - '0') * m;
if (k >= USEC_INFINITY - r) if (k >= USEC_INFINITY - usec)
return -ERANGE; return -ERANGE;
r += k; usec += k;
} }
/* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */ /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
@ -1095,13 +1147,13 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
} }
} }
if (usec) if (ret)
*usec = r; *ret = usec;
return 0; return 0;
} }
int parse_sec(const char *t, usec_t *usec) { int parse_sec(const char *t, usec_t *ret) {
return parse_time(t, usec, USEC_PER_SEC); return parse_time(t, ret, USEC_PER_SEC);
} }
int parse_sec_fix_0(const char *t, usec_t *ret) { int parse_sec_fix_0(const char *t, usec_t *ret) {
@ -1120,6 +1172,9 @@ int parse_sec_fix_0(const char *t, usec_t *ret) {
} }
int parse_sec_def_infinity(const char *t, usec_t *ret) { int parse_sec_def_infinity(const char *t, usec_t *ret) {
assert(t);
assert(ret);
t += strspn(t, WHITESPACE); t += strspn(t, WHITESPACE);
if (isempty(t)) { if (isempty(t)) {
*ret = USEC_INFINITY; *ret = USEC_INFINITY;
@ -1128,7 +1183,7 @@ int parse_sec_def_infinity(const char *t, usec_t *ret) {
return parse_sec(t, ret); return parse_sec(t, ret);
} }
static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) { static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) {
static const struct { static const struct {
const char *suffix; const char *suffix;
nsec_t nsec; nsec_t nsec;
@ -1168,12 +1223,15 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
}; };
size_t i; size_t i;
assert(p);
assert(ret);
for (i = 0; i < ELEMENTSOF(table); i++) { for (i = 0; i < ELEMENTSOF(table); i++) {
char *e; char *e;
e = startswith(p, table[i].suffix); e = startswith(p, table[i].suffix);
if (e) { if (e) {
*multiplier = table[i].nsec; *ret = table[i].nsec;
return e; return e;
} }
} }
@ -1181,13 +1239,13 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
return p; return p;
} }
int parse_nsec(const char *t, nsec_t *nsec) { int parse_nsec(const char *t, nsec_t *ret) {
const char *p, *s; const char *p, *s;
nsec_t r = 0; nsec_t nsec = 0;
bool something = false; bool something = false;
assert(t); assert(t);
assert(nsec); assert(ret);
p = t; p = t;
@ -1198,7 +1256,7 @@ int parse_nsec(const char *t, nsec_t *nsec) {
if (*s != 0) if (*s != 0)
return -EINVAL; return -EINVAL;
*nsec = NSEC_INFINITY; *ret = NSEC_INFINITY;
return 0; return 0;
} }
@ -1245,10 +1303,10 @@ int parse_nsec(const char *t, nsec_t *nsec) {
return -ERANGE; return -ERANGE;
k = (nsec_t) l * multiplier; k = (nsec_t) l * multiplier;
if (k >= NSEC_INFINITY - r) if (k >= NSEC_INFINITY - nsec)
return -ERANGE; return -ERANGE;
r += k; nsec += k;
something = true; something = true;
@ -1258,10 +1316,10 @@ int parse_nsec(const char *t, nsec_t *nsec) {
for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) { for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
k = (nsec_t) (*b - '0') * m; k = (nsec_t) (*b - '0') * m;
if (k >= NSEC_INFINITY - r) if (k >= NSEC_INFINITY - nsec)
return -ERANGE; return -ERANGE;
r += k; nsec += k;
} }
/* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */ /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
@ -1270,7 +1328,7 @@ int parse_nsec(const char *t, nsec_t *nsec) {
} }
} }
*nsec = r; *ret = nsec;
return 0; return 0;
} }
@ -1321,6 +1379,8 @@ static int get_timezones_from_tzdata_zi(char ***ret) {
_cleanup_strv_free_ char **zones = NULL; _cleanup_strv_free_ char **zones = NULL;
int r; int r;
assert(ret);
f = fopen("/usr/share/zoneinfo/tzdata.zi", "re"); f = fopen("/usr/share/zoneinfo/tzdata.zi", "re");
if (!f) if (!f)
return -errno; return -errno;
@ -1478,6 +1538,8 @@ int get_timezone(char **ret) {
char *z; char *z;
int r; int r;
assert(ret);
r = readlink_malloc("/etc/localtime", &t); r = readlink_malloc("/etc/localtime", &t);
if (r == -ENOENT) { if (r == -ENOENT) {
/* If the symlink does not exist, assume "UTC", like glibc does */ /* If the symlink does not exist, assume "UTC", like glibc does */
@ -1507,10 +1569,15 @@ int get_timezone(char **ret) {
} }
time_t mktime_or_timegm(struct tm *tm, bool utc) { time_t mktime_or_timegm(struct tm *tm, bool utc) {
assert(tm);
return utc ? timegm(tm) : mktime(tm); return utc ? timegm(tm) : mktime(tm);
} }
struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) { struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) {
assert(t);
assert(tm);
return utc ? gmtime_r(t, tm) : localtime_r(t, tm); return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
} }
@ -1610,14 +1677,14 @@ int time_change_fd(void) {
static const char* const timestamp_style_table[_TIMESTAMP_STYLE_MAX] = { static const char* const timestamp_style_table[_TIMESTAMP_STYLE_MAX] = {
[TIMESTAMP_PRETTY] = "pretty", [TIMESTAMP_PRETTY] = "pretty",
[TIMESTAMP_US] = "us", [TIMESTAMP_US] = "us",
[TIMESTAMP_UTC] = "utc", [TIMESTAMP_UTC] = "utc",
[TIMESTAMP_US_UTC] = "us+utc", [TIMESTAMP_US_UTC] = "us+utc",
[TIMESTAMP_UNIX] = "unix", [TIMESTAMP_UNIX] = "unix",
}; };
/* Use the macro for enum → string to allow for aliases */ /* Use the macro for enum → string to allow for aliases */
_DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle,); DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle);
/* For the string → enum mapping we use the generic implementation, but also support two aliases */ /* For the string → enum mapping we use the generic implementation, but also support two aliases */
TimestampStyle timestamp_style_from_string(const char *s) { TimestampStyle timestamp_style_from_string(const char *s) {

View File

@ -66,7 +66,6 @@ typedef enum TimestampStyle {
/* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this /* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this
* to 6. Let's rely on that. */ * to 6. Let's rely on that. */
#define FORMAT_TIMESTAMP_MAX (3U+1U+10U+1U+8U+1U+6U+1U+6U+1U) #define FORMAT_TIMESTAMP_MAX (3U+1U+10U+1U+8U+1U+6U+1U+6U+1U)
#define FORMAT_TIMESTAMP_WIDTH 28U /* when outputting, assume this width */
#define FORMAT_TIMESTAMP_RELATIVE_MAX 256U #define FORMAT_TIMESTAMP_RELATIVE_MAX 256U
#define FORMAT_TIMESPAN_MAX 64U #define FORMAT_TIMESPAN_MAX 64U
@ -147,15 +146,15 @@ static inline char* format_timestamp(char *buf, size_t l, usec_t t) {
#define FORMAT_TIMESTAMP_STYLE(t, style) \ #define FORMAT_TIMESTAMP_STYLE(t, style) \
format_timestamp_style((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t, style) format_timestamp_style((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t, style)
int parse_timestamp(const char *t, usec_t *usec); int parse_timestamp(const char *t, usec_t *ret);
int parse_sec(const char *t, usec_t *usec); int parse_sec(const char *t, usec_t *ret);
int parse_sec_fix_0(const char *t, usec_t *usec); int parse_sec_fix_0(const char *t, usec_t *ret);
int parse_sec_def_infinity(const char *t, usec_t *usec); int parse_sec_def_infinity(const char *t, usec_t *ret);
int parse_time(const char *t, usec_t *usec, usec_t default_unit); int parse_time(const char *t, usec_t *ret, usec_t default_unit);
int parse_nsec(const char *t, nsec_t *nsec); int parse_nsec(const char *t, nsec_t *ret);
int get_timezones(char ***l); int get_timezones(char ***ret);
int verify_timezone(const char *name, int log_level); int verify_timezone(const char *name, int log_level);
static inline bool timezone_is_valid(const char *name, int log_level) { static inline bool timezone_is_valid(const char *name, int log_level) {
return verify_timezone(name, log_level) >= 0; return verify_timezone(name, log_level) >= 0;
@ -165,7 +164,7 @@ bool clock_supported(clockid_t clock);
usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to); usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to);
int get_timezone(char **timezone); int get_timezone(char **ret);
time_t mktime_or_timegm(struct tm *tm, bool utc); 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); struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc);

View File

@ -1,6 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "dirent-util.h"
#include "env-util.h" #include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "random-util.h" #include "random-util.h"
#include "serialize.h" #include "serialize.h"
#include "string-util.h" #include "string-util.h"
@ -8,6 +11,8 @@
#include "tests.h" #include "tests.h"
#include "time-util.h" #include "time-util.h"
#define TRIAL 100u
TEST(parse_sec) { TEST(parse_sec) {
usec_t u; usec_t u;
@ -334,11 +339,11 @@ TEST(usec_sub_signed) {
} }
TEST(format_timestamp) { TEST(format_timestamp) {
for (unsigned i = 0; i < 100; i++) { for (unsigned i = 0; i < TRIAL; i++) {
char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)]; char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)];
usec_t x, y; usec_t x, y;
x = random_u64_range(2147483600 * USEC_PER_SEC) + 1; x = random_u64_range(USEC_TIMESTAMP_FORMATTABLE_MAX - USEC_PER_SEC) + USEC_PER_SEC;
assert_se(format_timestamp(buf, sizeof(buf), x)); assert_se(format_timestamp(buf, sizeof(buf), x));
log_debug("%s", buf); log_debug("%s", buf);
@ -384,20 +389,91 @@ TEST(format_timestamp) {
} }
} }
static void test_format_timestamp_impl(usec_t x) {
bool success;
const char *xx, *yy;
usec_t y;
xx = FORMAT_TIMESTAMP(x);
assert_se(xx);
assert_se(parse_timestamp(xx, &y) >= 0);
yy = FORMAT_TIMESTAMP(y);
assert_se(yy);
success = (x / USEC_PER_SEC == y / USEC_PER_SEC) && streq(xx, yy);
log_full(success ? LOG_DEBUG : LOG_ERR, "@" USEC_FMT " → %s → @" USEC_FMT " → %s", x, xx, y, yy);
assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC);
assert_se(streq(xx, yy));
}
static void test_format_timestamp_loop(void) {
test_format_timestamp_impl(USEC_PER_SEC);
for (unsigned i = 0; i < TRIAL; i++) {
usec_t x;
x = random_u64_range(USEC_TIMESTAMP_FORMATTABLE_MAX - USEC_PER_SEC) + USEC_PER_SEC;
test_format_timestamp_impl(x);
}
}
TEST(FORMAT_TIMESTAMP) { TEST(FORMAT_TIMESTAMP) {
for (unsigned i = 0; i < 100; i++) { test_format_timestamp_loop();
_cleanup_free_ char *buf; }
usec_t x, y;
x = random_u64_range(2147483600 * USEC_PER_SEC) + 1; static void test_format_timestamp_with_tz_one(const char *name1, const char *name2) {
_cleanup_free_ char *buf = NULL, *tz = NULL;
const char *name, *saved_tz;
/* strbuf() is to test the macro in an argument to a function call. */ if (name2)
assert_se(buf = strdup(FORMAT_TIMESTAMP(x))); assert_se(buf = path_join(name1, name2));
log_debug("%s", buf); name = buf ?: name1;
assert_se(parse_timestamp(buf, &y) >= 0);
assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC);
assert_se(streq(FORMAT_TIMESTAMP(x), buf)); if (!timezone_is_valid(name, LOG_DEBUG))
return;
log_info("/* %s(%s) */", __func__, name);
saved_tz = getenv("TZ");
assert_se(tz = strjoin(":", name));
assert_se(setenv("TZ", tz, 1) >= 0);
tzset();
log_debug("%s: tzname[0]=%s, tzname[1]=%s", tz, strempty(tzname[0]), strempty(tzname[1]));
test_format_timestamp_loop();
assert_se(set_unset_env("TZ", saved_tz, true) == 0);
tzset();
}
TEST(FORMAT_TIMESTAMP_with_tz) {
if (!slow_tests_enabled())
return (void) log_tests_skipped("slow tests are disabled");
_cleanup_closedir_ DIR *dir = opendir("/usr/share/zoneinfo");
if (!dir)
return (void) log_tests_skipped_errno(errno, "Failed to open /usr/share/zoneinfo");
FOREACH_DIRENT(de, dir, break) {
if (de->d_type == DT_REG)
test_format_timestamp_with_tz_one(de->d_name, NULL);
else if (de->d_type == DT_DIR) {
if (streq(de->d_name, "right"))
/* The test does not support timezone with leap second info. */
continue;
_cleanup_closedir_ DIR *subdir = xopendirat(dirfd(dir), de->d_name, 0);
if (!subdir) {
log_notice_errno(errno, "Failed to open /usr/share/zoneinfo/%s, ignoring: %m", de->d_name);
continue;
}
FOREACH_DIRENT(subde, subdir, break)
if (subde->d_type == DT_REG)
test_format_timestamp_with_tz_one(de->d_name, subde->d_name);
}
} }
} }
@ -579,6 +655,219 @@ TEST(format_timestamp_range) {
test_format_timestamp_one(USEC_INFINITY, TIMESTAMP_UTC, NULL); test_format_timestamp_one(USEC_INFINITY, TIMESTAMP_UTC, NULL);
} }
static void test_parse_timestamp_one(const char *str, usec_t max_diff, usec_t expected) {
usec_t usec;
log_debug("/* %s(%s) */", __func__, str);
assert_se(parse_timestamp(str, &usec) >= 0);
assert_se(usec >= expected);
assert_se(usec_sub_unsigned(usec, expected) <= max_diff);
}
TEST(parse_timestamp) {
usec_t today, now_usec;
/* UTC */
test_parse_timestamp_one("Thu 1970-01-01 00:01 UTC", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Thu 1970-01-01 00:00:01 UTC", 0, USEC_PER_SEC);
test_parse_timestamp_one("Thu 1970-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Thu 1970-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Thu 70-01-01 00:01 UTC", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Thu 70-01-01 00:00:01 UTC", 0, USEC_PER_SEC);
test_parse_timestamp_one("Thu 70-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Thu 70-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1970-01-01 00:01 UTC", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("1970-01-01 00:00:01 UTC", 0, USEC_PER_SEC);
test_parse_timestamp_one("1970-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1970-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("70-01-01 00:01 UTC", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("70-01-01 00:00:01 UTC", 0, USEC_PER_SEC);
test_parse_timestamp_one("70-01-01 00:00:01.001 UTC", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("70-01-01 00:00:01.0010 UTC", 0, USEC_PER_SEC + 1000);
if (timezone_is_valid("Asia/Tokyo", LOG_DEBUG)) {
/* Asia/Tokyo (+0900) */
test_parse_timestamp_one("Thu 1970-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Thu 1970-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC);
test_parse_timestamp_one("Thu 1970-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Thu 1970-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Thu 70-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Thu 70-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC);
test_parse_timestamp_one("Thu 70-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Thu 70-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1970-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("1970-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC);
test_parse_timestamp_one("1970-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1970-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("70-01-01 09:01 Asia/Tokyo", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("70-01-01 09:00:01 Asia/Tokyo", 0, USEC_PER_SEC);
test_parse_timestamp_one("70-01-01 09:00:01.001 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("70-01-01 09:00:01.0010 Asia/Tokyo", 0, USEC_PER_SEC + 1000);
const char *saved_tz = getenv("TZ");
assert_se(setenv("TZ", ":Asia/Tokyo", 1) >= 0);
/* JST (+0900) */
test_parse_timestamp_one("Thu 1970-01-01 09:01 JST", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Thu 1970-01-01 09:00:01 JST", 0, USEC_PER_SEC);
test_parse_timestamp_one("Thu 1970-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Thu 1970-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Thu 70-01-01 09:01 JST", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Thu 70-01-01 09:00:01 JST", 0, USEC_PER_SEC);
test_parse_timestamp_one("Thu 70-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Thu 70-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1970-01-01 09:01 JST", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("1970-01-01 09:00:01 JST", 0, USEC_PER_SEC);
test_parse_timestamp_one("1970-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1970-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("70-01-01 09:01 JST", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("70-01-01 09:00:01 JST", 0, USEC_PER_SEC);
test_parse_timestamp_one("70-01-01 09:00:01.001 JST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("70-01-01 09:00:01.0010 JST", 0, USEC_PER_SEC + 1000);
assert_se(set_unset_env("TZ", saved_tz, true) == 0);
}
if (timezone_is_valid("America/New_York", LOG_DEBUG)) {
/* America/New_York (-0500) */
test_parse_timestamp_one("Wed 1969-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 1969-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC);
test_parse_timestamp_one("Wed 1969-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 1969-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 69-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 69-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC);
test_parse_timestamp_one("Wed 69-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 69-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1969-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("1969-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC);
test_parse_timestamp_one("1969-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1969-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 19:01 America/New_York", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("69-12-31 19:00:01 America/New_York", 0, USEC_PER_SEC);
test_parse_timestamp_one("69-12-31 19:00:01.001 America/New_York", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 19:00:01.0010 America/New_York", 0, USEC_PER_SEC + 1000);
const char *saved_tz = getenv("TZ");
assert_se(setenv("TZ", ":America/New_York", 1) >= 0);
/* EST (-0500) */
test_parse_timestamp_one("Wed 1969-12-31 19:01 EST", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 1969-12-31 19:00:01 EST", 0, USEC_PER_SEC);
test_parse_timestamp_one("Wed 1969-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 1969-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 69-12-31 19:01 EST", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 69-12-31 19:00:01 EST", 0, USEC_PER_SEC);
test_parse_timestamp_one("Wed 69-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 69-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1969-12-31 19:01 EST", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("1969-12-31 19:00:01 EST", 0, USEC_PER_SEC);
test_parse_timestamp_one("1969-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1969-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 19:01 EST", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("69-12-31 19:00:01 EST", 0, USEC_PER_SEC);
test_parse_timestamp_one("69-12-31 19:00:01.001 EST", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 19:00:01.0010 EST", 0, USEC_PER_SEC + 1000);
assert_se(set_unset_env("TZ", saved_tz, true) == 0);
}
/* -06 */
test_parse_timestamp_one("Wed 1969-12-31 18:01 -06", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -06", 0, USEC_PER_SEC);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 69-12-31 18:01 -06", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 69-12-31 18:00:01 -06", 0, USEC_PER_SEC);
test_parse_timestamp_one("Wed 69-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 69-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1969-12-31 18:01 -06", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("1969-12-31 18:00:01 -06", 0, USEC_PER_SEC);
test_parse_timestamp_one("1969-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1969-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 18:01 -06", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("69-12-31 18:00:01 -06", 0, USEC_PER_SEC);
test_parse_timestamp_one("69-12-31 18:00:01.001 -06", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 18:00:01.0010 -06", 0, USEC_PER_SEC + 1000);
/* -0600 */
test_parse_timestamp_one("Wed 1969-12-31 18:01 -0600", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -0600", 0, USEC_PER_SEC);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 69-12-31 18:01 -0600", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 69-12-31 18:00:01 -0600", 0, USEC_PER_SEC);
test_parse_timestamp_one("Wed 69-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 69-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1969-12-31 18:01 -0600", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("1969-12-31 18:00:01 -0600", 0, USEC_PER_SEC);
test_parse_timestamp_one("1969-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1969-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 18:01 -0600", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("69-12-31 18:00:01 -0600", 0, USEC_PER_SEC);
test_parse_timestamp_one("69-12-31 18:00:01.001 -0600", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 18:00:01.0010 -0600", 0, USEC_PER_SEC + 1000);
/* -06:00 */
test_parse_timestamp_one("Wed 1969-12-31 18:01 -06:00", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01 -06:00", 0, USEC_PER_SEC);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 1969-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 69-12-31 18:01 -06:00", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("Wed 69-12-31 18:00:01 -06:00", 0, USEC_PER_SEC);
test_parse_timestamp_one("Wed 69-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("Wed 69-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1969-12-31 18:01 -06:00", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("1969-12-31 18:00:01 -06:00", 0, USEC_PER_SEC);
test_parse_timestamp_one("1969-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("1969-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 18:01 -06:00", 0, USEC_PER_MINUTE);
test_parse_timestamp_one("69-12-31 18:00:01 -06:00", 0, USEC_PER_SEC);
test_parse_timestamp_one("69-12-31 18:00:01.001 -06:00", 0, USEC_PER_SEC + 1000);
test_parse_timestamp_one("69-12-31 18:00:01.0010 -06:00", 0, USEC_PER_SEC + 1000);
/* without date */
assert_se(parse_timestamp("today", &today) == 0);
test_parse_timestamp_one("00:01", 0, today + USEC_PER_MINUTE);
test_parse_timestamp_one("00:00:01", 0, today + USEC_PER_SEC);
test_parse_timestamp_one("00:00:01.001", 0, today + USEC_PER_SEC + 1000);
test_parse_timestamp_one("00:00:01.0010", 0, today + USEC_PER_SEC + 1000);
test_parse_timestamp_one("tomorrow", 0, today + USEC_PER_DAY);
test_parse_timestamp_one("yesterday", 0, today - USEC_PER_DAY);
/* relative */
assert_se(parse_timestamp("now", &now_usec) == 0);
test_parse_timestamp_one("+5hours", USEC_PER_MINUTE, now_usec + 5 * USEC_PER_HOUR);
if (now_usec >= 10 * USEC_PER_DAY)
test_parse_timestamp_one("-10days", USEC_PER_MINUTE, now_usec - 10 * USEC_PER_DAY);
test_parse_timestamp_one("2weeks left", USEC_PER_MINUTE, now_usec + 2 * USEC_PER_WEEK);
if (now_usec >= 30 * USEC_PER_MINUTE)
test_parse_timestamp_one("30minutes ago", USEC_PER_MINUTE, now_usec - 30 * USEC_PER_MINUTE);
}
TEST(deserialize_dual_timestamp) { TEST(deserialize_dual_timestamp) {
int r; int r;
dual_timestamp t; dual_timestamp t;
@ -702,6 +991,71 @@ TEST(map_clock_usec) {
} }
} }
static void test_timezone_offset_change_one(const char *utc, const char *pretty) {
usec_t x, y, z;
char *s;
assert_se(parse_timestamp(utc, &x) >= 0);
s = FORMAT_TIMESTAMP_STYLE(x, TIMESTAMP_UTC);
assert_se(parse_timestamp(s, &y) >= 0);
log_debug("%s -> " USEC_FMT " -> %s -> " USEC_FMT, utc, x, s, y);
assert_se(streq(s, utc));
assert_se(x == y);
assert_se(parse_timestamp(pretty, &y) >= 0);
s = FORMAT_TIMESTAMP_STYLE(y, TIMESTAMP_PRETTY);
assert_se(parse_timestamp(s, &z) >= 0);
log_debug("%s -> " USEC_FMT " -> %s -> " USEC_FMT, pretty, y, s, z);
assert_se(streq(s, pretty));
assert_se(x == y);
assert_se(x == z);
}
TEST(timezone_offset_change) {
const char *tz = getenv("TZ");
/* See issue #26370. */
if (timezone_is_valid("Africa/Casablanca", LOG_DEBUG)) {
assert_se(setenv("TZ", ":Africa/Casablanca", 1) >= 0);
tzset();
log_debug("Africa/Casablanca: tzname[0]=%s, tzname[1]=%s", strempty(tzname[0]), strempty(tzname[1]));
test_timezone_offset_change_one("Sun 2015-10-25 01:59:59 UTC", "Sun 2015-10-25 02:59:59 +01");
test_timezone_offset_change_one("Sun 2015-10-25 02:00:00 UTC", "Sun 2015-10-25 02:00:00 +00");
test_timezone_offset_change_one("Sun 2018-06-17 01:59:59 UTC", "Sun 2018-06-17 01:59:59 +00");
test_timezone_offset_change_one("Sun 2018-06-17 02:00:00 UTC", "Sun 2018-06-17 03:00:00 +01");
test_timezone_offset_change_one("Sun 2018-10-28 01:59:59 UTC", "Sun 2018-10-28 02:59:59 +01");
test_timezone_offset_change_one("Sun 2018-10-28 02:00:00 UTC", "Sun 2018-10-28 03:00:00 +01");
}
if (timezone_is_valid("Asia/Atyrau", LOG_DEBUG)) {
assert_se(setenv("TZ", ":Asia/Atyrau", 1) >= 0);
tzset();
log_debug("Asia/Atyrau: tzname[0]=%s, tzname[1]=%s", strempty(tzname[0]), strempty(tzname[1]));
test_timezone_offset_change_one("Sat 2004-03-27 21:59:59 UTC", "Sun 2004-03-28 01:59:59 +04");
test_timezone_offset_change_one("Sat 2004-03-27 22:00:00 UTC", "Sun 2004-03-28 03:00:00 +05");
test_timezone_offset_change_one("Sat 2004-10-30 21:59:59 UTC", "Sun 2004-10-31 02:59:59 +05");
test_timezone_offset_change_one("Sat 2004-10-30 22:00:00 UTC", "Sun 2004-10-31 03:00:00 +05");
}
if (timezone_is_valid("Chile/EasterIsland", LOG_DEBUG)) {
assert_se(setenv("TZ", ":Chile/EasterIsland", 1) >= 0);
tzset();
log_debug("Chile/EasterIsland: tzname[0]=%s, tzname[1]=%s", strempty(tzname[0]), strempty(tzname[1]));
test_timezone_offset_change_one("Sun 1981-10-11 03:59:59 UTC", "Sat 1981-10-10 20:59:59 -07");
test_timezone_offset_change_one("Sun 1981-10-11 04:00:00 UTC", "Sat 1981-10-10 22:00:00 -06");
test_timezone_offset_change_one("Sun 1982-03-14 02:59:59 UTC", "Sat 1982-03-13 20:59:59 -06");
test_timezone_offset_change_one("Sun 1982-03-14 03:00:00 UTC", "Sat 1982-03-13 21:00:00 -06");
}
assert_se(set_unset_env("TZ", tz, true) == 0);
tzset();
}
static int intro(void) { static int intro(void) {
log_info("realtime=" USEC_FMT "\n" log_info("realtime=" USEC_FMT "\n"
"monotonic=" USEC_FMT "\n" "monotonic=" USEC_FMT "\n"