summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2019-10-29 22:14:39 +0100
committerLennart Poettering <lennart@poettering.net>2019-10-30 17:57:01 +0100
commitbe75c86dc60a23fbea33d1674197d049f605c966 (patch)
tree739249fe7a95939c0dd30c66439ae14365b317e0
parentb7db8b7b139fa6cfb6516197d5ac8d10360ac57a (diff)
downloadsystemd-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.c54
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;