test: ignore fails when the formatted timezone differs from the current one

When formatting a timestamp the C API takes into account historical data
from tzdata, so it returns a date strings with a historically-correct
timezone abbreviation. However, tzname[] doesn't do this and it returns
the most recent abbreviation for the given zone.

For example, according to tzdata America/Cancun switched from EST/EDT to
CST/CDT on 1998-08-02:

Zone America/Cancun     -5:47:04 -      LMT     1922 Jan  1  6:00u
                        -6:00   -       CST     1981 Dec 26  2:00
                        -5:00   -       EST     1983 Jan  4  0:00
                        -6:00   Mexico  C%sT    1997 Oct 26  2:00
                        -5:00   Mexico  E%sT    1998 Aug  2  2:00
                        -6:00   Mexico  C%sT    2015 Feb  1  2:00
                        -5:00   -       EST

So, formatting a timestamp from this time will yield a string with the
EDT timezone:

$ TZ=America/Cancun date -d "@902035565"
Sun Aug  2 01:26:05 EDT 1998

But using tzname[] (or strptime %z) shows the most recent data, where
America/Cancun uses EST (and doesn't use DST anymore, hence
tzname[1]=CDT that glibc remembers from the previous zone epoch):

$ TZ=America/Cancun ./tz
{EST, CDT}

This means that when we parse the formatted timestamp back we don't use
the historical timezone data, so we might end up with a different
offset:

TZ=America/Cancun, tzname[0]=EST, tzname[1]=CDT
@902035565603993 → Sun 1998-08-02 01:26:05 EDT → @902039165000000 → Sun 1998-08-02 01:26:05 CDT
src/test/test-time-util.c:452: Assertion failed: Expected "ignore" to be true
Aborted                    (core dumped) build-local/test-time-util

Instead of adding exceptions for every single timezone that switched
between different offsets in the past, let's address this a bit more
generally and skip the check if the parsed timezone doesn't match any of
the current timezones - this still keeps the check that the time
difference in such case is exactly one hour, so its effect should be
limited mostly to DST-related changes.

Resolves: #37684
This commit is contained in:
Frantisek Sumsal
2026-07-03 07:23:30 +09:00
committed by Yu Watanabe
parent 2d372e2a34
commit 7273d38335
+26 -13
View File
@@ -433,15 +433,27 @@ static void test_format_timestamp_impl(usec_t x) {
if (x_sec == y_sec && streq(xx, yy))
return; /* Yay! */
/* When the timezone is built with rearguard being enabled (e.g. old Ubuntu and RHEL), the timezone
* Africa/Windhoek may provide time shifted 1 hour from the original. See
* https://github.com/systemd/systemd/issues/28472 and https://github.com/systemd/systemd/pull/35471.
* Also, the same may happen on MSK timezone (e.g. Europe/Volgograd or Europe/Kirov), or on
* Africa/Tripoli (Libya) which switched between CET and EET multiple times historically, causing
* certain timestamps to round-trip with a 1h offset. */
/* There are two classes of known round-trip failures that produce exactly a 1h offset:
*
* 1) The formatted abbreviation doesn't match the current timezone's abbreviations - the timestamp
* is from a historical era (e.g. Africa/Tripoli switched between CET and EET multiple times
* historically, America/Cancun switched between EST/EDT and CST/CDT several times in the past,
* etc.), and the round-trip is inherently unreliable on platforms where parse_gmtoff() resolves
* such abbreviations with incorrect offsets.
*
* 2) Rearguard/vanguard database format differences where the abbreviation matches but the
* offset is still wrong (e.g. Africa/Windhoek, Europe/Kirov, Europe/Volgograd).
*
* See:
* - https://github.com/systemd/systemd/issues/28472
* - https://github.com/systemd/systemd/pull/35471
* - https://github.com/systemd/systemd/issues/37684
*/
bool ignore =
(STRPTR_IN_SET(getenv("TZ"), "Africa/Windhoek", "Africa/Tripoli", "Libya") ||
STRPTR_IN_SET(get_tzname(/* dst= */ false), "CAT", "EAT", "MSK", "WET")) &&
((!streq_ptr(tz, get_tzname(/* dst= */ false)) &&
!streq_ptr(tz, get_tzname(/* dst= */ true))) ||
streq_ptr(getenv("TZ"), "Africa/Windhoek") ||
STRPTR_IN_SET(get_tzname(/* dst= */ false), "MSK", "WET")) &&
(x_sec > y_sec ? x_sec - y_sec : y_sec - x_sec) == 3600;
log_full(ignore ? LOG_WARNING : LOG_ERR,
@@ -459,16 +471,17 @@ static void test_format_timestamp_loop(void) {
test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX-1);
test_format_timestamp_impl(USEC_TIMESTAMP_FORMATTABLE_MAX);
/* Two cases which trigger https://github.com/systemd/systemd/issues/28472 */
/* Specific timestamps known to cause a 1h round-trip discrepancy with certain timezones:
*
* Two cases which trigger https://github.com/systemd/systemd/issues/28472. */
test_format_timestamp_impl(1504938962980066);
test_format_timestamp_impl(1509482094632752);
/* With tzdata-2025c, the timestamp (randomly?) fails on MSK time zone (e.g. Europe/Volgograd). */
test_format_timestamp_impl(1414277092997572);
/* Africa/Tripoli (Libya) switched from CET to EET multiple times in the past, causing a 1h
* round-trip discrepancy for historical timestamps. */
/* Africa/Tripoli (Libya) switched from CET to EET multiple times in the past. */
test_format_timestamp_impl(378687574661411);
/* America/Cancun switched from EST/EDT to CST/CDT multiple times in the past. */
test_format_timestamp_impl(902035565603993);
for (unsigned i = 0; i < TRIAL; i++) {
usec_t x;