gnucash maint: Bug 795080 - Some dates reset to 01/01/1970

John Ralls jralls at code.gnucash.org
Fri Nov 2 13:45:52 EDT 2018


Updated	 via  https://github.com/Gnucash/gnucash/commit/0e723610 (commit)
	from  https://github.com/Gnucash/gnucash/commit/19b3643a (commit)



commit 0e723610f0cb23515f564bc87e6b0b2f16577f5b
Author: John Ralls <jralls at ceridwen.us>
Date:   Fri Nov 2 10:29:52 2018 -0700

    Bug 795080 - Some dates reset to 01/01/1970
    
    The first fix for this bug handled structs tm with ambiguous times.
    This one fixes the GncDate constructor when the time is ambiguous
    because it's in the DST-change hour, using the same add 3 hours,
    construct the LDT, and subtract the 3 hours from the result.
    
    The string constructor handles only simple-offset HH:MM timezones and so
    is immune to the bug.

diff --git a/libgnucash/engine/gnc-datetime.cpp b/libgnucash/engine/gnc-datetime.cpp
index f8b52dd..8561cb0 100644
--- a/libgnucash/engine/gnc-datetime.cpp
+++ b/libgnucash/engine/gnc-datetime.cpp
@@ -52,12 +52,18 @@ using LDTBase = boost::local_time::local_date_time_base<PTime, boost::date_time:
 using boost::date_time::not_a_date_time;
 using time64 = int64_t;
 
-static const TimeZoneProvider tzp;
+static const TimeZoneProvider ltzp;
+static const TimeZoneProvider* tzp = <zp;
+
 // For converting to/from POSIX time.
 static const PTime unix_epoch (Date(1970, boost::gregorian::Jan, 1),
         boost::posix_time::seconds(0));
 static const TZ_Ptr utc_zone(new boost::local_time::posix_time_zone("UTC-0"));
 
+/* Backdoor to enable unittests to temporarily override the timezone: */
+void _set_tzp(TimeZoneProvider& tz);
+void _reset_tzp();
+
 /* To ensure things aren't overly screwed up by setting the nanosecond clock for boost::date_time. Don't do it, though, it doesn't get us anything and slows down the date/time library. */
 #ifndef BOOST_DATE_TIME_HAS_NANOSECONDS
 static constexpr auto ticks_per_second = INT64_C(1000000);
@@ -146,7 +152,7 @@ LDT_from_unix_local(const time64 time)
         PTime temp(unix_epoch.date(),
                    boost::posix_time::hours(time / 3600) +
                    boost::posix_time::seconds(time % 3600));
-        auto tz = tzp.get(temp.date().year());
+        auto tz = tzp->get(temp.date().year());
         return LDT(temp, tz);
     }
     catch(boost::gregorian::bad_year&)
@@ -167,7 +173,7 @@ LDT_from_struct_tm(const struct tm tm)
         tdate = boost::gregorian::date_from_tm(tm);
         tdur = boost::posix_time::time_duration(tm.tm_hour, tm.tm_min,
                                                  tm.tm_sec, 0);
-        tz = tzp.get(tdate.year());
+        tz = tzp->get(tdate.year());
         LDT ldt(tdate, tdur, tz, LDTBase::EXCEPTION_ON_ERROR);
         return ldt;
     }
@@ -198,20 +204,32 @@ LDT_from_struct_tm(const struct tm tm)
 
 using TD = boost::posix_time::time_duration;
 
+void
+_set_tzp(TimeZoneProvider& new_tzp)
+{
+    tzp = &new_tzp;
+}
+
+void
+_reset_tzp()
+{
+    tzp = <zp;
+}
+
 class GncDateTimeImpl
 {
 public:
-    GncDateTimeImpl() : m_time(boost::local_time::local_sec_clock::local_time(tzp.get(boost::gregorian::day_clock::local_day().year()))) {}
+    GncDateTimeImpl() : m_time(boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year()))) {}
     GncDateTimeImpl(const time64 time) : m_time(LDT_from_unix_local(time)) {}
     GncDateTimeImpl(const struct tm tm) : m_time(LDT_from_struct_tm(tm)) {}
     GncDateTimeImpl(const GncDateImpl& date, DayPart part = DayPart::neutral);
     GncDateTimeImpl(std::string str);
-    GncDateTimeImpl(PTime&& pt) : m_time(pt, tzp.get(pt.date().year())) {}
+    GncDateTimeImpl(PTime&& pt) : m_time(pt, tzp->get(pt.date().year())) {}
     GncDateTimeImpl(LDT&& ldt) : m_time(ldt) {}
 
     operator time64() const;
     operator struct tm() const;
