gnucash maint: Fix timezone transition times.
John Ralls
jralls at code.gnucash.org
Sat Nov 7 14:05:59 EST 2020
Updated via https://github.com/Gnucash/gnucash/commit/3bcf57e7 (commit)
from https://github.com/Gnucash/gnucash/commit/b6c0a62b (commit)
commit 3bcf57e7f21033a73102ad986bcf669644fa9979
Author: John Ralls <jralls at ceridwen.us>
Date: Fri Nov 6 16:54:22 2020 -0800
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.
diff --git a/libgnucash/engine/gnc-timezone.cpp b/libgnucash/engine/gnc-timezone.cpp
index 3bb157b5a..6c03481d4 100644
--- a/libgnucash/engine/gnc-timezone.cpp
+++ b/libgnucash/engine/gnc-timezone.cpp
@@ -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);
}
diff --git a/libgnucash/engine/test/gtest-gnc-datetime.cpp b/libgnucash/engine/test/gtest-gnc-datetime.cpp
index 3fcbb6d20..446f810e8 100644
--- a/libgnucash/engine/test/gtest-gnc-datetime.cpp
+++ b/libgnucash/engine/test/gtest-gnc-datetime.cpp
@@ -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 };
Summary of changes:
libgnucash/engine/gnc-timezone.cpp | 32 ++++++++++++++---
libgnucash/engine/test/gtest-gnc-datetime.cpp | 51 +++++++++++++++++++++++++++
2 files changed, 79 insertions(+), 4 deletions(-)
More information about the gnucash-changes
mailing list