mirror of
https://github.com/Icinga/icinga2.git
synced 2025-07-23 21:55:03 +02:00
Merge pull request #10422 from Icinga/mktime-dst-consistency
Ensure consistent mktime() DST behavior across different implementations
This commit is contained in:
commit
b2b47981a5
5
.github/workflows/linux.bash
vendored
5
.github/workflows/linux.bash
vendored
@ -14,11 +14,6 @@ case "$DISTRO" in
|
||||
# https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/community/icinga2/APKBUILD
|
||||
apk add bison boost-dev ccache cmake flex g++ libedit-dev libressl-dev ninja-build tzdata
|
||||
ln -vs /usr/lib/ninja-build/bin/ninja /usr/local/bin/ninja
|
||||
|
||||
# This test fails due to some glibc/musl mismatch regarding timezone PST/PDT.
|
||||
# - https://www.openwall.com/lists/musl/2024/03/05/2
|
||||
# - https://gitlab.alpinelinux.org/alpine/aports/-/blob/b3ea02e2251451f9511086e1970f21eb640097f7/community/icinga2/disable-failing-tests.patch
|
||||
sed -i '/icinga_legacytimeperiod\/dst$/d' /icinga2/test/CMakeLists.txt
|
||||
;;
|
||||
|
||||
amazonlinux:2)
|
||||
|
@ -35,7 +35,7 @@ DateTime::DateTime(const std::vector<Value>& args)
|
||||
|
||||
tms.tm_isdst = -1;
|
||||
|
||||
m_Value = mktime(&tms);
|
||||
m_Value = Utility::TmToTimestamp(&tms);
|
||||
} else if (args.size() == 1)
|
||||
m_Value = args[0];
|
||||
else
|
||||
|
@ -1958,3 +1958,51 @@ bool Utility::ComparePasswords(const String& enteredPassword, const String& actu
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the given struct tm like mktime() from libc does with some exception for DST handling: If the given time
|
||||
* exists twice on a day, the instance in the DST timezone is picked. If the time does not actually exist on a day, it's
|
||||
* interpreted using the UTC offset of the standard timezone and then normalized.
|
||||
*
|
||||
* This is done in order to provide consistent behavior across operating systems. Historically, Icinga 2 just relied on
|
||||
* whatever mktime() of the operating system did and this function mimics what glibc does as that's what most systems
|
||||
* use.
|
||||
*
|
||||
* @param t tm struct to be normalized
|
||||
* @return time_t representing the timestamp given by t
|
||||
*/
|
||||
time_t Utility::NormalizeTm(tm *t)
|
||||
{
|
||||
// If tm_isdst already specifies the timezone (0 or 1), just use the mktime() behavior.
|
||||
if (t->tm_isdst >= 0) {
|
||||
return mktime(t);
|
||||
}
|
||||
|
||||
const tm copy = *t;
|
||||
|
||||
t->tm_isdst = 1;
|
||||
time_t result = mktime(t);
|
||||
if (result != -1 && t->tm_isdst == 1) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Restore the original input. mktime() can (and does) change more fields than just tm_isdst by converting from
|
||||
// daylight saving time to standard time (it moves the contents by (typically) an hour, which can move across
|
||||
// days/weeks/months/years changing all other fields).
|
||||
*t = copy;
|
||||
|
||||
t->tm_isdst = 0;
|
||||
return mktime(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the same as NormalizeTm() but takes a const pointer as argument and thus does not modify it.
|
||||
*
|
||||
* @param t struct tm to convert to time_t
|
||||
* @return time_t representing the timestamp given by t
|
||||
*/
|
||||
time_t Utility::TmToTimestamp(const tm *t)
|
||||
{
|
||||
tm copy = *t;
|
||||
return NormalizeTm(©);
|
||||
}
|
||||
|
@ -185,6 +185,9 @@ public:
|
||||
return in.SubStr(0, maxLength - sha1HexLength - strlen(trunc)) + trunc + SHA1(in);
|
||||
}
|
||||
|
||||
static time_t NormalizeTm(tm *t);
|
||||
static time_t TmToTimestamp(const tm *t);
|
||||
|
||||
private:
|
||||
Utility();
|
||||
|
||||
|
@ -13,23 +13,12 @@ using namespace icinga;
|
||||
|
||||
REGISTER_FUNCTION_NONCONST(Internal, LegacyTimePeriod, &LegacyTimePeriod::ScriptFunc, "tp:begin:end");
|
||||
|
||||
/**
|
||||
* Returns the same as mktime() but does not modify its argument and takes a const pointer.
|
||||
*
|
||||
* @param t struct tm to convert to time_t
|
||||
* @return time_t representing the timestamp given by t
|
||||
*/
|
||||
static time_t mktime_const(const tm *t) {
|
||||
tm copy = *t;
|
||||
return mktime(©);
|
||||
}
|
||||
|
||||
bool LegacyTimePeriod::IsInTimeRange(const tm *begin, const tm *end, int stride, const tm *reference)
|
||||
{
|
||||
time_t tsbegin, tsend, tsref;
|
||||
tsbegin = mktime_const(begin);
|
||||
tsend = mktime_const(end);
|
||||
tsref = mktime_const(reference);
|
||||
tsbegin = Utility::TmToTimestamp(begin);
|
||||
tsend = Utility::TmToTimestamp(end);
|
||||
tsref = Utility::TmToTimestamp(reference);
|
||||
|
||||
if (tsref < tsbegin || tsref >= tsend)
|
||||
return false;
|
||||
@ -85,7 +74,7 @@ void LegacyTimePeriod::FindNthWeekday(int wday, int n, tm *reference)
|
||||
t.tm_sec = 0;
|
||||
t.tm_isdst = -1;
|
||||
|
||||
mktime(&t);
|
||||
Utility::NormalizeTm(&t);
|
||||
|
||||
if (t.tm_wday == wday) {
|
||||
seen++;
|
||||
@ -398,8 +387,8 @@ bool LegacyTimePeriod::IsInDayDefinition(const String& daydef, const tm *referen
|
||||
ParseTimeRange(daydef, &begin, &end, &stride, reference);
|
||||
|
||||
Log(LogDebug, "LegacyTimePeriod")
|
||||
<< "ParseTimeRange: '" << daydef << "' => " << mktime(&begin)
|
||||
<< " -> " << mktime(&end) << ", stride: " << stride;
|
||||
<< "ParseTimeRange: '" << daydef << "' => " << Utility::TmToTimestamp(&begin)
|
||||
<< " -> " << Utility::TmToTimestamp(&end) << ", stride: " << stride;
|
||||
|
||||
return IsInTimeRange(&begin, &end, stride, reference);
|
||||
}
|
||||
@ -448,8 +437,8 @@ Dictionary::Ptr LegacyTimePeriod::ProcessTimeRange(const String& timestamp, cons
|
||||
ProcessTimeRangeRaw(timestamp, reference, &begin, &end);
|
||||
|
||||
return new Dictionary({
|
||||
{ "begin", (long)mktime(&begin) },
|
||||
{ "end", (long)mktime(&end) }
|
||||
{ "begin", (long)Utility::TmToTimestamp(&begin) },
|
||||
{ "end", (long)Utility::TmToTimestamp(&end) }
|
||||
});
|
||||
}
|
||||
|
||||
@ -480,13 +469,13 @@ Dictionary::Ptr LegacyTimePeriod::FindRunningSegment(const String& daydef, const
|
||||
time_t tsend, tsiter, tsref;
|
||||
int stride;
|
||||
|
||||
tsref = mktime_const(reference);
|
||||
tsref = Utility::TmToTimestamp(reference);
|
||||
|
||||
ParseTimeRange(daydef, &begin, &end, &stride, reference);
|
||||
|
||||
iter = begin;
|
||||
|
||||
tsend = mktime(&end);
|
||||
tsend = Utility::NormalizeTm(&end);
|
||||
|
||||
do {
|
||||
if (IsInTimeRange(&begin, &end, stride, &iter)) {
|
||||
@ -518,7 +507,7 @@ Dictionary::Ptr LegacyTimePeriod::FindRunningSegment(const String& daydef, const
|
||||
iter.tm_hour = 0;
|
||||
iter.tm_min = 0;
|
||||
iter.tm_sec = 0;
|
||||
tsiter = mktime(&iter);
|
||||
tsiter = Utility::NormalizeTm(&iter);
|
||||
} while (tsiter < tsend);
|
||||
|
||||
return nullptr;
|
||||
@ -538,13 +527,13 @@ Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const St
|
||||
ref.tm_mday++;
|
||||
}
|
||||
|
||||
tsref = mktime(&ref);
|
||||
tsref = Utility::NormalizeTm(&ref);
|
||||
|
||||
ParseTimeRange(daydef, &begin, &end, &stride, &ref);
|
||||
|
||||
iter = begin;
|
||||
|
||||
tsend = mktime(&end);
|
||||
tsend = Utility::NormalizeTm(&end);
|
||||
|
||||
do {
|
||||
if (IsInTimeRange(&begin, &end, stride, &iter)) {
|
||||
@ -575,7 +564,7 @@ Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const St
|
||||
iter.tm_hour = 0;
|
||||
iter.tm_min = 0;
|
||||
iter.tm_sec = 0;
|
||||
tsiter = mktime(&iter);
|
||||
tsiter = Utility::NormalizeTm(&iter);
|
||||
} while (tsiter < tsend);
|
||||
}
|
||||
|
||||
@ -607,17 +596,17 @@ Array::Ptr LegacyTimePeriod::ScriptFunc(const TimePeriod::Ptr& tp, double begin,
|
||||
t->tm_isdst = -1;
|
||||
|
||||
// Normalize fields using mktime.
|
||||
mktime(t);
|
||||
Utility::NormalizeTm(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)) {
|
||||
for (tm reference = tm_begin; Utility::TmToTimestamp(&reference) <= end; advance_to_next_day(&reference)) {
|
||||
|
||||
#ifdef I2_DEBUG
|
||||
Log(LogDebug, "LegacyTimePeriod")
|
||||
<< "Checking reference time " << mktime_const(&reference);
|
||||
<< "Checking reference time " << Utility::TmToTimestamp(&reference);
|
||||
#endif /* I2_DEBUG */
|
||||
|
||||
ObjectLock olock(ranges);
|
||||
|
@ -57,6 +57,7 @@ add_boost_test(types
|
||||
|
||||
set(base_test_SOURCES
|
||||
icingaapplication-fixture.cpp
|
||||
utils.cpp
|
||||
base-array.cpp
|
||||
base-base64.cpp
|
||||
base-convert.cpp
|
||||
@ -185,6 +186,7 @@ add_boost_test(base
|
||||
base_utility/EscapeCreateProcessArg
|
||||
base_utility/TruncateUsingHash
|
||||
base_utility/FormatDateTime
|
||||
base_utility/NormalizeTm
|
||||
base_value/scalar
|
||||
base_value/convert
|
||||
base_value/format
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
|
||||
|
||||
#include "base/utility.hpp"
|
||||
#include "test/utils.hpp"
|
||||
#include <chrono>
|
||||
#include <BoostTestTargetConfig.h>
|
||||
|
||||
@ -230,4 +231,86 @@ BOOST_AUTO_TEST_CASE(FormatDateTime) {
|
||||
BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", positive_out_of_range), positive_overflow);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(NormalizeTm)
|
||||
{
|
||||
GlobalTimezoneFixture tz(GlobalTimezoneFixture::TestTimezoneWithDST);
|
||||
|
||||
auto normalize = [](const std::string_view& input) {
|
||||
tm t = make_tm(std::string(input));
|
||||
return Utility::NormalizeTm(&t);
|
||||
};
|
||||
|
||||
auto is_dst = [](const std::string_view& input) {
|
||||
tm t = make_tm(std::string(input));
|
||||
Utility::NormalizeTm(&t);
|
||||
BOOST_CHECK_GE(t.tm_isdst, 0);
|
||||
return t.tm_isdst > 0;
|
||||
};
|
||||
|
||||
// The whole day 2021-01-01 uses PST (24h day)
|
||||
BOOST_CHECK(!is_dst("2021-01-01 10:00:00"));
|
||||
BOOST_CHECK_EQUAL(normalize("2021-01-01 10:00:00"), 1609524000);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-01-01 10:00:00 PST"), 1609524000);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-01-01 11:00:00 PDT"), 1609524000); // normalized to 10:00 PST
|
||||
BOOST_CHECK_EQUAL(normalize("2021-01-02 00:00:00") - normalize("2021-01-01 00:00:00"), 24*60*60);
|
||||
|
||||
// The whole day 2021-07-01 uses PDT (24h day)
|
||||
BOOST_CHECK(is_dst("2021-07-01 10:00:00"));
|
||||
BOOST_CHECK_EQUAL(normalize("2021-07-01 10:00:00"), 1625158800);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-07-01 10:00:00 PDT"), 1625158800);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-07-01 09:00:00 PST"), 1625158800); // normalized to 10:00 PDT
|
||||
BOOST_CHECK_EQUAL(normalize("2021-07-02 00:00:00") - normalize("2021-07-01 00:00:00"), 24*60*60);
|
||||
|
||||
// On 2021-03-14, PST changes to PDT (23h day)
|
||||
BOOST_CHECK(!is_dst("2021-03-14 00:00:00"));
|
||||
BOOST_CHECK(is_dst("2021-03-14 23:59:59"));
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-15 00:00:00") - normalize("2021-03-14 00:00:00"), 23*60*60);
|
||||
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 01:59:59 PST"), 1615715999);
|
||||
// The following three times do not exist on that day in that timezone.
|
||||
// They are interpreted as UTC-8, which is the offset of PST.
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 02:00:00 PST"), 1615716000);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 02:30:00 PST"), 1615717800);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 03:00:00 PST"), 1615719600);
|
||||
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 03:00:00 PDT"), 1615716000);
|
||||
// The following three times do not exist on that day in that timezone.
|
||||
// They are interpreted as UTC-7, which is the offset of PDT.
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 01:59:59 PDT"), 1615712399);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 02:00:00 PDT"), 1615712400);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 02:30:00 PDT"), 1615714200);
|
||||
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 01:59:59"), 1615715999);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 03:00:00"), 1615716000);
|
||||
// The following two times don't exist on that day, they are within the hour that is skipped.
|
||||
// They are interpreted as UTC-8 (offset of PST) and then normalized to PDT.
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 02:00:00"), 1615716000);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-03-14 02:30:00"), 1615717800);
|
||||
|
||||
// On 2021-11-07, PDT changes to PST (25h day)
|
||||
BOOST_CHECK(is_dst("2021-11-07 00:00:00"));
|
||||
BOOST_CHECK(!is_dst("2021-11-07 23:59:59"));
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-08 00:00:00") - normalize("2021-11-07 00:00:00"), 25*60*60);
|
||||
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 00:59:59 PDT"), 1636271999);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 01:00:00 PDT"), 1636272000);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 01:30:00 PDT"), 1636273800);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 01:59:59 PDT"), 1636275599);
|
||||
// The following time does not exist on that day in that timezone, it's interpreted as 01:00:00 PST.
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 02:00:00 PDT"), 1636275600);
|
||||
|
||||
// The following time does not exist on that day in that timezone, it's interpreted as 01:59:59 PDT.
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 00:59:59 PST"), 1636275599);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 01:00:00 PST"), 1636275600);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 01:30:00 PST"), 1636277400);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 01:59:59 PST"), 1636279199);
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 02:00:00 PST"), 1636279200);
|
||||
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 00:59:59"), 1636271999); // unambiguous: PDT
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 01:00:00"), 1636272000); // exists twice, interpreted as PDT
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 01:30:00"), 1636273800); // exists twice, interpreted as PDT
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 01:59:59"), 1636275599); // exists twice, interpreted as PDT
|
||||
BOOST_CHECK_EQUAL(normalize("2021-11-07 02:00:00"), 1636279200); // unambiguous: PST
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include "base/utility.hpp"
|
||||
#include "icinga/legacytimeperiod.hpp"
|
||||
#include "test/utils.hpp"
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <boost/date_time/posix_time/ptime.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time_duration.hpp>
|
||||
@ -15,52 +16,8 @@ using namespace icinga;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(icinga_legacytimeperiod);
|
||||
|
||||
struct GlobalTimezoneFixture
|
||||
{
|
||||
char *tz;
|
||||
|
||||
GlobalTimezoneFixture(const char *fixed_tz = "")
|
||||
{
|
||||
tz = getenv("TZ");
|
||||
#ifdef _WIN32
|
||||
_putenv_s("TZ", fixed_tz == "" ? "UTC" : fixed_tz);
|
||||
#else
|
||||
setenv("TZ", fixed_tz, 1);
|
||||
#endif
|
||||
tzset();
|
||||
}
|
||||
|
||||
~GlobalTimezoneFixture()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (tz)
|
||||
_putenv_s("TZ", tz);
|
||||
else
|
||||
_putenv_s("TZ", "");
|
||||
#else
|
||||
if (tz)
|
||||
setenv("TZ", tz, 1);
|
||||
else
|
||||
unsetenv("TZ");
|
||||
#endif
|
||||
tzset();
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
@ -471,35 +428,6 @@ BOOST_AUTO_TEST_CASE(advanced)
|
||||
AdvancedHelper("09:00:03-30:00:04", {{2014, 9, 24}, {9, 0, 3}}, {{2014, 9, 25}, {6, 0, 4}});
|
||||
}
|
||||
|
||||
tm make_tm(std::string s)
|
||||
{
|
||||
int dst = -1;
|
||||
size_t l = strlen("YYYY-MM-DD HH:MM:SS");
|
||||
if (s.size() > l) {
|
||||
std::string zone = s.substr(l);
|
||||
if (zone == " PST") {
|
||||
dst = 0;
|
||||
} else if (zone == " PDT") {
|
||||
dst = 1;
|
||||
} else {
|
||||
// tests should only use PST/PDT (for now)
|
||||
BOOST_CHECK_MESSAGE(false, "invalid or unknown time time: " << zone);
|
||||
}
|
||||
}
|
||||
|
||||
std::tm t = {};
|
||||
#if defined(__GNUC__) && __GNUC__ < 5
|
||||
// GCC did not implement std::get_time() until version 5
|
||||
strptime(s.c_str(), "%Y-%m-%d %H:%M:%S", &t);
|
||||
#else /* defined(__GNUC__) && __GNUC__ < 5 */
|
||||
std::istringstream stream(s);
|
||||
stream >> std::get_time(&t, "%Y-%m-%d %H:%M:%S");
|
||||
#endif /* defined(__GNUC__) && __GNUC__ < 5 */
|
||||
t.tm_isdst = dst;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
time_t make_time_t(const tm* t)
|
||||
{
|
||||
tm copy = *t;
|
||||
@ -551,7 +479,7 @@ std::ostream& operator<<(std::ostream& o, const boost::optional<Segment>& s)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dst)
|
||||
{
|
||||
GlobalTimezoneFixture tz(dst_test_timezone);
|
||||
GlobalTimezoneFixture tz(GlobalTimezoneFixture::TestTimezoneWithDST);
|
||||
|
||||
// Self-tests for helper functions
|
||||
BOOST_CHECK_EQUAL(make_tm("2021-11-07 02:30:00").tm_isdst, -1);
|
||||
@ -589,13 +517,8 @@ BOOST_AUTO_TEST_CASE(dst)
|
||||
day, "01:30-02:30",
|
||||
{make_tm("2021-03-14 01:00:00 PST")},
|
||||
{make_tm("2021-03-14 01:59:59 PST")},
|
||||
#ifndef _WIN32
|
||||
// As 02:30 does not exist on this day, it is parsed as if it was 02:30 PST which is actually 03:30 PDT.
|
||||
Segment("2021-03-14 01:30:00 PST", "2021-03-14 03:30:00 PDT"),
|
||||
#else
|
||||
// Windows interpretes 02:30 as 01:30 PST, so it is an empty segment.
|
||||
boost::none,
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
@ -605,14 +528,9 @@ BOOST_AUTO_TEST_CASE(dst)
|
||||
day, "02:30-03:30",
|
||||
{make_tm("2021-03-14 01:00:00 PST")},
|
||||
{make_tm("2021-03-14 03:00:00 PDT")},
|
||||
#ifndef _WIN32
|
||||
// As 02:30 does not exist on this day, it is parsed as if it was 02:30 PST which is actually 03:30 PDT.
|
||||
// Therefore, the result is a segment from 03:30 PDT to 03:30 PDT with a duration of 0, i.e. no segment.
|
||||
boost::none,
|
||||
#else
|
||||
// Windows parses non-existing 02:30 as 01:30 PST, resulting in an 1 hour segment.
|
||||
Segment("2021-03-14 01:30:00 PST", "2021-03-14 03:30:00 PDT"),
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
@ -621,13 +539,8 @@ BOOST_AUTO_TEST_CASE(dst)
|
||||
day, "02:15-03:45",
|
||||
{make_tm("2021-03-14 01:00:00 PST")},
|
||||
{make_tm("2021-03-14 03:30:00 PDT")},
|
||||
#ifndef _WIN32
|
||||
// As 02:15 does not exist on this day, it is parsed as if it was 02:15 PST which is actually 03:15 PDT.
|
||||
Segment("2021-03-14 03:15:00 PDT", "2021-03-14 03:45:00 PDT"),
|
||||
#else
|
||||
// Windows interprets 02:15 as 01:15 PST though.
|
||||
Segment("2021-03-14 01:15:00 PST", "2021-03-14 03:45:00 PDT"),
|
||||
#endif
|
||||
});
|
||||
|
||||
// range after DST change
|
||||
@ -659,7 +572,6 @@ BOOST_AUTO_TEST_CASE(dst)
|
||||
|
||||
if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday)
|
||||
// range existing twice during DST change (first instance)
|
||||
#ifndef _WIN32
|
||||
tests.push_back(TestData{
|
||||
day, "01:15-01:45",
|
||||
{make_tm("2021-11-07 01:00:00 PDT")},
|
||||
@ -667,15 +579,6 @@ BOOST_AUTO_TEST_CASE(dst)
|
||||
// Duplicate times are interpreted as the first occurrence.
|
||||
Segment("2021-11-07 01:15:00 PDT", "2021-11-07 01:45:00 PDT"),
|
||||
});
|
||||
#else
|
||||
tests.push_back(TestData{
|
||||
day, "01:15-01:45",
|
||||
{make_tm("2021-11-07 01:00:00 PDT")},
|
||||
{make_tm("2021-11-07 01:30:00 PST")},
|
||||
// However, Windows always uses the second occurrence.
|
||||
Segment("2021-11-07 01:15:00 PST", "2021-11-07 01:45:00 PST"),
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday)
|
||||
@ -684,13 +587,8 @@ BOOST_AUTO_TEST_CASE(dst)
|
||||
day, "01:15-01:45",
|
||||
{make_tm("2021-11-07 01:00:00 PST")},
|
||||
{make_tm("2021-11-07 01:30:00 PST")},
|
||||
#ifndef _WIN32
|
||||
// Interpreted as the first occurrence, so it's in the past.
|
||||
boost::none,
|
||||
#else
|
||||
// On Windows, it's the second occurrence, so it's still in the present/future and is found.
|
||||
Segment("2021-11-07 01:15:00 PST", "2021-11-07 01:45:00 PST"),
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
@ -716,13 +614,8 @@ BOOST_AUTO_TEST_CASE(dst)
|
||||
day, "00:30-01:30",
|
||||
{make_tm("2021-11-07 00:00:00 PDT")},
|
||||
{make_tm("2021-11-07 01:00:00 PDT")},
|
||||
#ifndef _WIN32
|
||||
// Both times are interpreted as the first instance on that day (i.e both PDT).
|
||||
Segment("2021-11-07 00:30:00 PDT", "2021-11-07 01:30:00 PDT")
|
||||
#else
|
||||
// Windows interprets duplicate times as the second instance (i.e. both PST).
|
||||
Segment("2021-11-07 00:30:00 PDT", "2021-11-07 01:30:00 PST")
|
||||
#endif
|
||||
});
|
||||
|
||||
// range beginning during duplicate DST hour (first instance)
|
||||
@ -730,18 +623,12 @@ BOOST_AUTO_TEST_CASE(dst)
|
||||
day, "01:30-02:30",
|
||||
{make_tm("2021-11-07 01:00:00 PDT")},
|
||||
{make_tm("2021-11-07 02:00:00 PST")},
|
||||
#ifndef _WIN32
|
||||
// 01:30 is interpreted as the first occurrence (PDT) but since there's no 02:30 PDT, it's PST.
|
||||
Segment("2021-11-07 01:30:00 PDT", "2021-11-07 02:30:00 PST")
|
||||
#else
|
||||
// Windows interprets both as PST though.
|
||||
Segment("2021-11-07 01:30:00 PST", "2021-11-07 02:30:00 PST")
|
||||
#endif
|
||||
});
|
||||
|
||||
if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday)
|
||||
// range ending during duplicate DST hour (second instance)
|
||||
#ifndef _WIN32
|
||||
tests.push_back(TestData{
|
||||
day, "00:30-01:30",
|
||||
{make_tm("2021-11-07 00:00:00 PST")},
|
||||
@ -750,15 +637,6 @@ BOOST_AUTO_TEST_CASE(dst)
|
||||
// 01:00 PST (02:00 PDT) is after the segment.
|
||||
boost::none,
|
||||
});
|
||||
#else
|
||||
tests.push_back(TestData{
|
||||
day, "00:30-01:30",
|
||||
{make_tm("2021-11-07 00:00:00 PDT")},
|
||||
{make_tm("2021-11-07 01:00:00 PST")},
|
||||
// As Windows interprets the end as PST, it's still in the future and the segment is found.
|
||||
Segment("2021-11-07 00:30:00 PDT", "2021-11-07 01:30:00 PST"),
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
// range beginning during duplicate DST hour (second instance)
|
||||
@ -766,13 +644,8 @@ BOOST_AUTO_TEST_CASE(dst)
|
||||
day, "01:30-02:30",
|
||||
{make_tm("2021-11-07 01:00:00 PDT")},
|
||||
{make_tm("2021-11-07 02:00:00 PST")},
|
||||
#ifndef _WIN32
|
||||
// As 01:30 always refers to the first occurrence (PDT), this is actually a 2 hour segment.
|
||||
Segment("2021-11-07 01:30:00 PDT", "2021-11-07 02:30:00 PST"),
|
||||
#else
|
||||
// On Windows, it refers t the second occurrence (PST), therefore it's an 1 hour segment.
|
||||
Segment("2021-11-07 01:30:00 PST", "2021-11-07 02:30:00 PST"),
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
@ -830,7 +703,7 @@ 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);
|
||||
GlobalTimezoneFixture tz(GlobalTimezoneFixture::TestTimezoneWithDST);
|
||||
|
||||
Function::Ptr update = new Function("LegacyTimePeriod", LegacyTimePeriod::ScriptFunc, {"tp", "begin", "end"});
|
||||
Dictionary::Ptr ranges = new Dictionary({
|
||||
|
67
test/utils.cpp
Normal file
67
test/utils.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
|
||||
|
||||
#include "utils.hpp"
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
tm make_tm(std::string s)
|
||||
{
|
||||
int dst = -1;
|
||||
size_t l = strlen("YYYY-MM-DD HH:MM:SS");
|
||||
if (s.size() > l) {
|
||||
std::string zone = s.substr(l);
|
||||
if (zone == " PST") {
|
||||
dst = 0;
|
||||
} else if (zone == " PDT") {
|
||||
dst = 1;
|
||||
} else {
|
||||
// tests should only use PST/PDT (for now)
|
||||
BOOST_CHECK_MESSAGE(false, "invalid or unknown time time: " << zone);
|
||||
}
|
||||
}
|
||||
|
||||
std::tm t = {};
|
||||
std::istringstream stream(s);
|
||||
stream >> std::get_time(&t, "%Y-%m-%d %H:%M:%S");
|
||||
t.tm_isdst = dst;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
const char *GlobalTimezoneFixture::TestTimezoneWithDST = "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
|
||||
const char *GlobalTimezoneFixture::TestTimezoneWithDST = "PST8PDT";
|
||||
#endif /* _WIN32 */
|
||||
|
||||
GlobalTimezoneFixture::GlobalTimezoneFixture(const char *fixed_tz)
|
||||
{
|
||||
tz = getenv("TZ");
|
||||
#ifdef _WIN32
|
||||
_putenv_s("TZ", fixed_tz == "" ? "UTC" : fixed_tz);
|
||||
#else
|
||||
setenv("TZ", fixed_tz, 1);
|
||||
#endif
|
||||
tzset();
|
||||
}
|
||||
|
||||
GlobalTimezoneFixture::~GlobalTimezoneFixture()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (tz)
|
||||
_putenv_s("TZ", tz);
|
||||
else
|
||||
_putenv_s("TZ", "");
|
||||
#else
|
||||
if (tz)
|
||||
setenv("TZ", tz, 1);
|
||||
else
|
||||
unsetenv("TZ");
|
||||
#endif
|
||||
tzset();
|
||||
}
|
25
test/utils.hpp
Normal file
25
test/utils.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
tm make_tm(std::string s);
|
||||
|
||||
struct GlobalTimezoneFixture
|
||||
{
|
||||
/**
|
||||
* Timezone used for testing DST changes.
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
static const char *TestTimezoneWithDST;
|
||||
|
||||
GlobalTimezoneFixture(const char *fixed_tz = "");
|
||||
~GlobalTimezoneFixture();
|
||||
|
||||
char *tz;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user