diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index 5fdae235366..01bc10f97ec 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -1235,14 +1235,43 @@ static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { return (weekdays_bits & (1 << k)); } +static int tm_compare(const struct tm *t1, const struct tm *t2) { + int r; + + assert(t1); + assert(t2); + + r = CMP(t1->tm_year, t2->tm_year); + if (r != 0) + return r; + + r = CMP(t1->tm_mon, t2->tm_mon); + if (r != 0) + return r; + + r = CMP(t1->tm_mday, t2->tm_mday); + if (r != 0) + return r; + + r = CMP(t1->tm_hour, t2->tm_hour); + if (r != 0) + return r; + + r = CMP(t1->tm_min, t2->tm_min); + if (r != 0) + return r; + + return CMP(t1->tm_sec, t2->tm_sec); +} + /* A safety valve: if we get stuck in the calculation, return an error. * C.f. https://bugzilla.redhat.com/show_bug.cgi?id=1941335. */ #define MAX_CALENDAR_ITERATIONS 1000 static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { struct tm c; - int tm_usec; - int r; + int tm_usec, r; + bool invalidate_dst = false; /* Returns -ENOENT if the expression is not going to elapse anymore */ @@ -1255,7 +1284,8 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { for (unsigned iteration = 0; iteration < MAX_CALENDAR_ITERATIONS; iteration++) { /* Normalize the current date */ (void) mktime_or_timegm_usec(&c, spec->utc, /* ret= */ NULL); - c.tm_isdst = spec->dst; + if (!invalidate_dst) + c.tm_isdst = spec->dst; c.tm_year += 1900; r = find_matching_component(spec, spec->year, &c, &c.tm_year); @@ -1345,6 +1375,18 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { if (r == 0) continue; + r = tm_compare(tm, &c); + if (r == 0) { + assert(tm_usec + 1 <= 1000000); + r = CMP(*usec, (usec_t) tm_usec + 1); + } + if (r >= 0) { + /* We're stuck - advance, let mktime determine DST transition and try again. */ + invalidate_dst = true; + c.tm_hour++; + continue; + } + *tm = c; *usec = tm_usec; return 0; diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c index 005d9b0771b..e8c50ead9f7 100644 --- a/src/test/test-calendarspec.c +++ b/src/test/test-calendarspec.c @@ -47,7 +47,7 @@ static void _test_next(int line, const char *input, const char *new_tz, usec_t a if (old_tz) old_tz = strdupa_safe(old_tz); - if (!isempty(new_tz)) + if (!isempty(new_tz) && !strchr(new_tz, ',')) new_tz = strjoina(":", new_tz); assert_se(set_unset_env("TZ", new_tz, true) == 0); @@ -219,6 +219,8 @@ TEST(calendar_spec_next) { /* Check that we don't start looping if mktime() moves us backwards */ test_next("Sun *-*-* 01:00:00 Europe/Dublin", "", 1616412478000000, 1617494400000000); test_next("Sun *-*-* 01:00:00 Europe/Dublin", "IST", 1616412478000000, 1617494400000000); + /* Europe/Dublin TZ that moves DST backwards */ + test_next("hourly", "IST-1GMT-0,M10.5.0/1,M3.5.0/1", 1743292800000000, 1743296400000000); } TEST(calendar_spec_from_string) {