1 // Copyright 2017 The Crashpad Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "snapshot/posix/timezone.h"
16 
17 #include <stdlib.h>
18 #include <sys/time.h>
19 #include <time.h>
20 
21 #include <string>
22 
23 #include "base/macros.h"
24 #include "base/stl_util.h"
25 #include "base/strings/stringprintf.h"
26 #include "gtest/gtest.h"
27 #include "test/errors.h"
28 
29 namespace crashpad {
30 namespace test {
31 namespace {
32 
33 class ScopedSetTZ {
34  public:
ScopedSetTZ(const std::string & tz)35   ScopedSetTZ(const std::string& tz) {
36     const char* old_tz = getenv(kTZ);
37     old_tz_set_ = old_tz;
38     if (old_tz_set_) {
39       old_tz_.assign(old_tz);
40     }
41 
42     EXPECT_EQ(setenv(kTZ, tz.c_str(), 1), 0) << ErrnoMessage("setenv");
43     tzset();
44   }
45 
~ScopedSetTZ()46   ~ScopedSetTZ() {
47     if (old_tz_set_) {
48       EXPECT_EQ(setenv(kTZ, old_tz_.c_str(), 1), 0) << ErrnoMessage("setenv");
49     } else {
50       EXPECT_EQ(unsetenv(kTZ), 0) << ErrnoMessage("unsetenv");
51     }
52     tzset();
53   }
54 
55  private:
56   std::string old_tz_;
57   bool old_tz_set_;
58 
59   static constexpr char kTZ[] = "TZ";
60 
61   DISALLOW_COPY_AND_ASSIGN(ScopedSetTZ);
62 };
63 
64 constexpr char ScopedSetTZ::kTZ[];
65 
TEST(TimeZone,Basic)66 TEST(TimeZone, Basic) {
67   SystemSnapshot::DaylightSavingTimeStatus dst_status;
68   int standard_offset_seconds;
69   int daylight_offset_seconds;
70   std::string standard_name;
71   std::string daylight_name;
72 
73   timeval snapshot_time;
74   ASSERT_EQ(gettimeofday(&snapshot_time, nullptr), 0);
75 
76   internal::TimeZone(snapshot_time,
77                      &dst_status,
78                      &standard_offset_seconds,
79                      &daylight_offset_seconds,
80                      &standard_name,
81                      &daylight_name);
82 
83   // |standard_offset_seconds| gives seconds east of UTC, and |timezone| gives
84   // seconds west of UTC.
85   EXPECT_EQ(standard_offset_seconds, -timezone);
86 
87   // In contemporary usage, most time zones have an integer hour offset from
88   // UTC, although several are at a half-hour offset, and two are at 15-minute
89   // offsets. Throughout history, other variations existed. See
90   // https://www.timeanddate.com/time/time-zones-interesting.html.
91   EXPECT_EQ(standard_offset_seconds % (15 * 60), 0)
92       << "standard_offset_seconds " << standard_offset_seconds;
93 
94   if (dst_status == SystemSnapshot::kDoesNotObserveDaylightSavingTime) {
95     EXPECT_EQ(daylight_offset_seconds, standard_offset_seconds);
96     EXPECT_EQ(daylight_name, standard_name);
97   } else {
98     EXPECT_EQ(daylight_offset_seconds % (15 * 60), 0)
99         << "daylight_offset_seconds " << daylight_offset_seconds;
100 
101     // In contemporary usage, dst_delta_seconds will almost always be one hour,
102     // except for Lord Howe Island, Australia, which uses a 30-minute delta.
103     // Throughout history, other variations existed. See
104     // https://www.timeanddate.com/time/dst/.
105     int dst_delta_seconds = daylight_offset_seconds - standard_offset_seconds;
106     if (dst_delta_seconds != 60 * 60 && dst_delta_seconds != 30 * 60) {
107       FAIL() << "dst_delta_seconds " << dst_delta_seconds;
108     }
109 
110     EXPECT_NE(standard_name, daylight_name);
111   }
112 
113   // Test a variety of time zones. Some of these observe daylight saving time,
114   // some don’t. Some used to but no longer do. Some have uncommon UTC offsets.
115   // standard_name and daylight_name can be nullptr where no name exists to
116   // verify, as may happen when some versions of the timezone database carry
117   // invented names and others do not.
118   static constexpr struct {
119     const char* tz;
120     bool observes_dst;
121     float standard_offset_hours;
122     float daylight_offset_hours;
123     const char* standard_name;
124     const char* daylight_name;
125   } kTestTimeZones[] = {
126       {"America/Anchorage", true, -9, -8, "AKST", "AKDT"},
127       {"America/Chicago", true, -6, -5, "CST", "CDT"},
128       {"America/Denver", true, -7, -6, "MST", "MDT"},
129       {"America/Halifax", true, -4, -3, "AST", "ADT"},
130       {"America/Los_Angeles", true, -8, -7, "PST", "PDT"},
131       {"America/New_York", true, -5, -4, "EST", "EDT"},
132       {"America/Phoenix", false, -7, -7, "MST", "MST"},
133       {"Asia/Karachi", false, 5, 5, "PKT", "PKT"},
134       {"Asia/Kolkata", false, 5.5, 5.5, "IST", "IST"},
135       {"Asia/Shanghai", false, 8, 8, "CST", "CST"},
136       {"Asia/Tokyo", false, 9, 9, "JST", "JST"},
137 
138       // Australian timezone names have an optional "A" prefix, which is
139       // present for glibc and macOS, but missing on Android.
140       {"Australia/Adelaide", true, 9.5, 10.5, nullptr, nullptr},
141       {"Australia/Brisbane", false, 10, 10, nullptr, nullptr},
142       {"Australia/Darwin", false, 9.5, 9.5, nullptr, nullptr},
143       {"Australia/Eucla", false, 8.75, 8.75, nullptr, nullptr},
144       {"Australia/Lord_Howe", true, 10.5, 11, nullptr, nullptr},
145       {"Australia/Perth", false, 8, 8, nullptr, nullptr},
146       {"Australia/Sydney", true, 10, 11, nullptr, nullptr},
147 
148       {"Europe/Bucharest", true, 2, 3, "EET", "EEST"},
149       {"Europe/London", true, 0, 1, "GMT", "BST"},
150       {"Europe/Paris", true, 1, 2, "CET", "CEST"},
151       {"Europe/Reykjavik", false, 0, 0, nullptr, nullptr},
152       {"Pacific/Auckland", true, 12, 13, "NZST", "NZDT"},
153       {"Pacific/Honolulu", false, -10, -10, "HST", "HST"},
154       {"UTC", false, 0, 0, "UTC", "UTC"},
155   };
156 
157   for (size_t index = 0; index < base::size(kTestTimeZones); ++index) {
158     const auto& test_time_zone = kTestTimeZones[index];
159     const char* tz = test_time_zone.tz;
160     SCOPED_TRACE(base::StringPrintf("index %zu, tz %s", index, tz));
161 
162     {
163       ScopedSetTZ set_tz(tz);
164       internal::TimeZone(snapshot_time,
165                          &dst_status,
166                          &standard_offset_seconds,
167                          &daylight_offset_seconds,
168                          &standard_name,
169                          &daylight_name);
170     }
171 
172     EXPECT_PRED2(
173         [](SystemSnapshot::DaylightSavingTimeStatus dst, bool observes) {
174           return (dst != SystemSnapshot::kDoesNotObserveDaylightSavingTime) ==
175                  observes;
176         },
177         dst_status,
178         test_time_zone.observes_dst);
179 
180     EXPECT_EQ(standard_offset_seconds,
181               test_time_zone.standard_offset_hours * 60 * 60);
182     EXPECT_EQ(daylight_offset_seconds,
183               test_time_zone.daylight_offset_hours * 60 * 60);
184     if (test_time_zone.standard_name) {
185       EXPECT_EQ(standard_name, test_time_zone.standard_name);
186     }
187     if (test_time_zone.daylight_name) {
188       EXPECT_EQ(daylight_name, test_time_zone.daylight_name);
189     }
190   }
191 }
192 
193 }  // namespace
194 }  // namespace test
195 }  // namespace crashpad
196