1// Copyright 2016 Google Inc. 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// https://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#if defined(_WIN32) || defined(_WIN64)
16#define _CRT_SECURE_NO_WARNINGS 1
17#endif
18
19#include "time_zone_libc.h"
20
21#include <chrono>
22#include <ctime>
23#include <limits>
24#include <utility>
25
26#include "absl/time/internal/cctz/include/cctz/civil_time.h"
27#include "absl/time/internal/cctz/include/cctz/time_zone.h"
28
29namespace absl {
30namespace time_internal {
31namespace cctz {
32
33namespace {
34
35#if defined(_WIN32) || defined(_WIN64)
36// Uses the globals: '_timezone', '_dstbias' and '_tzname'.
37auto tm_gmtoff(const std::tm& tm) -> decltype(_timezone + _dstbias) {
38 const bool is_dst = tm.tm_isdst > 0;
39 return _timezone + (is_dst ? _dstbias : 0);
40}
41auto tm_zone(const std::tm& tm) -> decltype(_tzname[0]) {
42 const bool is_dst = tm.tm_isdst > 0;
43 return _tzname[is_dst];
44}
45#elif defined(__sun)
46// Uses the globals: 'timezone', 'altzone' and 'tzname'.
47auto tm_gmtoff(const std::tm& tm) -> decltype(timezone) {
48 const bool is_dst = tm.tm_isdst > 0;
49 return is_dst ? altzone : timezone;
50}
51auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) {
52 const bool is_dst = tm.tm_isdst > 0;
53 return tzname[is_dst];
54}
55#elif defined(__native_client__) || defined(__myriad2__) || \
56 defined(__EMSCRIPTEN__)
57// Uses the globals: 'timezone' and 'tzname'.
58auto tm_gmtoff(const std::tm& tm) -> decltype(_timezone + 0) {
59 const bool is_dst = tm.tm_isdst > 0;
60 return _timezone + (is_dst ? 60 * 60 : 0);
61}
62auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) {
63 const bool is_dst = tm.tm_isdst > 0;
64 return tzname[is_dst];
65}
66#else
67// Adapt to different spellings of the struct std::tm extension fields.
68#if defined(tm_gmtoff)
69auto tm_gmtoff(const std::tm& tm) -> decltype(tm.tm_gmtoff) {
70 return tm.tm_gmtoff;
71}
72#elif defined(__tm_gmtoff)
73auto tm_gmtoff(const std::tm& tm) -> decltype(tm.__tm_gmtoff) {
74 return tm.__tm_gmtoff;
75}
76#else
77template <typename T>
78auto tm_gmtoff(const T& tm) -> decltype(tm.tm_gmtoff) {
79 return tm.tm_gmtoff;
80}
81template <typename T>
82auto tm_gmtoff(const T& tm) -> decltype(tm.__tm_gmtoff) {
83 return tm.__tm_gmtoff;
84}
85#endif // tm_gmtoff
86#if defined(tm_zone)
87auto tm_zone(const std::tm& tm) -> decltype(tm.tm_zone) {
88 return tm.tm_zone;
89}
90#elif defined(__tm_zone)
91auto tm_zone(const std::tm& tm) -> decltype(tm.__tm_zone) {
92 return tm.__tm_zone;
93}
94#else
95template <typename T>
96auto tm_zone(const T& tm) -> decltype(tm.tm_zone) {
97 return tm.tm_zone;
98}
99template <typename T>
100auto tm_zone(const T& tm) -> decltype(tm.__tm_zone) {
101 return tm.__tm_zone;
102}
103#endif // tm_zone
104#endif
105
106inline std::tm* gm_time(const std::time_t *timep, std::tm *result) {
107#if defined(_WIN32) || defined(_WIN64)
108 return gmtime_s(result, timep) ? nullptr : result;
109#else
110 return gmtime_r(timep, result);
111#endif
112}
113
114inline std::tm* local_time(const std::time_t *timep, std::tm *result) {
115#if defined(_WIN32) || defined(_WIN64)
116 return localtime_s(result, timep) ? nullptr : result;
117#else
118 return localtime_r(timep, result);
119#endif
120}
121
122// Converts a civil second and "dst" flag into a time_t and UTC offset.
123// Returns false if time_t cannot represent the requested civil second.
124// Caller must have already checked that cs.year() will fit into a tm_year.
125bool make_time(const civil_second& cs, int is_dst, std::time_t* t, int* off) {
126 std::tm tm;
127 tm.tm_year = static_cast<int>(cs.year() - year_t{1900});
128 tm.tm_mon = cs.month() - 1;
129 tm.tm_mday = cs.day();
130 tm.tm_hour = cs.hour();
131 tm.tm_min = cs.minute();
132 tm.tm_sec = cs.second();
133 tm.tm_isdst = is_dst;
134 *t = std::mktime(&tm);
135 if (*t == std::time_t{-1}) {
136 std::tm tm2;
137 const std::tm* tmp = local_time(t, &tm2);
138 if (tmp == nullptr || tmp->tm_year != tm.tm_year ||
139 tmp->tm_mon != tm.tm_mon || tmp->tm_mday != tm.tm_mday ||
140 tmp->tm_hour != tm.tm_hour || tmp->tm_min != tm.tm_min ||
141 tmp->tm_sec != tm.tm_sec) {
142 // A true error (not just one second before the epoch).
143 return false;
144 }
145 }
146 *off = static_cast<int>(tm_gmtoff(tm));
147 return true;
148}
149
150// Find the least time_t in [lo:hi] where local time matches offset, given:
151// (1) lo doesn't match, (2) hi does, and (3) there is only one transition.
152std::time_t find_trans(std::time_t lo, std::time_t hi, int offset) {
153 std::tm tm;
154 while (lo + 1 != hi) {
155 const std::time_t mid = lo + (hi - lo) / 2;
156 if (std::tm* tmp = local_time(&mid, &tm)) {
157 if (tm_gmtoff(*tmp) == offset) {
158 hi = mid;
159 } else {
160 lo = mid;
161 }
162 } else {
163 // If std::tm cannot hold some result we resort to a linear search,
164 // ignoring all failed conversions. Slow, but never really happens.
165 while (++lo != hi) {
166 if (std::tm* tmp = local_time(&lo, &tm)) {
167 if (tm_gmtoff(*tmp) == offset) break;
168 }
169 }
170 return lo;
171 }
172 }
173 return hi;
174}
175
176} // namespace
177
178TimeZoneLibC::TimeZoneLibC(const std::string& name)
179 : local_(name == "localtime") {}
180
181time_zone::absolute_lookup TimeZoneLibC::BreakTime(
182 const time_point<seconds>& tp) const {
183 time_zone::absolute_lookup al;
184 al.offset = 0;
185 al.is_dst = false;
186 al.abbr = "-00";
187
188 const std::int_fast64_t s = ToUnixSeconds(tp);
189
190 // If std::time_t cannot hold the input we saturate the output.
191 if (s < std::numeric_limits<std::time_t>::min()) {
192 al.cs = civil_second::min();
193 return al;
194 }
195 if (s > std::numeric_limits<std::time_t>::max()) {
196 al.cs = civil_second::max();
197 return al;
198 }
199
200 const std::time_t t = static_cast<std::time_t>(s);
201 std::tm tm;
202 std::tm* tmp = local_ ? local_time(&t, &tm) : gm_time(&t, &tm);
203
204 // If std::tm cannot hold the result we saturate the output.
205 if (tmp == nullptr) {
206 al.cs = (s < 0) ? civil_second::min() : civil_second::max();
207 return al;
208 }
209
210 const year_t year = tmp->tm_year + year_t{1900};
211 al.cs = civil_second(year, tmp->tm_mon + 1, tmp->tm_mday,
212 tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
213 al.offset = static_cast<int>(tm_gmtoff(*tmp));
214 al.abbr = local_ ? tm_zone(*tmp) : "UTC"; // as expected by cctz
215 al.is_dst = tmp->tm_isdst > 0;
216 return al;
217}
218
219time_zone::civil_lookup TimeZoneLibC::MakeTime(const civil_second& cs) const {
220 if (!local_) {
221 // If time_point<seconds> cannot hold the result we saturate.
222 static const civil_second min_tp_cs =
223 civil_second() + ToUnixSeconds(time_point<seconds>::min());
224 static const civil_second max_tp_cs =
225 civil_second() + ToUnixSeconds(time_point<seconds>::max());
226 const time_point<seconds> tp =
227 (cs < min_tp_cs)
228 ? time_point<seconds>::min()
229 : (cs > max_tp_cs) ? time_point<seconds>::max()
230 : FromUnixSeconds(cs - civil_second());
231 return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
232 }
233
234 // If tm_year cannot hold the requested year we saturate the result.
235 if (cs.year() < 0) {
236 if (cs.year() < std::numeric_limits<int>::min() + year_t{1900}) {
237 const time_point<seconds> tp = time_point<seconds>::min();
238 return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
239 }
240 } else {
241 if (cs.year() - year_t{1900} > std::numeric_limits<int>::max()) {
242 const time_point<seconds> tp = time_point<seconds>::max();
243 return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
244 }
245 }
246
247 // We probe with "is_dst" values of 0 and 1 to try to distinguish unique
248 // civil seconds from skipped or repeated ones. This is not always possible
249 // however, as the "dst" flag does not change over some offset transitions.
250 // We are also subject to the vagaries of mktime() implementations.
251 std::time_t t0, t1;
252 int offset0, offset1;
253 if (make_time(cs, 0, &t0, &offset0) && make_time(cs, 1, &t1, &offset1)) {
254 if (t0 == t1) {
255 // The civil time was singular (pre == trans == post).
256 const time_point<seconds> tp = FromUnixSeconds(t0);
257 return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
258 }
259
260 if (t0 > t1) {
261 std::swap(t0, t1);
262 std::swap(offset0, offset1);
263 }
264 const std::time_t tt = find_trans(t0, t1, offset1);
265 const time_point<seconds> trans = FromUnixSeconds(tt);
266
267 if (offset0 < offset1) {
268 // The civil time did not exist (pre >= trans > post).
269 const time_point<seconds> pre = FromUnixSeconds(t1);
270 const time_point<seconds> post = FromUnixSeconds(t0);
271 return {time_zone::civil_lookup::SKIPPED, pre, trans, post};
272 }
273
274 // The civil time was ambiguous (pre < trans <= post).
275 const time_point<seconds> pre = FromUnixSeconds(t0);
276 const time_point<seconds> post = FromUnixSeconds(t1);
277 return {time_zone::civil_lookup::REPEATED, pre, trans, post};
278 }
279
280 // make_time() failed somehow so we saturate the result.
281 const time_point<seconds> tp = (cs < civil_second())
282 ? time_point<seconds>::min()
283 : time_point<seconds>::max();
284 return {time_zone::civil_lookup::UNIQUE, tp, tp, tp};
285}
286
287bool TimeZoneLibC::NextTransition(const time_point<seconds>&,
288 time_zone::civil_transition*) const {
289 return false;
290}
291
292bool TimeZoneLibC::PrevTransition(const time_point<seconds>&,
293 time_zone::civil_transition*) const {
294 return false;
295}
296
297std::string TimeZoneLibC::Version() const {
298 return std::string(); // unknown
299}
300
301std::string TimeZoneLibC::Description() const {
302 return local_ ? "localtime" : "UTC";
303}
304
305} // namespace cctz
306} // namespace time_internal
307} // namespace absl
308