Coverage Report

Created: 2026-01-21 06:46

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