Bug 137017 - date of transaction change with time zone change

First step: Change the timestamp to 11:00 UTC instead of midnight local,
adjusting by an hour or two if the local timezone is one near the
International Date Line to keep the date from flipping around.

Scrub all old entries to make current files correct.

Note: This effectively disposes of the distinction of close-book transactions
having a noon instead of midnight timestamp as a way to distinguish them
from regular transactions, but that distinction doesn't seem to be used;
xaccTransIsCloseBook() is used instead.
This commit is contained in:
John Ralls 2016-07-02 16:03:55 -07:00
parent 6a81738e96
commit 51e29e7836
7 changed files with 119 additions and 13 deletions

View File

@ -36,6 +36,7 @@
#include "Account.h"
#include "Transaction.h"
#include <Scrub.h>
#include "gnc-lot.h"
#include "engine-helpers.h"
@ -376,6 +377,7 @@ query_transactions( GncSqlBackend* be, GncSqlStatement* stmt )
if ( tx != NULL )
{
tx_list = g_list_prepend( tx_list, tx );
xaccTransScrubPostedDate (tx);
}
row = gnc_sql_result_get_next_row( result );
}

View File

@ -278,6 +278,7 @@ add_transaction_local(sixtp_gdv2 *data, Transaction *trn)
xaccTransSetCurrency);
xaccTransScrubCurrency (trn);
xaccTransScrubPostedDate (trn);
xaccTransCommitEdit (trn);
data->counter.transactions_loaded++;

View File

@ -1373,4 +1373,17 @@ xaccScrubUtilityGetOrMakeAccount (Account *root, gnc_commodity * currency,
return acc;
}
void
xaccTransScrubPostedDate (Transaction *trans)
{
time64 orig = xaccTransGetDate(trans);
GDate date = xaccTransGetDatePostedGDate(trans);
Timespec ts = gdate_to_timespec(date);
if (orig && orig != ts.tv_sec)
{
/* xaccTransSetDatePostedTS handles committing the change. */
xaccTransSetDatePostedTS(trans, &ts);
}
}
/* ==================== END OF FILE ==================== */

View File

@ -142,8 +142,18 @@ void xaccAccountTreeScrubCommodities (Account *acc);
*/
void xaccAccountTreeScrubQuoteSources (Account *root, gnc_commodity_table *table);
/** Removes empty "notes", "placeholder", and "hbci" KVP slots from Accounts. */
void xaccAccountScrubKvp (Account *account);
/** Changes Transaction date_posted timestamps from 00:00 local to 11:00 UTC.
* 11:00 UTC is the same day local time in almost all timezones, the exceptions
* being the -12, +13, and +14 timezones along the International Date Line. If
* Local time is set to one of these timezones then the new date_posted time
* will be adjusted as needed to ensure that the date doesn't change there. This
* change was made for v2.6.14 to partially resolve bug 137017.
*/
void xaccTransScrubPostedDate (Transaction *trans);
#endif /* XACC_SCRUB_H */
/** @} */
/** @} */

View File

