Merge pull request #9048 from Icinga/bugfix/timeperiod-dst-2.0

LegacyTimePeriod::ScriptFunc: fix DST edge-cases
This commit is contained in:
Alexander Aleksandrovič Klimov 2022-01-03 18:11:32 +01:00 committed by GitHub
commit 80663cf5e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 15 deletions

View File

@ -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);

View File

@ -141,6 +141,7 @@ add_boost_test(base
icinga_legacytimeperiod/simple
icinga_legacytimeperiod/advanced
icinga_legacytimeperiod/dst
icinga_legacytimeperiod/dst_isinside
icinga_perfdata/empty
icinga_perfdata/simple
icinga_perfdata/quotes

View File

@ -49,6 +49,18 @@ struct GlobalTimezoneFixture
BOOST_GLOBAL_FIXTURE(GlobalTimezoneFixture);
// DST changes in America/Los_Angeles:
// 2021-03-14: 01:59:59 PST (UTC-8) -> 03:00:00 PDT (UTC-7)
// 2021-11-07: 01:59:59 PDT (UTC-7) -> 01:00:00 PST (UTC-8)
#ifndef _WIN32
static const char *dst_test_timezone = "America/Los_Angeles";
#else /* _WIN32 */
// Tests are using pacific time because Windows only really supports timezones following US DST rules with the TZ
// environment variable. Format is "[Standard TZ][negative UTC offset][DST TZ]".
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset?view=msvc-160#remarks
static const char *dst_test_timezone = "PST8PDT";
#endif /* _WIN32 */
BOOST_AUTO_TEST_CASE(simple)
{
tm tm_beg, tm_end, tm_ref;
@ -356,17 +368,7 @@ std::ostream& operator<<(std::ostream& o, const boost::optional<Segment>& s)
BOOST_AUTO_TEST_CASE(dst)
{
// DST changes in America/Los_Angeles:
// 2021-03-14: 01:59:59 PST (UTC-8) -> 03:00:00 PDT (UTC-7)
// 2021-11-07: 01:59:59 PDT (UTC-7) -> 01:00:00 PST (UTC-8)
#ifndef _WIN32
GlobalTimezoneFixture tz("America/Los_Angeles");
#else /* _WIN32 */
// Tests are using pacific time because Windows only really supports timezones following US DST rules with the TZ
// environment variable. Format is "[Standard TZ][negative UTC offset][DST TZ]".
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset?view=msvc-160#remarks
GlobalTimezoneFixture tz("PST8PDT");
#endif /* _WIN32 */
GlobalTimezoneFixture tz(dst_test_timezone);
// Self-tests for helper functions
BOOST_CHECK_EQUAL(make_tm("2021-11-07 02:30:00").tm_isdst, -1);
@ -642,4 +644,51 @@ BOOST_AUTO_TEST_CASE(dst)
}
}
// This tests checks that TimePeriod::IsInside() always returns true for a 24x7 period, even around DST changes.
BOOST_AUTO_TEST_CASE(dst_isinside)
{
GlobalTimezoneFixture tz(dst_test_timezone);
Function::Ptr update = new Function("LegacyTimePeriod", LegacyTimePeriod::ScriptFunc, {"tp", "begin", "end"});
Dictionary::Ptr ranges = new Dictionary({
{"monday", "00:00-24:00"},
{"tuesday", "00:00-24:00"},
{"wednesday", "00:00-24:00"},
{"thursday", "00:00-24:00"},
{"friday", "00:00-24:00"},
{"saturday", "00:00-24:00"},
{"sunday", "00:00-24:00"},
});
// Vary begin from Sat 06 Nov 2021 00:00:00 PDT to Mon 08 Nov 2021 00:00:00 PST in 30 minute intervals.
for (time_t t_begin = 1636182000; t_begin <= 1636358400; t_begin += 30*60) {
// Test varying interval lengths: 60 minutes, 24 hours, 48 hours.
for (time_t len : {60*60, 24*60*60, 48*60*60}) {
time_t t_end = t_begin + len;
TimePeriod::Ptr p = new TimePeriod();
p->SetUpdate(update, true);
p->SetRanges(ranges, true);
p->UpdateRegion(double(t_begin), double(t_end), true);
{
// Print resulting segments for easier debugging.
Array::Ptr segments = p->GetSegments();
ObjectLock lock(segments);
for (Dictionary::Ptr segment: segments) {
BOOST_TEST_MESSAGE("t_begin=" << t_begin << " t_end=" << t_end
<< " segment.begin=" << segment->Get("begin") << " segment.end=" << segment->Get("end"));
}
}
time_t step = 10*60;
for (time_t t = t_begin+step; t < t_end; t += step) {
BOOST_CHECK_MESSAGE(p->IsInside(double(t)),
t << " should be inside for t_begin=" << t_begin << " t_end=" << t_end);
}
}
}
}
BOOST_AUTO_TEST_SUITE_END()