mirror of
https://github.com/morgan9e/systemd
synced 2026-04-15 00:47:10 +09:00
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
This commit is contained in:
committed by
Lennart Poettering
parent
b7db8b7b13
commit
be75c86dc6
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user