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