diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 7d44358cd8..657e231b0d 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -662,6 +662,9 @@ static int parse_timestamp_impl( * +5min * -5days * @2147483647 (seconds since epoch) + * + * Note, on DST change, 00:00:00 may not exist and in that case the time part may be shifted. + * E.g. "Sun 2023-03-13 America/Havana" is parsed as "Sun 2023-03-13 01:00:00 CDT". */ assert(t); diff --git a/src/basic/time-util.h b/src/basic/time-util.h index 087f5324ef..79a6715656 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -205,13 +205,17 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) { return usec_sub_unsigned(timestamp, (usec_t) delta); } +/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit + * year territory. However, since we want to stay away from this in all timezones we take one day off. */ +#define USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT ((usec_t) 253402214399000000) /* Thu 9999-12-30 23:59:59 UTC */ +/* With a 32bit time_t we can't go beyond 2038... + * We parse timestamp with RFC-822/ISO 8601 (e.g. +06, or -03:00) as UTC, hence the upper bound must be off + * by USEC_PER_DAY. See parse_timestamp() for more details. */ +#define USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT (((usec_t) INT32_MAX) * USEC_PER_SEC - USEC_PER_DAY) #if SIZEOF_TIME_T == 8 - /* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit - * year territory. However, since we want to stay away from this in all timezones we take one day off. */ -# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000) +# define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT #elif SIZEOF_TIME_T == 4 -/* With a 32bit time_t we can't go beyond 2038... */ -# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000) +# define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT #else # error "Yuck, time_t is neither 4 nor 8 bytes wide?" #endif diff --git a/src/test/test-date.c b/src/test/test-date.c index 097066b61a..16c6d4e5be 100644 --- a/src/test/test-date.c +++ b/src/test/test-date.c @@ -101,8 +101,8 @@ int main(int argc, char *argv[]) { test_should_fail("9999-12-31 00:00:00 UTC"); test_should_fail("10000-01-01 00:00:00 UTC"); #elif SIZEOF_TIME_T == 4 - test_should_pass("2038-01-19 03:14:07 UTC"); - test_should_fail("2038-01-19 03:14:08 UTC"); + test_should_pass("2038-01-18 03:14:07 UTC"); + test_should_fail("2038-01-18 03:14:08 UTC"); #endif return 0; diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index e974520bbe..e0386d4ec5 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -407,6 +407,10 @@ static void test_format_timestamp_impl(usec_t x) { static void test_format_timestamp_loop(void) { test_format_timestamp_impl(USEC_PER_SEC); + test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT-1); + test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT); + test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX-1); + test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX); for (unsigned i = 0; i < TRIAL; i++) { usec_t x; @@ -617,8 +621,8 @@ TEST(format_timestamp_range) { test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_US_UTC, "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC"); test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_DATE, "--- XXXX-XX-XX"); #elif SIZEOF_TIME_T == 4 - test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_UTC, "Tue 2038-01-19 03:14:07 UTC"); - test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_DATE, "Tue 2038-01-19"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_UTC, "Mon 2038-01-18 03:14:07 UTC"); + test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX, TIMESTAMP_DATE, "Mon 2038-01-18"); test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_UTC, "--- XXXX-XX-XX XX:XX:XX UTC"); test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_US_UTC, "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC"); test_format_timestamp_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, TIMESTAMP_DATE, "--- XXXX-XX-XX"); @@ -632,12 +636,30 @@ static void test_parse_timestamp_one(const char *str, usec_t max_diff, usec_t ex int r; 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); + 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); } +static bool time_is_zero(usec_t usec) { + const char *s; + + s = FORMAT_TIMESTAMP(usec); + return strstr(s, " 00:00:00 "); +} + +static bool timezone_equal(usec_t today, usec_t target) { + const char *s, *t, *sz, *tz; + + s = FORMAT_TIMESTAMP(today); + t = FORMAT_TIMESTAMP(target); + assert_se(sz = strrchr(s, ' ')); + assert_se(tz = strrchr(t, ' ')); + log_debug("%s("USEC_FMT", "USEC_FMT") -> %s, %s", __func__, today, target, s, t); + return streq(sz, tz); +} + static void test_parse_timestamp_impl(const char *tz) { usec_t today, now_usec; @@ -819,12 +841,17 @@ static void test_parse_timestamp_impl(const char *tz) { /* 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); + if (time_is_zero(today)) { + 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); + + if (timezone_equal(today, today + USEC_PER_DAY) && time_is_zero(today + USEC_PER_DAY)) + test_parse_timestamp_one("tomorrow", 0, today + USEC_PER_DAY); + if (timezone_equal(today, today - USEC_PER_DAY) && time_is_zero(today - USEC_PER_DAY)) + test_parse_timestamp_one("yesterday", 0, today - USEC_PER_DAY); + } /* relative */ assert_se(parse_timestamp("now", &now_usec) == 0);