From af1bc45021aaa9c844a9e204ead402f155835e36 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sun, 19 Nov 2017 09:36:44 -0800 Subject: [PATCH] A better way to handle MySQL's 0000-00-00 invalid date indicator. --- src/backend/sql/gnc-backend-sql.c | 43 +++++++++++++------ src/libqof/qof/gnc-date.c | 29 ++++--------- src/libqof/qof/gnc-date.h | 5 +-- src/libqof/qof/test/test-gnc-date.c | 66 ++++++++++++++++------------- 4 files changed, 78 insertions(+), 65 deletions(-) diff --git a/src/backend/sql/gnc-backend-sql.c b/src/backend/sql/gnc-backend-sql.c index e9dfc7521a..7d169d1248 100644 --- a/src/backend/sql/gnc-backend-sql.c +++ b/src/backend/sql/gnc-backend-sql.c @@ -30,6 +30,7 @@ #include "config.h" #include +#include #include #include #include @@ -1904,27 +1905,43 @@ load_timespec( const GncSqlBackend* be, GncSqlRow* row, } else { + /* MySQL's TIMESTAMP type is not compliant with the SQL + * standard in that it is valid only from 1970-01-01 00:00:01 + * to 2038-01-15 23:59:59. Times outside that range are set to + * "0000-00-00 00:00:00"; depending on the libdbi version we + * may get either the string value or the time64 representing + * it. In either case leave the timespec at 0. + */ if ( G_VALUE_HOLDS_INT64( val ) ) { - timespecFromTime64 (&ts, (time64)(g_value_get_int64 (val))); - isOK = TRUE; + const time64 MINTIME = INT64_C(-62135596800); + const time64 MAXTIME = INT64_C(253402300799); + const time64 t = (time64)(g_value_get_int64 (val)); + if (t >= MINTIME && t <= MAXTIME) + { + timespecFromTime64 (&ts, t); + } + isOK = TRUE; } else if (G_VALUE_HOLDS_STRING (val)) { const gchar* s = g_value_get_string( val ); if ( s != NULL ) { - gchar* buf; - buf = g_strdup_printf( "%c%c%c%c-%c%c-%c%c %c%c:%c%c:%c%c", - s[0], s[1], s[2], s[3], - s[4], s[5], - s[6], s[7], - s[8], s[9], - s[10], s[11], - s[12], s[13] ); - ts = gnc_iso8601_to_timespec_gmt( buf ); - g_free( buf ); - isOK = TRUE; + if (! g_str_has_prefix (s, "0000-00-00")) + { + gchar* buf; + buf = g_strdup_printf( "%c%c%c%c-%c%c-%c%c %c%c:%c%c:%c%c", + s[0], s[1], s[2], s[3], + s[4], s[5], + s[6], s[7], + s[8], s[9], + s[10], s[11], + s[12], s[13] ); + ts = gnc_iso8601_to_timespec_gmt( buf ); + g_free( buf ); + } + isOK = TRUE; } } else diff --git a/src/libqof/qof/gnc-date.c b/src/libqof/qof/gnc-date.c index 3af9be9055..1c652dedf0 100644 --- a/src/libqof/qof/gnc-date.c +++ b/src/libqof/qof/gnc-date.c @@ -73,17 +73,6 @@ # define GNC_T_FMT "%r" #endif -/* t < MINTIME is probably from a bad conversion from t 0 to - * 0000-00-00, so restore it to the Unix Epoch. t anywhere near - * MAXTIME is obviously an error, but we don't want to crash with a - * bad date-time so just clamp it to MAXTIME. - */ -static inline time64 -clamp_time(time64 t) -{ - return t < MINTIME ? 0 : t > MAXTIME ? MAXTIME : t; -} - const char *gnc_default_strftime_date_format = #ifdef G_OS_WIN32 /* The default date format for use with strftime in Win32. */ @@ -152,7 +141,7 @@ GDateTime* gnc_g_date_time_new_from_unix_local (time64 time) { GTimeZone *tz = gnc_g_time_zone_new_local (); - GDateTime *gdt = g_date_time_new_from_unix_utc (clamp_time (time)); + GDateTime *gdt = g_date_time_new_from_unix_utc (time); if (gdt) gdt = gnc_g_date_time_adjust_for_dst (gdt, tz); return gdt; @@ -260,7 +249,7 @@ struct tm* gnc_localtime_r (const time64 *secs, struct tm* time) { guint index = 0; - GDateTime *gdt = gnc_g_date_time_new_from_unix_local (clamp_time (*secs)); + GDateTime *gdt = gnc_g_date_time_new_from_unix_local (*secs); g_return_val_if_fail (gdt != NULL, NULL); gnc_g_date_time_fill_struct_tm (gdt, time); @@ -282,7 +271,7 @@ struct tm* gnc_gmtime (const time64 *secs) { struct tm *time; - GDateTime *gdt = g_date_time_new_from_unix_utc (clamp_time (*secs)); + GDateTime *gdt = g_date_time_new_from_unix_utc (*secs); g_return_val_if_fail (gdt != NULL, NULL); time = g_slice_alloc0 (sizeof (struct tm)); gnc_g_date_time_fill_struct_tm (gdt, time); @@ -400,10 +389,10 @@ gnc_timegm (struct tm* time) gchar* gnc_ctime (const time64 *secs) { - GDateTime *gdt = gnc_g_date_time_new_from_unix_local (clamp_time (*secs)); - gchar *string = g_date_time_format (gdt, "%a %b %e %H:%M:%S %Y"); - g_date_time_unref (gdt); - return string; + GDateTime *gdt = gnc_g_date_time_new_from_unix_local (*secs); + gchar *string = g_date_time_format (gdt, "%a %b %e %H:%M:%S %Y"); + g_date_time_unref (gdt); + return string; } time64 @@ -898,7 +887,7 @@ size_t qof_print_date_buff (char * buff, size_t len, time64 t) { struct tm theTime; - time64 bt = clamp_time (t); + time64 bt = t; size_t actual; if (!buff) return 0 ; if (!gnc_localtime_r(&bt, &theTime)) @@ -1646,7 +1635,7 @@ gnc_timezone (const struct tm *tm) void timespecFromTime64 ( Timespec *ts, time64 t ) { - ts->tv_sec = clamp_time (t); + ts->tv_sec = t; ts->tv_nsec = 0; } diff --git a/src/libqof/qof/gnc-date.h b/src/libqof/qof/gnc-date.h index ad5bcf0319..acdbe65efd 100644 --- a/src/libqof/qof/gnc-date.h +++ b/src/libqof/qof/gnc-date.h @@ -70,7 +70,7 @@ #include #include -#include + /** * Many systems, including Microsoft Windows and BSD-derived Unixes * like Darwin, are retaining the int-32 typedef for time_t. Since @@ -100,8 +100,7 @@ extern const char *gnc_default_strftime_date_format; /** The maximum length of a string created by the date printers */ #define MAX_DATE_LENGTH 34 -#define MAXTIME INT64_C(253402300799) -#define MINTIME INT64_C(-62135596800) + /** Constants *******************************************************/ /** \brief UTC date format string. diff --git a/src/libqof/qof/test/test-gnc-date.c b/src/libqof/qof/test/test-gnc-date.c index c54d2f0bcb..acd48f9127 100644 --- a/src/libqof/qof/test/test-gnc-date.c +++ b/src/libqof/qof/test/test-gnc-date.c @@ -58,17 +58,6 @@ typedef struct static _GncDateTime gncdt; extern void _gnc_date_time_init (_GncDateTime *); -/* t < MINTIME is probably from a bad conversion from t 0 to - * 0000-00-00, so restore it to the Unix Epoch. t anywhere near - * MAXTIME is obviously an error, but we don't want to crash with a - * bad date-time so just clamp it to MAXTIME. - */ -static inline time64 -clamp_time(time64 t) -{ - return t < MINTIME ? 0 : t > MAXTIME ? MAXTIME : t; -} - /* gnc_localtime just creates a tm on the heap and calls * gnc_localtime_r with it, so this suffices to test both. */ @@ -81,6 +70,18 @@ test_gnc_localtime (void) // difference between g_date_time and tm->tm_wday) }; guint ind; +#if defined(__clang__) && __clang_major__ < 6 +#define _func "struct tm *gnc_localtime_r(const time64 *, struct tm *)" +#else +#define _func "gnc_localtime_r" +#endif + gchar *msg = _func ": assertion " _Q "gdt != NULL' failed"; +#undef _func + gint loglevel = G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL; + gchar *logdomain = "qof"; + TestErrorStruct check = {loglevel, logdomain, msg, 0}; + GLogFunc hdlr = g_log_set_default_handler ((GLogFunc)test_null_handler, &check); + g_test_log_set_fatal_handler ((GTestLogFatalFunc)test_checked_handler, &check); for (ind = 0; ind < G_N_ELEMENTS (secs); ind++) { @@ -111,6 +112,8 @@ test_gnc_localtime (void) g_date_time_unref (gdt); gnc_tm_free (time); } + g_assert_cmpint (check.hits, ==, 1); + g_log_set_default_handler (hdlr, NULL); } static void @@ -126,23 +129,35 @@ test_gnc_gmtime (void) { 48, 51, 23, 18, 11, 69, 4, 352, 0, 0, NULL }, { 41, 12, 0, 6, 0, 70, 2, 6, 0, 0, NULL }, { 32, 30, 2, 3, 11, 92, 4, 338, 0, 0, NULL }, - { 59, 59, 23, 31, 11, 8099, 5, 365, 0, 0, NULL }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL }, { 6, 47, 16, 7, 3, 107, 6, 97, 0, 0, NULL }, #else { 6, 41, 2, 24, 9, -1301, 4, 297, 0 }, { 48, 51, 23, 18, 11, 69, 4, 352, 0 }, { 41, 12, 0, 6, 0, 70, 2, 6, 0 }, { 32, 30, 2, 3, 11, 92, 4, 338, 0 }, - { 59, 50, 23, 31, 11, 8099, 5, 365, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 6, 47, 16, 7, 3, 107, 6, 97, 0 }, #endif }; guint ind; +#if defined(__clang__) && __clang_major__ < 6 +#define _func "struct tm *gnc_gmtime(const time64 *)" +#else +#define _func "gnc_gmtime" +#endif + gchar *msg = _func ": assertion " _Q "gdt != NULL' failed"; +#undef _func + gint loglevel = G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL; + gchar *logdomain = "qof"; + TestErrorStruct check = {loglevel, logdomain, msg, 0}; + GLogFunc hdlr = g_log_set_default_handler ((GLogFunc)test_null_handler, &check); + g_test_log_set_fatal_handler ((GTestLogFatalFunc)test_checked_handler, &check); for (ind = 0; ind < G_N_ELEMENTS (secs); ind++) { struct tm* time = gnc_gmtime (&secs[ind]); - GDateTime *gdt = g_date_time_new_from_unix_utc (clamp_time (secs[ind])); + GDateTime *gdt = g_date_time_new_from_unix_utc (secs[ind]); if (gdt == NULL) { g_assert (time == NULL); @@ -163,6 +178,8 @@ test_gnc_gmtime (void) g_date_time_unref (gdt); gnc_tm_free (time); } + g_assert_cmpint (check.hits, ==, 1); + g_log_set_default_handler (hdlr, NULL); } static void @@ -2020,23 +2037,14 @@ gnc_timezone (const struct tm *tm)// C: 5 in 2 Local: 2:0:0 test_gnc_timezone (void) { }*/ -/* timespecFromTime64 +/* timespecFromtime64 void -timespecFromTime64( Timespec *ts, time64 t )// C: 22 in 11 Local: 0:0:0 +timespecFromtime64( Timespec *ts, time64 t )// C: 22 in 11 Local: 0:0:0 */ -static void -test_timespecFromTime64 (void) +/* static void +test_timespecFromtime64 (void) { - Timespec ts = {-9999, 0}; - timespecFromTime64 (&ts, MINTIME - 1); - g_assert_cmpint (0, ==, ts.tv_sec); - timespecFromTime64 (&ts, MINTIME + 1); - g_assert_cmpint (MINTIME + 1, ==, ts.tv_sec); - timespecFromTime64 (&ts, MAXTIME + 1); - g_assert_cmpint (MAXTIME, ==, ts.tv_sec); - timespecFromTime64 (&ts, MAXTIME - 1); - g_assert_cmpint (MAXTIME - 1, ==, ts.tv_sec); -} +}*/ /* timespec_now Timespec timespec_now()// C: 2 in 2 Local: 0:0:0 @@ -2466,7 +2474,7 @@ test_suite_gnc_date (void) GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec end", test_gnc_dmy2timespec_end); GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec Neutral", test_gnc_dmy2timespec_neutral); // GNC_TEST_ADD_FUNC (suitename, "gnc timezone", test_gnc_timezone); - GNC_TEST_ADD_FUNC (suitename, "timespecFromTime64", test_timespecFromTime64); +// GNC_TEST_ADD_FUNC (suitename, "timespecFromTime t", test_timespecFromtime64); // GNC_TEST_ADD_FUNC (suitename, "timespec now", test_timespec_now); // GNC_TEST_ADD_FUNC (suitename, "timespecToTime t", test_timespecTotime64); GNC_TEST_ADD_FUNC (suitename, "timespec to gdate", test_timespec_to_gdate);