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
-static bool tm_out_of_bounds(const struct tm *tm, bool utc) {
+static int tm_within_bounds(struct tm *tm, bool utc) {
- 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)
/*
* 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 -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 */
/* 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) {
}
static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
- if (tm_out_of_bounds(&c, spec->utc))
+ if (tm_within_bounds(&c, spec->utc) <= 0)
return -ENOENT;
c.tm_mon += 1;
return -ENOENT;
c.tm_mon += 1;
c.tm_mday = 1;
c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
}
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;
}
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;
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;
}
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++;
if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) {
c.tm_mday++;
r = find_matching_component(spec, spec->hour, &c, &c.tm_hour);
if (r > 0)
c.tm_min = c.tm_sec = tm_usec = 0;
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;
}
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;
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;
}
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;
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;
}
c.tm_min++;
c.tm_sec = tm_usec = 0;
continue;
}
+ if (r == 0)
+ continue;
*tm = c;
*usec = tm_usec;
*tm = c;
*usec = tm_usec;