diff options
author | Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> | 2019-10-29 22:14:39 +0100 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2019-10-30 17:57:01 +0100 |
commit | be75c86dc60a23fbea33d1674197d049f605c966 (patch) | |
tree | 739249fe7a95939c0dd30c66439ae14365b317e0 | |
parent | b7db8b7b139fa6cfb6516197d5ac8d10360ac57a (diff) | |
download | systemd-be75c86dc60a23fbea33d1674197d049f605c966.tar.gz |
calendarspec: fix calculation of timespec iterations that fall onto a DST change
If we tested a candidate time that would fall onto the DST change, and we
realized that it is now a valid time ('cause the given "hour" is missing),
we would jump to to beginning of the next bigger time period, i.e. the next
day.
mktime_or_timegm() already tells us what the next valid time is, so let's reuse
this, and continue the calculations at this point. This should allow us to
correctly jump over DST changes, but also leap seconds and similar. It should
be OK even multiple days were removed from calendar, similarly to the
Gregorian-Julian transition. By reusing the information from normalization, we
don't have to make assumptions what the next valid time is.
Fixes #13745.
$ TZ=Australia/Sydney faketime '2019-10-06 01:50' build/systemd-analyze calendar 0/1:0/1 --iterations 20 | grep Iter
Iter. #2: Sun 2019-10-06 01:52:00 AEST
Iter. #3: Sun 2019-10-06 01:53:00 AEST
Iter. #4: Sun 2019-10-06 01:54:00 AEST
Iter. #5: Sun 2019-10-06 01:55:00 AEST
Iter. #6: Sun 2019-10-06 01:56:00 AEST
Iter. #7: Sun 2019-10-06 01:57:00 AEST
Iter. #8: Sun 2019-10-06 01:58:00 AEST
Iter. #9: Sun 2019-10-06 01:59:00 AEST
Iter. #10: Sun 2019-10-06 03:00:00 AEDT
Iter. #11: Sun 2019-10-06 03:01:00 AEDT
Iter. #12: Sun 2019-10-06 03:02:00 AEDT
Iter. #13: Sun 2019-10-06 03:03:00 AEDT
Iter. #14: Sun 2019-10-06 03:04:00 AEDT
Iter. #15: Sun 2019-10-06 03:05:00 AEDT
Iter. #16: Sun 2019-10-06 03:06:00 AEDT
Iter. #17: Sun 2019-10-06 03:07:00 AEDT
Iter. #18: Sun 2019-10-06 03:08:00 AEDT
Iter. #19: Sun 2019-10-06 03:09:00 AEDT
Iter. #20: Sun 2019-10-06 03:10:00 AEDT
$ TZ=Australia/Sydney faketime 2019-10-06 build/systemd-analyze calendar 2/4:30 --iterations=3
Original form: 2/4:30
Normalized form: *-*-* 02/4:30:00
Next elapse: Sun 2019-10-06 06:30:00 AEDT
(in UTC): Sat 2019-10-05 19:30:00 UTC
From now: 5h 29min left
Iter. #2: Sun 2019-10-06 10:30:00 AEDT
(in UTC): Sat 2019-10-05 23:30:00 UTC
From now: 9h left
Iter. #3: Sun 2019-10-06 14:30:00 AEDT
(in UTC): Sun 2019-10-06 03:30:00 UTC
From now: 13h left
-rw-r--r-- | src/shared/calendarspec.c | 54 |
1 files changed, 34 insertions, 20 deletions
diff --git a/src/shared/calendarspec.c b/src/shared/calendarspec.c index 1045b00929..9b1582da99 100644 --- a/src/shared/calendarspec.c +++ b/src/shared/calendarspec.c @@ -1146,31 +1146,32 @@ static int find_matching_component(const CalendarSpec *spec, const CalendarCompo return r; } -static bool tm_out_of_bounds(const struct tm *tm, bool utc) { +static int tm_within_bounds(struct tm *tm, bool utc) { struct tm t; assert(tm); - t = *tm; - - if (mktime_or_timegm(&t, utc) < 0) - return true; - /* * Set an upper bound on the year so impossible dates like "*-02-31" * don't cause find_next() to loop forever. tm_year contains years * since 1900, so adjust it accordingly. */ if (tm->tm_year + 1900 > MAX_YEAR) - return true; + return -ERANGE; + + t = *tm; + if (mktime_or_timegm(&t, utc) < 0) + return negative_errno(); /* Did any normalization take place? If so, it was out of bounds before */ - return - t.tm_year != tm->tm_year || - t.tm_mon != tm->tm_mon || - t.tm_mday != tm->tm_mday || - t.tm_hour != tm->tm_hour || - t.tm_min != tm->tm_min || - t.tm_sec != tm->tm_sec; + bool good = t.tm_year == tm->tm_year && + t.tm_mon == tm->tm_mon && + t.tm_mday == tm->tm_mday && + t.tm_hour == tm->tm_hour && + t.tm_min == tm->tm_min && + t.tm_sec == tm->tm_sec; + if (!good) + *tm = t; + return good; } static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { @@ -1217,7 +1218,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { } if (r < 0) return r; - if (tm_out_of_bounds(&c, spec->utc)) + if (tm_within_bounds(&c, spec->utc) <= 0) return -ENOENT; c.tm_mon += 1; @@ -1228,23 +1229,27 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; } - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) { c.tm_year++; c.tm_mon = 0; c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; continue; } + if (r == 0) + continue; r = find_matching_component(spec, spec->day, &c, &c.tm_mday); if (r > 0) c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) { c.tm_mon++; c.tm_mday = 1; c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; continue; } + if (r == 0) + continue; if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) { c.tm_mday++; @@ -1255,31 +1260,40 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { r = find_matching_component(spec, spec->hour, &c, &c.tm_hour); if (r > 0) c.tm_min = c.tm_sec = tm_usec = 0; - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) { c.tm_mday++; c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; continue; } + if (r == 0) + /* The next hour we set might be missing if there + * are time zone changes. Let's try again starting at + * normalized time. */ + continue; r = find_matching_component(spec, spec->minute, &c, &c.tm_min); if (r > 0) c.tm_sec = tm_usec = 0; - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) { c.tm_hour++; c.tm_min = c.tm_sec = tm_usec = 0; continue; } + if (r == 0) + continue; c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec; r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec); tm_usec = c.tm_sec % USEC_PER_SEC; c.tm_sec /= USEC_PER_SEC; - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + if (r < 0 || (r = tm_within_bounds(&c, spec->utc)) < 0) { c.tm_min++; c.tm_sec = tm_usec = 0; continue; } + if (r == 0) + continue; *tm = c; *usec = tm_usec; |