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