Fix DST calculation error.

The symptom was that in 2017 the PDT->PST transition was set a week
late. The cause was that the timezone lookup function went the wrong
way, finding the *next* timezone rule instead of the desired one because
timezone rules are stored for the year that they start rather than when
they end. Fix reverses the search to find the correct timezone rule.

Commit includes new tests to detect the problem.
This commit is contained in:
John Ralls 2017-03-17 15:18:00 -07:00
parent e66dd12aa9
commit 66e81040cb
4 changed files with 35 additions and 16 deletions

View File

@ -692,18 +692,18 @@ TimeZoneProvider::TimeZoneProvider(const std::string& tzname) : zone_vector {}
{
if(construct(tzname))
return;
std::cerr << tzname << " invalid, trying TZ environment variable.\n";
DEBUG("%s invalid, trying TZ environment variable.\n", tzname.c_str());
const char* tz_env = getenv("TZ");
if(tz_env && construct(tz_env))
return;
std::cerr << "No valid $TZ, resorting to /etc/localtime.\n";
DEBUG("No valid $TZ, resorting to /etc/localtime.\n");
try
{
parse_file("/etc/localtime");
}
catch(const std::invalid_argument& env)
{
std::cerr << "/etc/localtime invalid, resorting to GMT.";
DEBUG("/etc/localtime invalid, resorting to GMT.");
TZ_Ptr zone(new PTZ("UTC0"));
zone_vector.push_back(std::make_pair(max_year, zone));
}
@ -714,15 +714,12 @@ TimeZoneProvider::TimeZoneProvider(const std::string& tzname) : zone_vector {}
TZ_Ptr
TimeZoneProvider::get(int year) const noexcept
{
auto iter = find_if(zone_vector.begin(), zone_vector.end(),
[=](TZ_Entry e) { return e.first >= year; });
if (iter == zone_vector.end())
auto iter = find_if(zone_vector.rbegin(), zone_vector.rend(),
[=](TZ_Entry e) { return e.first <= year; });
if (iter == zone_vector.rend())
{
/* This shouldn't happen, but if it does: */
PERR("TimeZoneProvider::get was unable to get a timezone for year %d",
year);
if (!zone_vector.empty())
return zone_vector.back().second;
return zone_vector.front().second;
return TZ_Ptr(new PTZ("UTC0"));
}
return iter->second;

View File

@ -95,3 +95,14 @@ TEST(gnc_datetime_functions, test_date)
EXPECT_EQ(ymd.month, 11);
EXPECT_EQ(ymd.day, 13);
}
TEST(gnc_datetime_functions, test_timezone_offset)
{
GncDateTime gncdt1(1488797940); //6 Mar 2017
EXPECT_EQ(-28800, gncdt1.offset());
GncDateTime gncdt2(1489661940); //16 Mar 2017 10:59 Z
EXPECT_EQ(-25200, gncdt2.offset());
GncDateTime gncdt3(1490525940); //26 Mar 2017
EXPECT_EQ(-25200, gncdt3.offset());
}

View File

@ -44,8 +44,8 @@ TEST(gnc_timezone_constructors, test_pacific_time_constructor)
std::string timezone("America/Los_Angeles");
#endif
TimeZoneProvider tzp (timezone);
EXPECT_NO_THROW (tzp.get(2006));
TZ_Ptr tz = tzp.get (2006);
EXPECT_NO_THROW (tzp.get(2012));
TZ_Ptr tz = tzp.get (2012);
EXPECT_FALSE(tz->std_zone_abbrev().empty());
#if PLATFORM(WINDOWS)
@ -54,7 +54,9 @@ TEST(gnc_timezone_constructors, test_pacific_time_constructor)
EXPECT_TRUE(tz->std_zone_abbrev() == "PST");
EXPECT_TRUE(tz->dst_zone_abbrev() == "PDT");
#endif
EXPECT_TRUE(tz->base_utc_offset().hours() == -8);
EXPECT_EQ(-8, tz->base_utc_offset().hours());
EXPECT_EQ(12, tz->dst_local_start_time (2017).date().day());
}
#if !PLATFORM(WINDOWS)

View File

@ -104,7 +104,7 @@ typedef struct
typedef struct
{
TimeMap test[8];
TimeMap test[9];
} FixtureB;
static void
@ -118,6 +118,7 @@ setup_begin(FixtureB *f, gconstpointer pData)
f->test[5] = (TimeMap){2017, 02, 29, INT64_C(1488326400)}; /*invalid day*/
f->test[6] = (TimeMap){2017, 02, 33, INT64_C(1488672000)}; /*invalid day*/
f->test[7] = (TimeMap){2017, 13, 29, INT64_C(1517184000)}; /*invalid month*/
f->test[8] = (TimeMap){2017, 03, 16, INT64_C(1489622400)};
}
static void
@ -131,6 +132,7 @@ setup_neutral(FixtureB *f, gconstpointer pData)
f->test[5] = (TimeMap){2017, 02, 29, INT64_MAX};
f->test[6] = (TimeMap){2017, 02, 33, INT64_MAX};
f->test[7] = (TimeMap){2017, 13, 29, INT64_MAX};
f->test[8] = (TimeMap){2017, 03, 16, INT64_C(1489661940)};
}
static void
@ -144,6 +146,7 @@ setup_end(FixtureB *f, gconstpointer pData)
f->test[5] = (TimeMap){2017, 02, 29, INT64_C(1488412799)};
f->test[6] = (TimeMap){2017, 02, 33, INT64_C(1488758399)};
f->test[7] = (TimeMap){2017, 13, 29, INT64_C(1517270399)};
f->test[8] = (TimeMap){2017, 03, 16, INT64_C(1489708799)};
}
void test_suite_gnc_date ( void );
@ -1974,12 +1977,18 @@ test_gdate_to_timespec (FixtureB *f, gconstpointer pData)
g_test_log_set_fatal_handler ((GTestLogFatalFunc)test_checked_handler, &check);
for (int i = 0; i < sizeof(f->test)/sizeof(TimeMap); ++i)
{
GDate gd;
GDate gd, gd2;
Timespec r_t;
g_date_clear(&gd, 1);
g_date_clear(&gd2, 1);
g_date_set_dmy(&gd, f->test[i].day, f->test[i].mon, f->test[i].yr);
r_t = gdate_to_timespec(gd);
g_assert_cmpint (r_t.tv_sec, ==, f->test[i].secs);
if (f->test[i].secs < INT64_MAX)
{
gd2 = timespec_to_gdate(r_t);
g_assert (g_date_compare (&gd2, &gd) == 0);
}
}
g_log_set_default_handler (hdlr, 0);
}