-    void now() { m_time = boost::local_time::local_sec_clock::local_time(tzp.get(boost::gregorian::day_clock::local_day().year())); }
+    void now() { m_time = boost::local_time::local_sec_clock::local_time(tzp->get(boost::gregorian::day_clock::local_day().year())); }
     long offset() const;
     struct tm utc_tm() const { return to_tm(m_time.utc_time()); }
     std::unique_ptr<GncDateImpl> date() const;
@@ -254,13 +272,28 @@ private:
  */
 
 GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl& date, DayPart part) :
-    m_time(date.m_greg, time_of_day[part], tzp.get(date.m_greg.year()),
+    m_time(date.m_greg, time_of_day[part], tzp->get(date.m_greg.year()),
                      LDT::NOT_DATE_TIME_ON_ERROR)
 {
     using boost::posix_time::hours;
-    try
+    if (m_time.is_not_a_date_time())
+    {
+        try
+        {
+            auto t_o_d = time_of_day[part] + hours(3);
+            LDT time(date.m_greg, t_o_d, tzp->get(date.m_greg.year()),
+                     LDT::EXCEPTION_ON_ERROR);
+            m_time = time - hours(3);
+        }
+        catch(boost::gregorian::bad_year&)
+        {
+            throw(std::invalid_argument("Time value is outside the supported year range."));
+        }
+    }
+
+    if (part == DayPart::neutral)
     {
-        if (part == DayPart::neutral)
+        try
         {
             auto offset = m_time.local_time() - m_time.utc_time();
             m_time = LDT(date.m_greg, time_of_day[part], utc_zone,
@@ -270,10 +303,10 @@ GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl& date, DayPart part) :
             if (offset > hours(13))
                 m_time -= hours(offset.hours() - 11);
         }
-    }
-    catch(boost::gregorian::bad_year&)
-    {
-        throw(std::invalid_argument("Time value is outside the supported year range."));
+        catch(boost::gregorian::bad_year&)
+        {
+            throw(std::invalid_argument("Time value is outside the supported year range."));
+        }
     }
 }
 
diff --git a/libgnucash/engine/test/gtest-gnc-datetime.cpp b/libgnucash/engine/test/gtest-gnc-datetime.cpp
index 65989bf..1d25933 100644
--- a/libgnucash/engine/test/gtest-gnc-datetime.cpp
+++ b/libgnucash/engine/test/gtest-gnc-datetime.cpp
@@ -23,8 +23,13 @@
 \********************************************************************/
 
 #include "../gnc-datetime.hpp"
+#include "../gnc-timezone.hpp"
 #include <gtest/gtest.h>
 
+/* Backdoor to enable unittests to temporarily override the timezone: */
+void _set_tzp(TimeZoneProvider& tz);
+void _reset_tzp();
+
 TEST(gnc_date_constructors, test_default_constructor)
 {
     GncDate date;
@@ -341,6 +346,23 @@ TEST(gnc_datetime_constructors, test_gncdate_start_constructor)
     EXPECT_EQ(atime.format("%d-%m-%Y %H:%M:%S"), "20-04-2017 00:00:00");
 }
 
+/* Summertime transitions have been a recurring problem. At the time of adding
+ * this test the GncDateEditor was refusing to set the date to 28 October 2018
+ * in the Europe/London timezone.
+ */
+TEST(gnc_datetime_constructors, test_gncdate_BST_transition)
+{
+    const ymd begins = {2018, 03, 25};
+    const ymd ends = {2018, 10, 28};
+    TimeZoneProvider tzp("Europe/London");
+    _set_tzp(tzp);
+    GncDateTime btime(GncDate(begins.year, begins.month, begins.day), DayPart::start);
+    GncDateTime etime(GncDate(ends.year, ends.month, ends.day), DayPart::start);
+    _reset_tzp();
+    EXPECT_EQ(btime.format("%d-%m-%Y %H:%M:%S"), "25-03-2018 00:00:00");
+    EXPECT_EQ(etime.format("%d-%m-%Y %H:%M:%S"), "28-10-2018 00:00:00");
+}
+
 TEST(gnc_datetime_constructors, test_gncdate_end_constructor)
 {
     const ymd aymd = { 2046, 11, 06 };
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 8e327ea..892c211 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -616,6 +616,7 @@ libgnucash/core-utils/gnc-locale-utils.c
 libgnucash/core-utils/gnc-path.c
 libgnucash/core-utils/gnc-prefs.c
 libgnucash/doc/doxygen_main_page.c
+libgnucash/engine/.#gnc-datetime.cpp
 libgnucash/engine/Account.cpp
 libgnucash/engine/business-core.scm
 libgnucash/engine/cap-gains.c



Summary of changes:
 libgnucash/engine/gnc-datetime.cpp            | 59 +++++++++++++++++++++------
 libgnucash/engine/test/gtest-gnc-datetime.cpp | 22 ++++++++++
 po/POTFILES.in                                |  1 +
 3 files changed, 69 insertions(+), 13 deletions(-)



More information about the gnucash-changes mailing list