From a740b1d66c4131387c1ad19b818661b3f28537e2 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 26 Oct 2021 13:21:49 +0200 Subject: [PATCH] LegacyTimePeriod::ScriptFunc: fix DST edge-cases This change fixes two problems: * The internal functions used by ScriptFunc more or less expect to operate on full days, but ScriptFunc may have called them with some random timestamp during the day. This is fixed by always using midnight of the day as reference time. * Previously, the code advanced a timestamp to the next day by adding 24 hours. On days with DST changes, this could either still be on the same day (a day may have 25 hours) or skip an entire day (a day may have 23 hours). This is fixed by using a struct tm to advance the time to the next day. --- lib/icinga/legacytimeperiod.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/icinga/legacytimeperiod.cpp b/lib/icinga/legacytimeperiod.cpp index d8355110f..33e666544 100644 --- a/lib/icinga/legacytimeperiod.cpp +++ b/lib/icinga/legacytimeperiod.cpp @@ -586,13 +586,35 @@ Array::Ptr LegacyTimePeriod::ScriptFunc(const TimePeriod::Ptr& tp, double begin, Dictionary::Ptr ranges = tp->GetRanges(); if (ranges) { - for (int i = 0; i <= (end - begin) / (24 * 60 * 60); i++) { - time_t refts = begin + i * 24 * 60 * 60; - tm reference = Utility::LocalTime(refts); + tm tm_begin = Utility::LocalTime(begin); + + // Always evaluate time periods for full days as their ranges are given per day. + tm_begin.tm_hour = 0; + tm_begin.tm_min = 0; + tm_begin.tm_sec = 0; + tm_begin.tm_isdst = -1; + + // Helper to move a struct tm to midnight of the next day for the loop below. + // Due to DST changes, this may move the time by something else than 24 hours. + auto advance_to_next_day = [](tm *t) { + t->tm_mday++; + t->tm_hour = 0; + t->tm_min = 0; + t->tm_sec = 0; + t->tm_isdst = -1; + + // Normalize fields using mktime. + mktime(t); + + // Reset tm_isdst so that future calls figure out the correct time zone after setting tm_hour/tm_min/tm_sec. + t->tm_isdst = -1; + }; + + for (tm reference = tm_begin; mktime_const(&reference) <= end; advance_to_next_day(&reference)) { #ifdef I2_DEBUG Log(LogDebug, "LegacyTimePeriod") - << "Checking reference time " << refts; + << "Checking reference time " << mktime_const(&reference); #endif /* I2_DEBUG */ ObjectLock olock(ranges);