/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "dirent-util.h"
#include "env-util.h"
+#include "fd-util.h"
+#include "fileio.h"
#include "random-util.h"
#include "serialize.h"
#include "string-util.h"
#include "tests.h"
#include "time-util.h"
+#define TRIAL 100u
+
TEST(parse_sec) {
usec_t u;
}
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)];
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));
log_debug("%s", buf);
}
}
+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) {
- for (unsigned i = 0; i < 100; i++) {
- _cleanup_free_ char *buf;
- usec_t x, y;
+ test_format_timestamp_loop();
+}
- 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. */
- assert_se(buf = strdup(FORMAT_TIMESTAMP(x)));
- log_debug("%s", buf);
- assert_se(parse_timestamp(buf, &y) >= 0);
- assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC);
+ if (name2)
+ assert_se(buf = path_join(name1, name2));
+ name = buf ?: name1;
+
+ 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;
- assert_se(streq(FORMAT_TIMESTAMP(x), buf));
+ _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);
+ }
}
}
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) {
int r;
dual_timestamp t;
}
}
+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) {
log_info("realtime=" USEC_FMT "\n"
"monotonic=" USEC_FMT "\n"