Fix timezone transition times.

This is responsible for test failures on DST transition days.

See the comments in gnc-timezone.cpp for an explanation of why this is
correct. The rubric was tested on macOS, Arch Linux, Debian Unstable,
Fedora 33, and Ubuntu 18.04 to confirm universal applicability.
pull/811/head
John Ralls 6 years ago
parent b6c0a62bbd
commit 3bcf57e7f2

@ -549,10 +549,34 @@ namespace DSTRule
std::swap(to_std_time, to_dst_time);
std::swap(std_info, dst_info);
}
if (dst_info->isgmt)
to_dst_time += boost::posix_time::seconds(dst_info->info.gmtoff);
if (std_info->isgmt)
to_std_time += boost::posix_time::seconds(std_info->info.gmtoff);
/* Documentation notwithstanding, the date-time rules are
* looking for local time (wall clock to use the RFC 8538
* definition) values.
*
* The TZ Info contains two fields, isstd and isgmt (renamed
* to isut in newer versions of tzinfo). In theory if both are
* 0 the transition times represent wall-clock times,
* i.e. time stamps in the respective time zone's local time
* at the moment of the transition. If isstd is 1 then the
* representation is always in standard time instead of
* daylight time; this is significant for dst->std
* transitions. If isgmt/isut is one then isstd must also be
* set and the transition time is in UTC.
*
* In practice it seems that the timestamps are always in UTC
* so the isgmt/isut flag isn't meaningful. The times always
* need to have the utc offset added to them to make the
* transition occur at the right time; the isstd flag
* determines whether that should be the standard offset or
* the daylight offset for the daylight->standard transition.
*/
to_dst_time += boost::posix_time::seconds(std_info->info.gmtoff);
if (std_info->isstd) //if isstd always use standard time
to_std_time += boost::posix_time::seconds(std_info->info.gmtoff);
else
to_std_time += boost::posix_time::seconds(dst_info->info.gmtoff);
}

@ -381,6 +381,57 @@ TEST(gnc_datetime_constructors, test_gncdate_end_constructor)
EXPECT_EQ(atime.format("%d-%m-%Y %H:%M:%S"), "06-11-2046 23:59:59");
}
static ::testing::AssertionResult
test_offset(time64 start_time, int hour, int offset1, int offset2,
const char* zone)
{
GncDateTime gdt{start_time + hour * 3600};
if ((hour < 2 && gdt.offset() == offset1) ||
(hour >= 2 && gdt.offset() == offset2))
return ::testing::AssertionSuccess();
else
return ::testing::AssertionFailure() << zone << ": " << gdt.format("%D %T %z %q") << " hour " << hour;
}
TEST(gnc_datetime_constructors, test_DST_start_transition_time)
{
#ifdef __MINGW32__
TimeZoneProvider tzp_can{"A.U.S Eastern Standard Time"};
TimeZoneProvider tzp_la{"Pacific Standard Time"};
#else
TimeZoneProvider tzp_can("Australia/Canberra");
TimeZoneProvider tzp_la("America/Los_Angeles");
#endif
_set_tzp(tzp_la);
for (auto hours = 0; hours < 23; ++hours)
EXPECT_TRUE(test_offset(1583657940, hours, -28800, -25200, "Los Angeles"));
_reset_tzp();
_set_tzp(tzp_can);
for (auto hours = 0; hours < 23; ++hours)
EXPECT_TRUE(test_offset(1601737140, hours, 36000, 39600, "Canberra"));
_reset_tzp();
}
TEST(gnc_datetime_constructors, test_DST_end_transition_time)
{
#ifdef __MINGW32__
TimeZoneProvider tzp_can{"A.U.S Eastern Standard Time"};
TimeZoneProvider tzp_la{"Pacific Standard Time"};
#else
TimeZoneProvider tzp_can("Australia/Canberra");
TimeZoneProvider tzp_la("America/Los_Angeles");
#endif
_set_tzp(tzp_la);
for (auto hours = 0; hours < 23; ++hours)
EXPECT_TRUE(test_offset(1604217540, hours, -25200, -28800, "Los Angeles"));
_reset_tzp();
_set_tzp(tzp_can);
for (auto hours = 0; hours < 23; ++hours)
EXPECT_TRUE(test_offset(1586008740, hours, 39600, 36000, "Canberra"));
_reset_tzp();
}
TEST(gnc_datetime_constructors, test_gncdate_neutral_constructor)
{
const ymd aymd = { 2017, 04, 20 };

Loading…
Cancel
Save