mirror of
https://github.com/systemd/systemd
synced 2024-07-21 18:24:38 +00:00
Merge pull request #26649 from yuwata/parse-timstamp
test: fix and extend test for parse_timestamp()
This commit is contained in:
commit
4ed17fb88c
|
@ -612,7 +612,7 @@ char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
|
|||
|
||||
static int parse_timestamp_impl(
|
||||
const char *t,
|
||||
bool with_tz,
|
||||
size_t tz_offset,
|
||||
bool utc,
|
||||
int isdst,
|
||||
long gmtoff,
|
||||
|
@ -638,7 +638,9 @@ static int parse_timestamp_impl(
|
|||
{ "Sat", 6 },
|
||||
};
|
||||
|
||||
_cleanup_free_ char *t_alloc = NULL;
|
||||
usec_t usec, plus = 0, minus = 0;
|
||||
bool with_tz = false;
|
||||
int r, weekday = -1;
|
||||
unsigned fractional = 0;
|
||||
const char *k;
|
||||
|
@ -663,6 +665,29 @@ static int parse_timestamp_impl(
|
|||
|
||||
assert(t);
|
||||
|
||||
if (tz_offset != SIZE_MAX) {
|
||||
/* If the input string contains timezone, then cut it here. */
|
||||
|
||||
if (tz_offset <= 1) /* timezone must be after a space. */
|
||||
return -EINVAL;
|
||||
|
||||
t_alloc = strndup(t, tz_offset - 1);
|
||||
if (!t_alloc)
|
||||
return -ENOMEM;
|
||||
|
||||
t = t_alloc;
|
||||
with_tz = true;
|
||||
}
|
||||
|
||||
if (utc) {
|
||||
/* glibc accepts gmtoff more than 24 hours, but we refuse it. */
|
||||
if ((usec_t) labs(gmtoff) * USEC_PER_SEC > USEC_PER_DAY)
|
||||
return -EINVAL;
|
||||
} else {
|
||||
if (gmtoff != 0)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (t[0] == '@' && !with_tz)
|
||||
return parse_sec(t + 1, ret);
|
||||
|
||||
|
@ -841,7 +866,7 @@ from_tm:
|
|||
if (gmtoff < 0) {
|
||||
plus = -gmtoff * USEC_PER_SEC;
|
||||
|
||||
/* If gmtoff is negative, the string maye be too old to be parsed as UTC.
|
||||
/* If gmtoff is negative, the string may 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
|
||||
|
@ -880,22 +905,8 @@ finish:
|
|||
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;
|
||||
|
||||
static int parse_timestamp_maybe_with_tz(const char *t, size_t tz_offset, bool valid_tz, usec_t *ret) {
|
||||
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();
|
||||
|
||||
|
@ -903,19 +914,18 @@ static int parse_timestamp_maybe_with_tz(const char *t, size_t len, bool valid_t
|
|||
if (isempty(tzname[j]))
|
||||
continue;
|
||||
|
||||
if (!streq(t + len + 1, tzname[j]))
|
||||
if (!streq(t + tz_offset, 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);
|
||||
return parse_timestamp_impl(t, tz_offset, /* 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);
|
||||
/* If we know that the last word is a valid timezone (e.g. Asia/Tokyo), then simply drop the timezone
|
||||
* and parse the remaining string as a local time. If we know that the last word is not a timezone,
|
||||
* then assume that it is a part of the time and try to parse the whole string as a local time. */
|
||||
return parse_timestamp_impl(t, valid_tz ? tz_offset : SIZE_MAX,
|
||||
/* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
|
||||
}
|
||||
|
||||
typedef struct ParseTimestampResult {
|
||||
|
@ -925,45 +935,41 @@ typedef struct ParseTimestampResult {
|
|||
|
||||
int parse_timestamp(const char *t, usec_t *ret) {
|
||||
ParseTimestampResult *shared, tmp;
|
||||
const char *k, *tz, *space;
|
||||
const char *k, *tz, *current_tz;
|
||||
size_t tz_offset;
|
||||
struct tm tm;
|
||||
int r;
|
||||
|
||||
assert(t);
|
||||
|
||||
space = strrchr(t, ' ');
|
||||
if (!space)
|
||||
return parse_timestamp_impl(t, /* with_tz = */ false, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
|
||||
tz = strrchr(t, ' ');
|
||||
if (!tz)
|
||||
return parse_timestamp_impl(t, /* tz_offset = */ SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
|
||||
|
||||
/* The string starts with space. */
|
||||
if (space == t)
|
||||
return -EINVAL;
|
||||
tz++;
|
||||
tz_offset = tz - t;
|
||||
|
||||
/* 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 (streq(tz, "UTC"))
|
||||
return parse_timestamp_impl(t, tz_offset, /* 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);
|
||||
}
|
||||
* UTC and shift the result. Note, this must be earlier than the timezone check with tzname[], as
|
||||
* tzname[] may be in the same format. */
|
||||
k = strptime(tz, "%z", &tm);
|
||||
if (k && *k == '\0')
|
||||
return parse_timestamp_impl(t, tz_offset, /* 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);
|
||||
if (!timezone_is_valid(tz, LOG_DEBUG))
|
||||
return parse_timestamp_maybe_with_tz(t, tz_offset, /* 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);
|
||||
current_tz = getenv("TZ");
|
||||
if (current_tz && *current_tz == ':' && streq(current_tz + 1, tz))
|
||||
return parse_timestamp_maybe_with_tz(t, tz_offset, /* 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. */
|
||||
|
@ -981,7 +987,7 @@ int parse_timestamp(const char *t, usec_t *ret) {
|
|||
const char *colon_tz;
|
||||
|
||||
/* tzset(3) says $TZ should be prefixed with ":" if we reference timezone files */
|
||||
colon_tz = strjoina(":", space + 1);
|
||||
colon_tz = strjoina(":", tz);
|
||||
|
||||
if (setenv("TZ", colon_tz, 1) != 0) {
|
||||
shared->return_value = negative_errno();
|
||||
|
@ -990,7 +996,7 @@ int parse_timestamp(const char *t, usec_t *ret) {
|
|||
|
||||
tzset();
|
||||
|
||||
shared->return_value = parse_timestamp_maybe_with_tz(t, space - t, /* valid_tz = */ true, &shared->usec);
|
||||
shared->return_value = parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ true, &shared->usec);
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
|
|
@ -156,7 +156,6 @@ simple_tests += files(
|
|||
'test-strxcpyx.c',
|
||||
'test-sysctl-util.c',
|
||||
'test-terminal-util.c',
|
||||
'test-time-util.c',
|
||||
'test-tmpfile-util.c',
|
||||
'test-tmpfiles.c',
|
||||
'test-tpm2.c',
|
||||
|
@ -501,6 +500,10 @@ tests += [
|
|||
udev_includes,
|
||||
],
|
||||
},
|
||||
{
|
||||
'sources' : files('test-time-util.c'),
|
||||
'timeout' : 120,
|
||||
},
|
||||
{
|
||||
'sources' : files('test-udev.c'),
|
||||
'link_with' : [
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "dirent-util.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
|
@ -421,23 +420,18 @@ TEST(FORMAT_TIMESTAMP) {
|
|||
test_format_timestamp_loop();
|
||||
}
|
||||
|
||||
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;
|
||||
static void test_format_timestamp_with_tz_one(const char *tz) {
|
||||
const char *saved_tz, *colon_tz;
|
||||
|
||||
if (name2)
|
||||
assert_se(buf = path_join(name1, name2));
|
||||
name = buf ?: name1;
|
||||
|
||||
if (!timezone_is_valid(name, LOG_DEBUG))
|
||||
if (!timezone_is_valid(tz, LOG_DEBUG))
|
||||
return;
|
||||
|
||||
log_info("/* %s(%s) */", __func__, name);
|
||||
log_info("/* %s(%s) */", __func__, tz);
|
||||
|
||||
saved_tz = getenv("TZ");
|
||||
|
||||
assert_se(tz = strjoin(":", name));
|
||||
assert_se(setenv("TZ", tz, 1) >= 0);
|
||||
assert_se(colon_tz = strjoina(":", tz));
|
||||
assert_se(setenv("TZ", colon_tz, 1) >= 0);
|
||||
tzset();
|
||||
log_debug("%s: tzname[0]=%s, tzname[1]=%s", tz, strempty(tzname[0]), strempty(tzname[1]));
|
||||
|
||||
|
@ -448,33 +442,11 @@ static void test_format_timestamp_with_tz_one(const char *name1, const char *nam
|
|||
}
|
||||
|
||||
TEST(FORMAT_TIMESTAMP_with_tz) {
|
||||
if (!slow_tests_enabled())
|
||||
return (void) log_tests_skipped("slow tests are disabled");
|
||||
_cleanup_strv_free_ char **timezones = NULL;
|
||||
|
||||
_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);
|
||||
}
|
||||
}
|
||||
assert_se(get_timezones(&timezones) >= 0);
|
||||
STRV_FOREACH(tz, timezones)
|
||||
test_format_timestamp_with_tz_one(*tz);
|
||||
}
|
||||
|
||||
TEST(format_timestamp_relative_full) {
|
||||
|
@ -656,15 +628,17 @@ TEST(format_timestamp_range) {
|
|||
}
|
||||
|
||||
static void test_parse_timestamp_one(const char *str, usec_t max_diff, usec_t expected) {
|
||||
usec_t usec;
|
||||
usec_t usec = USEC_INFINITY;
|
||||
int r;
|
||||
|
||||
log_debug("/* %s(%s) */", __func__, str);
|
||||
assert_se(parse_timestamp(str, &usec) >= 0);
|
||||
r = parse_timestamp(str, &usec);
|
||||
log_debug("/* %s(%s): max_diff="USEC_FMT", expected="USEC_FMT", result="USEC_FMT"*/", __func__, str, max_diff, expected, usec);
|
||||
assert_se(r >= 0);
|
||||
assert_se(usec >= expected);
|
||||
assert_se(usec_sub_unsigned(usec, expected) <= max_diff);
|
||||
}
|
||||
|
||||
TEST(parse_timestamp) {
|
||||
static void test_parse_timestamp_impl(const char *tz) {
|
||||
usec_t today, now_usec;
|
||||
|
||||
/* UTC */
|
||||
|
@ -709,10 +683,9 @@ TEST(parse_timestamp) {
|
|||
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);
|
||||
|
||||
if (streq_ptr(tz, "Asia/Tokyo")) {
|
||||
/* 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);
|
||||
|
@ -733,8 +706,6 @@ TEST(parse_timestamp) {
|
|||
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)) {
|
||||
|
@ -758,10 +729,9 @@ TEST(parse_timestamp) {
|
|||
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);
|
||||
|
||||
if (streq_ptr(tz, "America/New_York")) {
|
||||
/* 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);
|
||||
|
@ -782,8 +752,6 @@ TEST(parse_timestamp) {
|
|||
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 */
|
||||
|
@ -851,13 +819,12 @@ TEST(parse_timestamp) {
|
|||
|
||||
/* without date */
|
||||
assert_se(parse_timestamp("today", &today) == 0);
|
||||
// FIXME: currently failing, needs to investigate the changes from https://github.com/systemd/systemd/pull/26409
|
||||
/*test_parse_timestamp_one("00:01", 0, today + USEC_PER_MINUTE);
|
||||
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);*/
|
||||
test_parse_timestamp_one("yesterday", 0, today - USEC_PER_DAY);
|
||||
|
||||
/* relative */
|
||||
assert_se(parse_timestamp("now", &now_usec) == 0);
|
||||
|
@ -869,6 +836,39 @@ TEST(parse_timestamp) {
|
|||
test_parse_timestamp_one("30minutes ago", USEC_PER_MINUTE, now_usec - 30 * USEC_PER_MINUTE);
|
||||
}
|
||||
|
||||
TEST(parse_timestamp) {
|
||||
test_parse_timestamp_impl(NULL);
|
||||
}
|
||||
|
||||
static void test_parse_timestamp_with_tz_one(const char *tz) {
|
||||
const char *saved_tz, *colon_tz;
|
||||
|
||||
if (!timezone_is_valid(tz, LOG_DEBUG))
|
||||
return;
|
||||
|
||||
log_info("/* %s(%s) */", __func__, tz);
|
||||
|
||||
saved_tz = getenv("TZ");
|
||||
|
||||
assert_se(colon_tz = strjoina(":", tz));
|
||||
assert_se(setenv("TZ", colon_tz, 1) >= 0);
|
||||
tzset();
|
||||
log_debug("%s: tzname[0]=%s, tzname[1]=%s", tz, strempty(tzname[0]), strempty(tzname[1]));
|
||||
|
||||
test_parse_timestamp_impl(tz);
|
||||
|
||||
assert_se(set_unset_env("TZ", saved_tz, true) == 0);
|
||||
tzset();
|
||||
}
|
||||
|
||||
TEST(parse_timestamp_with_tz) {
|
||||
_cleanup_strv_free_ char **timezones = NULL;
|
||||
|
||||
assert_se(get_timezones(&timezones) >= 0);
|
||||
STRV_FOREACH(tz, timezones)
|
||||
test_parse_timestamp_with_tz_one(*tz);
|
||||
}
|
||||
|
||||
TEST(deserialize_dual_timestamp) {
|
||||
int r;
|
||||
dual_timestamp t;
|
||||
|
|
Loading…
Reference in a new issue