Skip to content

Commit

Permalink
Fix timezone transition times.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jralls committed Nov 7, 2020
1 parent b6c0a62 commit 3bcf57e
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 4 deletions.
32 changes: 28 additions & 4 deletions libgnucash/engine/gnc-timezone.cpp
Expand Up @@ -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);

}

Expand Down
51 changes: 51 additions & 0 deletions libgnucash/engine/test/gtest-gnc-datetime.cpp
Expand Up @@ -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 };
Expand Down

0 comments on commit 3bcf57e

Please sign in to comment.