Report time zones with no DST transition within a year as not observing

In locations where daylight saving time was once observed or is expected
to be observed in the future, but where no transitions to or from
daylight saving time occurred or will occur within a year of the current
date, act as though DST is not being observed at all.

Set TZ=America/Phoenix to test for this bug.

BUG=crashpad:130
TEST=crashpad_snapshot_test SystemSnapshotMacTest.TimeZone

Change-Id: Ie466b5906eab3c0cf2e51b962a171acb5b16210b
Reviewed-on: https://chromium-review.googlesource.com/438004
Reviewed-by: Robert Sesek <rsesek@chromium.org>
This commit is contained in:
Mark Mentovai 2017-02-07 13:59:18 -05:00
parent 594eb43b58
commit b638163e72
2 changed files with 120 additions and 11 deletions

View File

@ -19,6 +19,8 @@
#include <sys/utsname.h> #include <sys/utsname.h>
#include <time.h> #include <time.h>
#include <algorithm>
#include "base/logging.h" #include "base/logging.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "build/build_config.h" #include "build/build_config.h"
@ -349,34 +351,44 @@ void SystemSnapshotMac::TimeZone(DaylightSavingTimeStatus* dst_status,
PCHECK(localtime_r(&snapshot_time_->tv_sec, &local)) << "localtime_r"; PCHECK(localtime_r(&snapshot_time_->tv_sec, &local)) << "localtime_r";
*standard_name = tzname[0]; *standard_name = tzname[0];
bool found_transition = false;
long probe_gmtoff = local.tm_gmtoff;
if (daylight) { if (daylight) {
// Scan forward and backward, one month at a time, looking for an instance // Scan forward and backward, one month at a time, looking for an instance
// when the observance of daylight saving time is different than it is in // when the observance of daylight saving time is different than it is in
// |local|. // |local|. Its possible that no such instance will be found even with
long probe_gmtoff = local.tm_gmtoff; // |daylight| set. This can happen in locations where daylight saving time
// was once observed or is expected to be observed in the future, but where
// no transitions to or from daylight saving time occurred or will occur
// within a year of the current date. Arizona, which last observed daylight
// saving time in 1967, is an example.
const int kMonthDeltas[] = const int kMonthDeltas[] =
{ 0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6, { 0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6,
7, -7, 8, -8, 9, -9, 10, -10, 11, -11, 12, -12 }; 7, -7, 8, -8, 9, -9, 10, -10, 11, -11, 12, -12 };
for (size_t index = 0; index < arraysize(kMonthDeltas); ++index) { for (size_t index = 0;
// Look at the 15th day of each month at local noon. Set tm_isdst to -1 to index < arraysize(kMonthDeltas) && !found_transition;
// avoid giving mktime() any hints about whether to consider daylight ++index) {
// saving time in effect. mktime() accepts values of tm_mon that are // Look at a day of each month at local noon. Set tm_isdst to -1 to avoid
// outside of its normal range and behaves as expected: if tm_mon is -1, // giving mktime() any hints about whether to consider daylight saving
// it references December of the preceding year, and if it is 12, it // time in effect. mktime() accepts values of tm_mon that are outside of
// its normal range and behaves as expected: if tm_mon is -1, it
// references December of the preceding year, and if it is 12, it
// references January of the following year. // references January of the following year.
tm probe_tm = {}; tm probe_tm = {};
probe_tm.tm_hour = 12; probe_tm.tm_hour = 12;
probe_tm.tm_mday = 15; probe_tm.tm_mday = std::min(local.tm_mday, 28);
probe_tm.tm_mon = local.tm_mon + kMonthDeltas[index]; probe_tm.tm_mon = local.tm_mon + kMonthDeltas[index];
probe_tm.tm_year = local.tm_year; probe_tm.tm_year = local.tm_year;
probe_tm.tm_isdst = -1; probe_tm.tm_isdst = -1;
if (mktime(&probe_tm) != -1 && probe_tm.tm_isdst != local.tm_isdst) { if (mktime(&probe_tm) != -1 && probe_tm.tm_isdst != local.tm_isdst) {
found_transition = true;
probe_gmtoff = probe_tm.tm_gmtoff; probe_gmtoff = probe_tm.tm_gmtoff;
break;
} }
} }
}
if (found_transition) {
*daylight_name = tzname[1]; *daylight_name = tzname[1];
if (!local.tm_isdst) { if (!local.tm_isdst) {
*dst_status = kObservingStandardTime; *dst_status = kObservingStandardTime;

View File

@ -14,11 +14,13 @@
#include "snapshot/mac/system_snapshot_mac.h" #include "snapshot/mac/system_snapshot_mac.h"
#include <stdlib.h>
#include <sys/time.h> #include <sys/time.h>
#include <time.h> #include <time.h>
#include <string> #include <string>
#include "base/strings/stringprintf.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "snapshot/mac/process_reader.h" #include "snapshot/mac/process_reader.h"
@ -127,6 +129,39 @@ TEST_F(SystemSnapshotMacTest, MachineDescription) {
EXPECT_FALSE(system_snapshot().MachineDescription().empty()); EXPECT_FALSE(system_snapshot().MachineDescription().empty());
} }
class ScopedSetTZ {
public:
ScopedSetTZ(const std::string& tz) {
const char* old_tz = getenv(kTZ);
old_tz_set_ = old_tz;
if (old_tz_set_) {
old_tz_.assign(old_tz);
}
EXPECT_EQ(0, setenv(kTZ, tz.c_str(), 1)) << ErrnoMessage("setenv");
tzset();
}
~ScopedSetTZ() {
if (old_tz_set_) {
EXPECT_EQ(0, setenv(kTZ, old_tz_.c_str(), 1)) << ErrnoMessage("setenv");
} else {
EXPECT_EQ(0, unsetenv(kTZ)) << ErrnoMessage("unsetenv");
}
tzset();
}
private:
std::string old_tz_;
bool old_tz_set_;
static constexpr char kTZ[] = "TZ";
DISALLOW_COPY_AND_ASSIGN(ScopedSetTZ);
};
constexpr char ScopedSetTZ::kTZ[];
TEST_F(SystemSnapshotMacTest, TimeZone) { TEST_F(SystemSnapshotMacTest, TimeZone) {
SystemSnapshot::DaylightSavingTimeStatus dst_status; SystemSnapshot::DaylightSavingTimeStatus dst_status;
int standard_offset_seconds; int standard_offset_seconds;
@ -169,6 +204,68 @@ TEST_F(SystemSnapshotMacTest, TimeZone) {
EXPECT_NE(standard_name, daylight_name); EXPECT_NE(standard_name, daylight_name);
} }
// Test a variety of time zones. Some of these observe daylight saving time,
// some dont. Some used to but no longer do. Some have uncommon UTC offsets.
const struct {
const char* tz;
bool observes_dst;
float standard_offset_hours;
float daylight_offset_hours;
const char* standard_name;
const char* daylight_name;
} kTestTimeZones[] = {
{"America/Anchorage", true, -9, -8, "AKST", "AKDT"},
{"America/Chicago", true, -6, -5, "CST", "CDT"},
{"America/Denver", true, -7, -6, "MST", "MDT"},
{"America/Halifax", true, -4, -3, "AST", "ADT"},
{"America/Los_Angeles", true, -8, -7, "PST", "PDT"},
{"America/New_York", true, -5, -4, "EST", "EDT"},
{"America/Phoenix", false, -7, -7, "MST", "MST"},
{"Asia/Karachi", false, 5, 5, "PKT", "PKT"},
{"Asia/Kolkata", false, 5.5, 5.5, "IST", "IST"},
{"Asia/Shanghai", false, 8, 8, "CST", "CST"},
{"Asia/Tokyo", false, 9, 9, "JST", "JST"},
{"Australia/Adelaide", true, 9.5, 10.5, "ACST", "ACDT"},
{"Australia/Brisbane", false, 10, 10, "AEST", "AEST"},
{"Australia/Darwin", false, 9.5, 9.5, "ACST", "ACST"},
{"Australia/Eucla", false, 8.75, 8.75, "ACWST", "ACWST"},
{"Australia/Lord_Howe", true, 10.5, 11, "LHST", "LHDT"},
{"Australia/Perth", false, 8, 8, "AWST", "AWST"},
{"Australia/Sydney", true, 10, 11, "AEST", "AEDT"},
{"Europe/Bucharest", true, 2, 3, "EET", "EEST"},
{"Europe/London", true, 0, 1, "GMT", "BST"},
{"Europe/Moscow", false, 3, 3, "MSK", "MSK"},
{"Europe/Paris", true, 1, 2, "CET", "CEST"},
{"Europe/Reykjavik", false, 0, 0, "UTC", "UTC"},
{"Pacific/Auckland", true, 12, 13, "NZST", "NZDT"},
{"Pacific/Honolulu", false, -10, -10, "HST", "HST"},
{"UTC", false, 0, 0, "UTC", "UTC"},
};
for (size_t index = 0; index < arraysize(kTestTimeZones); ++index) {
const auto& test_time_zone = kTestTimeZones[index];
const char* tz = test_time_zone.tz;
SCOPED_TRACE(base::StringPrintf("index %zu, tz %s", index, tz));
{
ScopedSetTZ set_tz(tz);
system_snapshot().TimeZone(&dst_status,
&standard_offset_seconds,
&daylight_offset_seconds,
&standard_name,
&daylight_name);
}
EXPECT_EQ(test_time_zone.observes_dst,
dst_status != SystemSnapshot::kDoesNotObserveDaylightSavingTime);
EXPECT_EQ(test_time_zone.standard_offset_hours * 60 * 60,
standard_offset_seconds);
EXPECT_EQ(test_time_zone.daylight_offset_hours * 60 * 60,
daylight_offset_seconds);
EXPECT_EQ(test_time_zone.standard_name, standard_name);
EXPECT_EQ(test_time_zone.daylight_name, daylight_name);
}
} }
} // namespace } // namespace