mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
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.
This commit is contained in:
parent
19b3643abf
commit
0e723610f0
@ -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())
|
||||
{
|
||||
if (part == DayPart::neutral)
|
||||
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)
|
||||
{
|
||||
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."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 };
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user