@ -183,6 +183,7 @@ gnc_g_date_time_to_local (GDateTime* gdt)
typedef struct
{
GDateTime *(*new_local)(gint, gint, gint, gint, gint, gdouble);
GDateTime *(*new_utc)(gint, gint, gint, gint, gint, gdouble);
GDateTime *(*adjust_for_dst)(GDateTime *, GTimeZone *);
GDateTime *(*new_from_unix_local)(time64);
GDateTime *(*new_from_timeval_local)(const GTimeVal *);
@ -195,6 +196,7 @@ void
_gnc_date_time_init (_GncDateTime *gncdt)
{
gncdt->new_local = gnc_g_date_time_new_local;
gncdt->new_utc = g_date_time_new_utc;
gncdt->adjust_for_dst = gnc_g_date_time_adjust_for_dst;
gncdt->new_from_unix_local = gnc_g_date_time_new_from_unix_local;
gncdt->new_from_timeval_local = gnc_g_date_time_new_from_timeval_local;
@ -372,12 +374,13 @@ gnc_timegm (struct tm* time)
gdt = g_date_time_new_utc (time->tm_year + 1900, time->tm_mon,
time->tm_mday, time->tm_hour, time->tm_min,
(gdouble)(time->tm_sec));
time->tm_mon = time->tm_mon > 0 ? time->tm_mon - 1 : 11;
// Watch out: struct tm has wday=0..6 with Sunday=0, but GDateTime has wday=1..7 with Sunday=7.
time->tm_wday = g_date_time_get_day_of_week (gdt) % 7;
time->tm_yday = g_date_time_get_day_of_year (gdt);
time->tm_isdst = g_date_time_is_daylight_savings (gdt);
if (gdt == NULL)
{
PERR("Failed to get valid GDateTime with struct tm: %d-%d-%d %d:%d:%d",
time->tm_year + 1900, time->tm_mon, time->tm_mday, time->tm_hour,
time->tm_min, time->tm_sec);
return 0;
}
secs = g_date_time_to_unix (gdt);
g_date_time_unref (gdt);
return secs;
@ -1551,6 +1554,7 @@ gnc_dmy2timespec_internal (int day, int month, int year, gboolean start_of_day)
return result;
}
Timespec
gnc_dmy2timespec (int day, int month, int year)
{
@ -1563,6 +1567,29 @@ gnc_dmy2timespec_end (int day, int month, int year)
return gnc_dmy2timespec_internal (day, month, year, FALSE);
}
Timespec
gnc_dmy2timespec_neutral (int day, int month, int year)
{
struct tm date;
Timespec ts = {0, 0};
GTimeZone *zone = gnc_g_time_zone_new_local();
GDateTime *gdt = gnc_g_date_time_new_local (year, month, day, 12, 0, 0.0);
int interval = g_time_zone_find_interval (zone, G_TIME_TYPE_STANDARD,
g_date_time_to_unix(gdt));
int offset = g_time_zone_get_offset(gnc_g_time_zone_new_local(),
interval) / 3600;
g_date_time_unref (gdt);
memset (&date, 0, sizeof(struct tm));
date.tm_year = year - 1900;
date.tm_mon = month - 1;
date.tm_mday = day;
date.tm_hour = offset < -11 ? -offset : offset > 13 ? 24 - offset : 11;
date.tm_min = 0;
date.tm_sec = 0;
ts.tv_sec = gnc_timegm(&date);
return ts;
}
/********************************************************************\
\********************************************************************/
@ -1648,9 +1675,9 @@ GDate* gnc_g_date_new_today ()
Timespec gdate_to_timespec (GDate d)
{
gnc_gdate_range_check (&d);
return gnc_dmy2timespec(g_date_get_day(&d),
g_date_get_month(&d),
g_date_get_year(&d));
return gnc_dmy2timespec_neutral (g_date_get_day(&d),
g_date_get_month(&d),
g_date_get_year(&d));
}
static void

View File

@ -351,6 +351,15 @@ Timespec gnc_dmy2timespec (gint day, gint month, gint year);
/** Same as gnc_dmy2timespec, but last second of the day */
Timespec gnc_dmy2timespec_end (gint day, gint month, gint year);
/** Converts a day, month, and year to a Timespec representing 11:00:00 UTC
* 11:00:00 UTC falls on the same time in almost all timezones, the exceptions
* being the +13, +14, and -12 timezones used by countries along the
* International Date Line. Since users in those timezones would see dates
* immediately change by one day, the function checks the current timezone for
* those changes and adjusts the UTC time so that the date will be consistent.
*/
Timespec gnc_dmy2timespec_neutral (gint day, gint month, gint year);
/** The gnc_iso8601_to_timespec_gmt() routine converts an ISO-8601 style
* date/time string to Timespec. Please note that ISO-8601 strings
* are a representation of Universal Time (UTC), and as such, they

View File

@ -47,6 +47,7 @@ void test_suite_gnc_date ( void );
typedef struct
{
GDateTime *(*new_local)(gint, gint, gint, gint, gint, gdouble);
GDateTime *(*new_utc)(gint, gint, gint, gint, gint, gdouble);
GDateTime *(*adjust_for_dst)(GDateTime *, GTimeZone *);
GDateTime *(*new_from_unix_local)(time64);
GDateTime *(*new_from_timeval_local)(GTimeVal *);
@ -1967,6 +1968,48 @@ test_gnc_dmy2timespec_end (void)
g_date_time_unref (gdt3);
g_date_time_unref (gdt4);
}
/*gnc_dmy2timespec_neutral*/
static void
test_gnc_dmy2timespec_neutral (void)
{
GDateTime *gdt1 = gncdt.new_utc (1999, 7, 21, 11, 0, 0);
GDateTime *gdt2 = gncdt.new_utc (1918, 3, 31, 11, 0, 0);
GDateTime *gdt3 = gncdt.new_utc (1918, 4, 1, 11, 0, 0);
GDateTime *gdt4 = gncdt.new_utc (2057, 11, 20, 11, 0, 0);
gint day, mon, yr;
Timespec t, r_t;
t = g_date_time_to_timespec (gdt1);
g_date_time_get_ymd (gdt1, &yr, &mon, &day);
r_t = gnc_dmy2timespec_neutral (day, mon, yr);
g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
t = g_date_time_to_timespec (gdt2);
g_date_time_get_ymd (gdt2, &yr, &mon, &day);
r_t = gnc_dmy2timespec_neutral (day, mon, yr);
g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
t = g_date_time_to_timespec (gdt3);
g_date_time_get_ymd (gdt3, &yr, &mon, &day);
r_t = gnc_dmy2timespec_neutral (day, mon, yr);
g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
t = g_date_time_to_timespec (gdt4);
g_date_time_get_ymd (gdt4, &yr, &mon, &day);
r_t = gnc_dmy2timespec_neutral (day, mon, yr);
g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
g_date_time_unref (gdt1);
g_date_time_unref (gdt2);
g_date_time_unref (gdt3);
g_date_time_unref (gdt4);
}
/* gnc_timezone
long int
gnc_timezone (const struct tm *tm)// C: 5 in 2 Local: 2:0:0
@ -2086,10 +2129,10 @@ Timespec gdate_to_timespec (GDate d)// C: 7 in 6 Local: 0:0:0
static void
test_gdate_to_timespec (void)
{
GDateTime *gdt1 = gncdt.new_local (1999, 7, 21, 0, 0, 0);
GDateTime *gdt2 = gncdt.new_local (1918, 3, 31, 0, 0, 0);
GDateTime *gdt3 = gncdt.new_local (1918, 4, 1, 0, 0, 0);
GDateTime *gdt4 = gncdt.new_local (2057, 11, 20, 0, 0, 0);
GDateTime *gdt1 = gncdt.new_utc (1999, 7, 21, 11, 0, 0);
GDateTime *gdt2 = gncdt.new_utc (1918, 3, 31, 11, 0, 0);
GDateTime *gdt3 = gncdt.new_utc (1918, 4, 1, 11, 0, 0);
GDateTime *gdt4 = gncdt.new_utc (2057, 11, 20, 11, 0, 0);
gint day, mon, yr;
Timespec t, r_t;
@ -2411,6 +2454,7 @@ test_suite_gnc_date (void)
// GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec internal", test_gnc_dmy2timespec_internal);
GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec", test_gnc_dmy2timespec);
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, "timespecFromTime t", test_timespecFromtime64);
// GNC_TEST_ADD_FUNC (suitename, "timespec now", test_timespec_now);