From 6db08207718304af6bff832f4a607d61d52e514b Mon Sep 17 00:00:00 2001 From: John Ralls Date: Tue, 12 Dec 2017 08:42:32 -0800 Subject: [PATCH] Fix offset handling in GncDateTime struct tm ctor, gnc_mktime, & gnc_timegm. Tests now pass in all TZa from Honolulu to New Zealand. --- libgnucash/engine/gnc-date.cpp | 12 +++++-- libgnucash/engine/gnc-date.h | 3 -- libgnucash/engine/gnc-datetime.cpp | 14 +++++++- libgnucash/engine/test/gtest-gnc-datetime.cpp | 20 +++++++---- libgnucash/engine/test/test-gnc-date.c | 36 ++++++++++--------- 5 files changed, 56 insertions(+), 29 deletions(-) diff --git a/libgnucash/engine/gnc-date.cpp b/libgnucash/engine/gnc-date.cpp index e124135236..0c7b774c35 100644 --- a/libgnucash/engine/gnc-date.cpp +++ b/libgnucash/engine/gnc-date.cpp @@ -208,7 +208,8 @@ gnc_mktime (struct tm* time) { normalize_struct_tm (time); GncDateTime gncdt(*time); - return static_cast(gncdt) - gncdt.offset(); + *time = static_cast(gncdt); + return static_cast(gncdt); } catch(std::invalid_argument) { @@ -222,7 +223,14 @@ gnc_timegm (struct tm* time) try { normalize_struct_tm(time); - return static_cast(GncDateTime(*time)); + GncDateTime gncdt(*time); + *time = static_cast(gncdt); + time->tm_sec -= gncdt.offset(); + normalize_struct_tm(time); +#ifdef HAVE_STRUcT_TM_GMTOFF + time->tm_gmtoff = 0; +#endif + return static_cast(gncdt) - gncdt.offset(); } catch(std::invalid_argument) { diff --git a/libgnucash/engine/gnc-date.h b/libgnucash/engine/gnc-date.h index 46ca0c432d..7d15b31257 100644 --- a/libgnucash/engine/gnc-date.h +++ b/libgnucash/engine/gnc-date.h @@ -594,7 +594,6 @@ void gnc_tm_set_day_start (struct tm *tm) tm->tm_hour = 0; tm->tm_min = 0; tm->tm_sec = 0; - tm->tm_isdst = -1; } /** The gnc_tm_set_day_middle() inline routine will set the appropriate @@ -609,7 +608,6 @@ void gnc_tm_set_day_middle (struct tm *tm) tm->tm_hour = 12; tm->tm_min = 0; tm->tm_sec = 0; - tm->tm_isdst = -1; } /** The gnc_tm_set_day_end() inline routine will set the appropriate @@ -624,7 +622,6 @@ void gnc_tm_set_day_end (struct tm *tm) tm->tm_hour = 23; tm->tm_min = 59; tm->tm_sec = 59; - tm->tm_isdst = -1; } /** The gnc_time64_get_day_start() routine will take the given time in diff --git a/libgnucash/engine/gnc-datetime.cpp b/libgnucash/engine/gnc-datetime.cpp index 7a0b98c10b..e40b898a89 100644 --- a/libgnucash/engine/gnc-datetime.cpp +++ b/libgnucash/engine/gnc-datetime.cpp @@ -29,6 +29,7 @@ extern "C" } #include #include +#include #include #include #include @@ -163,12 +164,23 @@ LDT_from_struct_tm(const struct tm tm) auto tdur = boost::posix_time::time_duration(tm.tm_hour, tm.tm_min, tm.tm_sec, 0); auto tz = tzp.get(tdate.year()); - return LDT(PTime(tdate, tdur), tz); + LDT ldt(tdate, tdur, tz, LDTBase::EXCEPTION_ON_ERROR); + if (tm.tm_isdst == -1 && ldt.is_dst()) + ldt += tz->dst_offset(); + return ldt; } catch(boost::gregorian::bad_year) { throw(std::invalid_argument("Time value is outside the supported year range.")); } + catch(boost::local_time::time_label_invalid) + { + throw(std::invalid_argument("Struct tm does not resolve to a valid time.")); + } + catch(boost::local_time::ambiguous_result) + { + throw(std::invalid_argument("Struct tm can resolve to more than one time.")); + } } class GncDateTimeImpl diff --git a/libgnucash/engine/test/gtest-gnc-datetime.cpp b/libgnucash/engine/test/gtest-gnc-datetime.cpp index 936e8befb1..b77b303333 100644 --- a/libgnucash/engine/test/gtest-gnc-datetime.cpp +++ b/libgnucash/engine/test/gtest-gnc-datetime.cpp @@ -282,13 +282,15 @@ TEST(gnc_datetime_constructors, test_struct_tm_constructor) const time64 time = 2394187200; //2045-11-13 12:00:00 Z GncDateTime atime(tm); - EXPECT_EQ(static_cast(atime), time); + EXPECT_EQ(static_cast(atime) + atime.offset(), time); const struct tm tm1 = static_cast(atime); EXPECT_EQ(tm1.tm_year, tm.tm_year); EXPECT_EQ(tm1.tm_mon, tm.tm_mon); +// We have to contort these a bit to handle offsets > 12, e.g. New Zealand during DST. +// EXPECT_EQ(tm1.tm_mday - (11 + atime.offset() / 3600) / 24 , tm.tm_mday); +// EXPECT_EQ((24 + tm1.tm_hour - atime.offset() / 3600) % 24, tm.tm_hour); EXPECT_EQ(tm1.tm_mday, tm.tm_mday); -// We have to contort this a bit to handle offsets > 12, e.g. New Zealand during DST. - EXPECT_EQ((24 + tm1.tm_hour - atime.offset() / 3600) % 24, tm.tm_hour); + EXPECT_EQ(tm1.tm_hour, tm.tm_hour); EXPECT_EQ(tm1.tm_min, tm.tm_min); } @@ -324,8 +326,10 @@ TEST(gnc_datetime_constructors, test_gncdate_neutral_constructor) TEST(gnc_datetime_functions, test_format) { GncDateTime atime(2394187200); //2045-11-13 12:00:00 Z - //Date only to finesse timezone issues. It will still fail in +12 DST. - EXPECT_EQ(atime.format("%d-%m-%Y"), "13-11-2045"); + if ((atime.offset() / 3600) > 12) + EXPECT_EQ(atime.format("%d-%m-%Y"), "14-11-2045"); + else + EXPECT_EQ(atime.format("%d-%m-%Y"), "13-11-2045"); } TEST(gnc_datetime_functions, test_format_zulu) @@ -343,9 +347,10 @@ TEST(gnc_datetime_functions, test_date) auto ymd = gncd.year_month_day(); EXPECT_EQ(ymd.year, 2045); EXPECT_EQ(ymd.month, 11); - EXPECT_EQ(ymd.day, 13); + EXPECT_EQ(ymd.day - (12 + atime.offset() / 3600) / 24, 13); } - +/* This test works only in the America/LosAngeles time zone and + * there's no way at present to make it more flexible. TEST(gnc_datetime_functions, test_timezone_offset) { @@ -356,3 +361,4 @@ TEST(gnc_datetime_functions, test_timezone_offset) GncDateTime gncdt3(1490525940); //26 Mar 2017 EXPECT_EQ(-25200, gncdt3.offset()); } +*/ diff --git a/libgnucash/engine/test/test-gnc-date.c b/libgnucash/engine/test/test-gnc-date.c index 616909f7db..4b765c8581 100644 --- a/libgnucash/engine/test/test-gnc-date.c +++ b/libgnucash/engine/test/test-gnc-date.c @@ -268,10 +268,10 @@ test_gnc_mktime (void) #endif }; guint ind; - int offset = timegm(&time[4]) - mktime(&time[4]); for (ind = 0; ind < G_N_ELEMENTS (time); ind++) { + int offset = timegm(&time[ind]) - mktime(&time[ind]); time64 secs = gnc_mktime (&time[ind]); #if !PLATFORM(WINDOWS) //The timezone database uses local time for some @@ -319,7 +319,8 @@ test_gnc_mktime_normalization (void) #endif }; guint ind; - int offset = timegm(&normal_time) - mktime(&normal_time); + time_t calc_timegm = timegm(&normal_time); + time_t calc_time = mktime(&normal_time); for (ind = 0; ind < G_N_ELEMENTS (time); ind++) { time64 secs = gnc_mktime (&time[ind]); @@ -330,7 +331,7 @@ test_gnc_mktime_normalization (void) g_assert_cmpint (time[ind].tm_mday, ==, normal_time.tm_mday); g_assert_cmpint (time[ind].tm_mon, ==, normal_time.tm_mon); g_assert_cmpint (time[ind].tm_year, ==, normal_time.tm_year); - g_assert_cmpint (secs, ==, ans - offset); + g_assert_cmpint (secs, ==, ans - (calc_timegm - calc_time)); } } @@ -705,9 +706,11 @@ test_timespecCanonicalDayTime (void) const int sec_per_day = 24 * 3600; const int sec_per_mo = 30 * sec_per_day; const time64 sec_per_yr = 365 * sec_per_day; - const time64 secs = 8 * 3600 + 43 * 60 + 11; - const time64 secs1 = 23 * sec_per_yr + 5 * sec_per_mo + 11 * sec_per_day + 8 * 3600 + 43 * 60 + 11; - const time64 secs2 = 21 * sec_per_yr + 11 * sec_per_mo + 19 * sec_per_day + 21 * 3600 + 9 * 60 + 48; + const time64 secs = 8 * 3600 + 43 * 60 + 11; /* 1970-01-01 08:43:11 Z */ + const time64 secs1 = 23 * sec_per_yr + 5 * sec_per_mo + + 11 * sec_per_day + 8 * 3600 + 43 * 60 + 11; /* 1993-05-11 08:43:60 Z */ + const time64 secs2 = 21 * sec_per_yr + 11 * sec_per_mo + + 19 * sec_per_day + 21 * 3600 + 9 * 60 + 48; /* 1991-11-19 21:09:48 Z */ Timespec t0 = { secs, 0 }; Timespec ta = { secs1, 0 }; @@ -998,9 +1001,9 @@ test_qof_print_date_buff (void) gchar buff[MAX_DATE_LENGTH], ans[MAX_DATE_LENGTH]; gchar *locale = g_strdup (setlocale (LC_TIME, NULL)); - time64 time1 = 154440000; //1974-11-23 12:00:00 - time64 time2 = -281188800; //1961-02-02 12:00:00 - time64 time3 = 2381227200LL; //2045-06-16 12:00:00 + time64 time1 = 154436399; //1974-11-23 10:59:59 + time64 time2 = -281192401; //1961-02-02 10:59:59 + time64 time3 = 2381223599LL; //2045-06-16 10:59:59 struct tm tm1 = {0, 0, 12, 23, 10, 74}; struct tm tm2 = {0, 0, 12, 2, 1, 61}; struct tm tm3 = {0, 0, 12, 16, 5, 145}; @@ -1280,9 +1283,9 @@ test_qof_print_date (void) { gchar *locale = g_strdup (setlocale (LC_TIME, NULL)); char ans[MAX_DATE_LENGTH]; - time64 time1 = 154440000; //1974-11-23 12:00:00 - time64 time2 = -281188800; //1961-02-02 12:00:00 - time64 time3 = 2381227200LL; //2045-06-16 12:00:00 + time64 time1 = 154436399; //1974-11-23 10:59:59 + time64 time2 = -281192401; //1961-02-02 10:59:59 + time64 time3 = 2381223599LL; //2045-06-16 10:59:59 struct tm tm1 = {0, 0, 12, 23, 10, 74}; struct tm tm2 = {0, 0, 12, 2, 1, 61}; struct tm tm3 = {0, 0, 12, 16, 5, 145}; @@ -1812,7 +1815,7 @@ static void test_gnc_dmy2timespec (FixtureB *f, gconstpointer pData) { gchar *msg1 = "[qof_dmy2timespec()] Date computation error from Y-M-D 1257-7-2: Time value is outside the supported year range."; - gint loglevel = G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL; + gint loglevel = G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL; gchar *logdomain = "qof.engine"; TestErrorStruct check = {loglevel, logdomain, msg1, 0}; GLogFunc hdlr = g_log_set_default_handler ((GLogFunc)test_null_handler, &check); @@ -1828,13 +1831,14 @@ test_gnc_dmy2timespec (FixtureB *f, gconstpointer pData) #endif Timespec r_t = gnc_dmy2timespec (f->test[i].day, f->test[i].mon, f->test[i].yr); - int offset = gnc_mktime(&tm) - gnc_timegm(&tm); + struct tm time1 = tm, time2 = tm; + int offset = gnc_mktime(&time1) - gnc_timegm(&time2); if (f->test[i].secs == INT64_MAX) /* We use INT64_MAX as invalid timespec.secs. * As we can't *add* to the max, we can ignore the tz offset in this case. */ g_assert_cmpint (r_t.tv_sec, ==, INT64_MAX); else - g_assert_cmpint (r_t.tv_sec, ==, f->test[i].secs + offset); + g_assert_cmpint (r_t.tv_sec, ==, f->test[i].secs - offset); } g_log_set_default_handler (hdlr, 0); } @@ -1868,7 +1872,7 @@ test_gnc_dmy2timespec_end (FixtureB *f, gconstpointer pData) * As we can't *add* to the max, we can ignore the tz offset in this case. */ g_assert_cmpint (r_t.tv_sec, ==, INT64_MAX); else - g_assert_cmpint (r_t.tv_sec, ==, f->test[i].secs + offset); + g_assert_cmpint (r_t.tv_sec, ==, f->test[i].secs - offset); } g_log_set_default_handler (hdlr, 0); }