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.
This commit is contained in:
Julian Brost 2021-10-26 13:21:49 +02:00
parent e1c6c9eb19
commit a740b1d66c
1 changed files with 26 additions and 4 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);