Coverage Report

Created: 2024-02-11 06:24

/src/znc/third_party/cctz/include/cctz/time_zone.h
Line
Count
Source (jump to first uncovered line)
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
// A library for translating between absolute times (represented by
16
// std::chrono::time_points of the std::chrono::system_clock) and civil
17
// times (represented by cctz::civil_second) using the rules defined by
18
// a time zone (cctz::time_zone).
19
20
#ifndef CCTZ_TIME_ZONE_H_
21
#define CCTZ_TIME_ZONE_H_
22
23
#include <chrono>
24
#include <cstdint>
25
#include <limits>
26
#include <string>
27
#include <utility>
28
29
#include "cctz/civil_time.h"
30
31
namespace cctz {
32
33
// Convenience aliases. Not intended as public API points.
34
template <typename D>
35
using time_point = std::chrono::time_point<std::chrono::system_clock, D>;
36
using seconds = std::chrono::duration<std::int_fast64_t>;
37
using sys_seconds = seconds;  // Deprecated.  Use cctz::seconds instead.
38
39
namespace detail {
40
template <typename D>
41
std::pair<time_point<seconds>, D> split_seconds(const time_point<D>& tp);
42
std::pair<time_point<seconds>, seconds> split_seconds(
43
    const time_point<seconds>& tp);
44
}  // namespace detail
45
46
// cctz::time_zone is an opaque, small, value-type class representing a
47
// geo-political region within which particular rules are used for mapping
48
// between absolute and civil times. Time zones are named using the TZ
49
// identifiers from the IANA Time Zone Database, such as "America/Los_Angeles"
50
// or "Australia/Sydney". Time zones are created from factory functions such
51
// as load_time_zone(). Note: strings like "PST" and "EDT" are not valid TZ
52
// identifiers.
53
//
54
// Example:
55
//   cctz::time_zone utc = cctz::utc_time_zone();
56
//   cctz::time_zone pst = cctz::fixed_time_zone(std::chrono::hours(-8));
57
//   cctz::time_zone loc = cctz::local_time_zone();
58
//   cctz::time_zone lax;
59
//   if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
60
//
61
// See also:
62
// - http://www.iana.org/time-zones
63
// - https://en.wikipedia.org/wiki/Zoneinfo
64
class time_zone {
65
 public:
66
0
  time_zone() : time_zone(nullptr) {}  // Equivalent to UTC
67
  time_zone(const time_zone&) = default;
68
  time_zone& operator=(const time_zone&) = default;
69
70
  std::string name() const;
71
72
  // An absolute_lookup represents the civil time (cctz::civil_second) within
73
  // this time_zone at the given absolute time (time_point). There are
74
  // additionally a few other fields that may be useful when working with
75
  // older APIs, such as std::tm.
76
  //
77
  // Example:
78
  //   const cctz::time_zone tz = ...
79
  //   const auto tp = std::chrono::system_clock::now();
80
  //   const cctz::time_zone::absolute_lookup al = tz.lookup(tp);
81
  struct absolute_lookup {
82
    civil_second cs;
83
    // Note: The following fields exist for backward compatibility with older
84
    // APIs. Accessing these fields directly is a sign of imprudent logic in
85
    // the calling code. Modern time-related code should only access this data
86
    // indirectly by way of cctz::format().
87
    int offset;        // civil seconds east of UTC
88
    bool is_dst;       // is offset non-standard?
89
    const char* abbr;  // time-zone abbreviation (e.g., "PST")
90
  };
91
  absolute_lookup lookup(const time_point<seconds>& tp) const;
92
  template <typename D>
93
  absolute_lookup lookup(const time_point<D>& tp) const {
94
    return lookup(detail::split_seconds(tp).first);
95
  }
96
97
  // A civil_lookup represents the absolute time(s) (time_point) that
98
  // correspond to the given civil time (cctz::civil_second) within this
99
  // time_zone. Usually the given civil time represents a unique instant
100
  // in time, in which case the conversion is unambiguous. However,
101
  // within this time zone, the given civil time may be skipped (e.g.,
102
  // during a positive UTC offset shift), or repeated (e.g., during a
103
  // negative UTC offset shift). To account for these possibilities,
104
  // civil_lookup is richer than just a single time_point.
105
  //
106
  // In all cases the civil_lookup::kind enum will indicate the nature
107
  // of the given civil-time argument, and the pre, trans, and post
108
  // members will give the absolute time answers using the pre-transition
109
  // offset, the transition point itself, and the post-transition offset,
110
  // respectively (all three times are equal if kind == UNIQUE). If any
111
  // of these three absolute times is outside the representable range of a
112
  // time_point<seconds> the field is set to its maximum/minimum value.
113
  //
114
  // Example:
115
  //   cctz::time_zone lax;
116
  //   if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
117
  //
118
  //   // A unique civil time.
119
  //   auto jan01 = lax.lookup(cctz::civil_second(2011, 1, 1, 0, 0, 0));
120
  //   // jan01.kind == cctz::time_zone::civil_lookup::UNIQUE
121
  //   // jan01.pre    is 2011/01/01 00:00:00 -0800
122
  //   // jan01.trans  is 2011/01/01 00:00:00 -0800
123
  //   // jan01.post   is 2011/01/01 00:00:00 -0800
124
  //
125
  //   // A Spring DST transition, when there is a gap in civil time.
126
  //   auto mar13 = lax.lookup(cctz::civil_second(2011, 3, 13, 2, 15, 0));
127
  //   // mar13.kind == cctz::time_zone::civil_lookup::SKIPPED
128
  //   // mar13.pre   is 2011/03/13 03:15:00 -0700
129
  //   // mar13.trans is 2011/03/13 03:00:00 -0700
130
  //   // mar13.post  is 2011/03/13 01:15:00 -0800
131
  //
132
  //   // A Fall DST transition, when civil times are repeated.
133
  //   auto nov06 = lax.lookup(cctz::civil_second(2011, 11, 6, 1, 15, 0));
134
  //   // nov06.kind == cctz::time_zone::civil_lookup::REPEATED
135
  //   // nov06.pre   is 2011/11/06 01:15:00 -0700
136
  //   // nov06.trans is 2011/11/06 01:00:00 -0800
137
  //   // nov06.post  is 2011/11/06 01:15:00 -0800
138
  struct civil_lookup {
139
    enum civil_kind {
140
      UNIQUE,    // the civil time was singular (pre == trans == post)
141
      SKIPPED,   // the civil time did not exist (pre >= trans > post)
142
      REPEATED,  // the civil time was ambiguous (pre < trans <= post)
143
    } kind;
144
    time_point<seconds> pre;    // uses the pre-transition offset
145
    time_point<seconds> trans;  // instant of civil-offset change
146
    time_point<seconds> post;   // uses the post-transition offset
147
  };
148
  civil_lookup lookup(const civil_second& cs) const;
149
150
  // Finds the time of the next/previous offset change in this time zone.
151
  //
152
  // By definition, next_transition(tp, &trans) returns false when tp has
153
  // its maximum value, and prev_transition(tp, &trans) returns false
154
  // when tp has its minimum value. If the zone has no transitions, the
155
  // result will also be false no matter what the argument.
156
  //
157
  // Otherwise, when tp has its minimum value, next_transition(tp, &trans)
158
  // returns true and sets trans to the first recorded transition. Chains
159
  // of calls to next_transition()/prev_transition() will eventually return
160
  // false, but it is unspecified exactly when next_transition(tp, &trans)
161
  // jumps to false, or what time is set by prev_transition(tp, &trans) for
162
  // a very distant tp.
163
  //
164
  // Note: Enumeration of time-zone transitions is for informational purposes
165
  // only. Modern time-related code should not care about when offset changes
166
  // occur.
167
  //
168
  // Example:
169
  //   cctz::time_zone nyc;
170
  //   if (!cctz::load_time_zone("America/New_York", &nyc)) { ... }
171
  //   const auto now = std::chrono::system_clock::now();
172
  //   auto tp = cctz::time_point<cctz::seconds>::min();
173
  //   cctz::time_zone::civil_transition trans;
174
  //   while (tp <= now && nyc.next_transition(tp, &trans)) {
175
  //     // transition: trans.from -> trans.to
176
  //     tp = nyc.lookup(trans.to).trans;
177
  //   }
178
  struct civil_transition {
179
    civil_second from;  // the civil time we jump from
180
    civil_second to;    // the civil time we jump to
181
  };
182
  bool next_transition(const time_point<seconds>& tp,
183
                       civil_transition* trans) const;
184
  template <typename D>
185
  bool next_transition(const time_point<D>& tp,
186
                       civil_transition* trans) const {
187
    return next_transition(detail::split_seconds(tp).first, trans);
188
  }
189
  bool prev_transition(const time_point<seconds>& tp,
190
                       civil_transition* trans) const;
191
  template <typename D>
192
  bool prev_transition(const time_point<D>& tp,
193
                       civil_transition* trans) const {
194
    return prev_transition(detail::split_seconds(tp).first, trans);
195
  }
196
197
  // version() and description() provide additional information about the
198
  // time zone. The content of each of the returned strings is unspecified,
199
  // however, when the IANA Time Zone Database is the underlying data source
200
  // the version() string will be in the familar form (e.g, "2018e") or
201
  // empty when unavailable.
202
  //
203
  // Note: These functions are for informational or testing purposes only.
204
  std::string version() const;  // empty when unknown
205
  std::string description() const;
206
207
  // Relational operators.
208
0
  friend bool operator==(time_zone lhs, time_zone rhs) {
209
0
    return &lhs.effective_impl() == &rhs.effective_impl();
210
0
  }
211
0
  friend bool operator!=(time_zone lhs, time_zone rhs) {
212
0
    return !(lhs == rhs);
213
0
  }
214
215
  class Impl;
216
217
 private:
218
0
  explicit time_zone(const Impl* impl) : impl_(impl) {}
219
  const Impl& effective_impl() const;  // handles implicit UTC
220
  const Impl* impl_;
221
};
222
223
// Loads the named time zone. May perform I/O on the initial load.
224
// If the name is invalid, or some other kind of error occurs, returns
225
// false and "*tz" is set to the UTC time zone.
226
bool load_time_zone(const std::string& name, time_zone* tz);
227
228
// Returns a time_zone representing UTC. Cannot fail.
229
time_zone utc_time_zone();
230
231
// Returns a time zone that is a fixed offset (seconds east) from UTC.
232
// Note: If the absolute value of the offset is greater than 24 hours
233
// you'll get UTC (i.e., zero offset) instead.
234
time_zone fixed_time_zone(const seconds& offset);
235
236
// Returns a time zone representing the local time zone. Falls back to UTC.
237
// Note: local_time_zone.name() may only be something like "localtime".
238
time_zone local_time_zone();
239
240
// Returns the civil time (cctz::civil_second) within the given time zone at
241
// the given absolute time (time_point). Since the additional fields provided
242
// by the time_zone::absolute_lookup struct should rarely be needed in modern
243
// code, this convert() function is simpler and should be preferred.
244
template <typename D>
245
inline civil_second convert(const time_point<D>& tp, const time_zone& tz) {
246
  return tz.lookup(tp).cs;
247
}
248
249
// Returns the absolute time (time_point) that corresponds to the given civil
250
// time within the given time zone. If the civil time is not unique (i.e., if
251
// it was either repeated or non-existent), then the returned time_point is
252
// the best estimate that preserves relative order. That is, this function
253
// guarantees that if cs1 < cs2, then convert(cs1, tz) <= convert(cs2, tz).
254
inline time_point<seconds> convert(const civil_second& cs,
255
0
                                   const time_zone& tz) {
256
0
  const time_zone::civil_lookup cl = tz.lookup(cs);
257
0
  if (cl.kind == time_zone::civil_lookup::SKIPPED) return cl.trans;
258
0
  return cl.pre;
259
0
}
260
261
namespace detail {
262
using femtoseconds = std::chrono::duration<std::int_fast64_t, std::femto>;
263
std::string format(const std::string&, const time_point<seconds>&,
264
                   const femtoseconds&, const time_zone&);
265
bool parse(const std::string&, const std::string&, const time_zone&,
266
           time_point<seconds>*, femtoseconds*, std::string* err = nullptr);
267
template <typename Rep, std::intmax_t Denom>
268
bool join_seconds(
269
    const time_point<seconds>& sec, const femtoseconds& fs,
270
    time_point<std::chrono::duration<Rep, std::ratio<1, Denom>>>* tpp);
271
template <typename Rep, std::intmax_t Num>
272
bool join_seconds(
273
    const time_point<seconds>& sec, const femtoseconds& fs,
274
    time_point<std::chrono::duration<Rep, std::ratio<Num, 1>>>* tpp);
275
template <typename Rep>
276
bool join_seconds(
277
    const time_point<seconds>& sec, const femtoseconds& fs,
278
    time_point<std::chrono::duration<Rep, std::ratio<1, 1>>>* tpp);
279
bool join_seconds(const time_point<seconds>& sec, const femtoseconds&,
280
                  time_point<seconds>* tpp);
281
}  // namespace detail
282
283
// Formats the given time_point in the given cctz::time_zone according to
284
// the provided format string. Uses strftime()-like formatting options,
285
// with the following extensions:
286
//
287
//   - %Ez  - RFC3339-compatible numeric UTC offset (+hh:mm or -hh:mm)
288
//   - %E*z - Full-resolution numeric UTC offset (+hh:mm:ss or -hh:mm:ss)
289
//   - %E#S - Seconds with # digits of fractional precision
290
//   - %E*S - Seconds with full fractional precision (a literal '*')
291
//   - %E#f - Fractional seconds with # digits of precision
292
//   - %E*f - Fractional seconds with full precision (a literal '*')
293
//   - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999)
294
//   - %ET  - The RFC3339 "date-time" separator "T"
295
//
296
// Note that %E0S behaves like %S, and %E0f produces no characters. In
297
// contrast %E*f always produces at least one digit, which may be '0'.
298
//
299
// Note that %Y produces as many characters as it takes to fully render the
300
// year. A year outside of [-999:9999] when formatted with %E4Y will produce
301
// more than four characters, just like %Y.
302
//
303
// Tip: Format strings should include the UTC offset (e.g., %z, %Ez, or %E*z)
304
// so that the resulting string uniquely identifies an absolute time.
305
//
306
// Example:
307
//   cctz::time_zone lax;
308
//   if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
309
//   auto tp = cctz::convert(cctz::civil_second(2013, 1, 2, 3, 4, 5), lax);
310
//   std::string f = cctz::format("%H:%M:%S", tp, lax);  // "03:04:05"
311
//   f = cctz::format("%H:%M:%E3S", tp, lax);            // "03:04:05.000"
312
template <typename D>
313
inline std::string format(const std::string& fmt, const time_point<D>& tp,
314
0
                          const time_zone& tz) {
315
0
  const auto p = detail::split_seconds(tp);
316
0
  const auto n = std::chrono::duration_cast<detail::femtoseconds>(p.second);
317
0
  return detail::format(fmt, p.first, n, tz);
318
0
}
319
320
// Parses an input string according to the provided format string and
321
// returns the corresponding time_point. Uses strftime()-like formatting
322
// options, with the same extensions as cctz::format(), but with the
323
// exceptions that %E#S is interpreted as %E*S, and %E#f as %E*f. %Ez
324
// and %E*z also accept the same inputs, which (along with %z) includes
325
// 'z' and 'Z' as synonyms for +00:00.  %ET accepts either 'T' or 't'.
326
//
327
// %Y consumes as many numeric characters as it can, so the matching data
328
// should always be terminated with a non-numeric. %E4Y always consumes
329
// exactly four characters, including any sign.
330
//
331
// Unspecified fields are taken from the default date and time of ...
332
//
333
//   "1970-01-01 00:00:00.0 +0000"
334
//
335
// For example, parsing a string of "15:45" (%H:%M) will return a time_point
336
// that represents "1970-01-01 15:45:00.0 +0000".
337
//
338
// Note that parse() returns time instants, so it makes most sense to parse
339
// fully-specified date/time strings that include a UTC offset (%z, %Ez, or
340
// %E*z).
341
//
342
// Note also that parse() only heeds the fields year, month, day, hour,
343
// minute, (fractional) second, and UTC offset. Other fields, like weekday (%a
344
// or %A), while parsed for syntactic validity, are ignored in the conversion.
345
//
346
// Date and time fields that are out-of-range will be treated as errors rather
347
// than normalizing them like cctz::civil_second() would do. For example, it
348
// is an error to parse the date "Oct 32, 2013" because 32 is out of range.
349
//
350
// A second of ":60" is normalized to ":00" of the following minute with
351
// fractional seconds discarded. The following table shows how the given
352
// seconds and subseconds will be parsed:
353
//
354
//   "59.x" -> 59.x  // exact
355
//   "60.x" -> 00.0  // normalized
356
//   "00.x" -> 00.x  // exact
357
//
358
// Errors are indicated by returning false.
359
//
360
// Example:
361
//   const cctz::time_zone tz = ...
362
//   std::chrono::system_clock::time_point tp;
363
//   if (cctz::parse("%Y-%m-%d", "2015-10-09", tz, &tp)) {
364
//     ...
365
//   }
366
template <typename D>
367
inline bool parse(const std::string& fmt, const std::string& input,
368
0
                  const time_zone& tz, time_point<D>* tpp) {
369
0
  time_point<seconds> sec;
370
0
  detail::femtoseconds fs;
371
0
  return detail::parse(fmt, input, tz, &sec, &fs) &&
372
0
         detail::join_seconds(sec, fs, tpp);
373
0
}
374
375
namespace detail {
376
377
// Split a time_point<D> into a time_point<seconds> and a D subseconds.
378
// Undefined behavior if time_point<seconds> is not of sufficient range.
379
// Note that this means it is UB to call cctz::time_zone::lookup(tp) or
380
// cctz::format(fmt, tp, tz) with a time_point that is outside the range
381
// of a 64-bit std::time_t.
382
template <typename D>
383
0
std::pair<time_point<seconds>, D> split_seconds(const time_point<D>& tp) {
384
0
  auto sec = std::chrono::time_point_cast<seconds>(tp);
385
0
  auto sub = tp - sec;
386
0
  if (sub.count() < 0) {
387
0
    sec -= seconds(1);
388
0
    sub += seconds(1);
389
0
  }
390
0
  return {sec, std::chrono::duration_cast<D>(sub)};
391
0
}
392
393
inline std::pair<time_point<seconds>, seconds> split_seconds(
394
0
    const time_point<seconds>& tp) {
395
0
  return {tp, seconds::zero()};
396
0
}
397
398
// Join a time_point<seconds> and femto subseconds into a time_point<D>.
399
// Floors to the resolution of time_point<D>. Returns false if time_point<D>
400
// is not of sufficient range.
401
template <typename Rep, std::intmax_t Denom>
402
bool join_seconds(
403
    const time_point<seconds>& sec, const femtoseconds& fs,
404
0
    time_point<std::chrono::duration<Rep, std::ratio<1, Denom>>>* tpp) {
405
0
  using D = std::chrono::duration<Rep, std::ratio<1, Denom>>;
406
  // TODO(#199): Return false if result unrepresentable as a time_point<D>.
407
0
  *tpp = std::chrono::time_point_cast<D>(sec);
408
0
  *tpp += std::chrono::duration_cast<D>(fs);
409
0
  return true;
410
0
}
411
412
template <typename Rep, std::intmax_t Num>
413
bool join_seconds(
414
    const time_point<seconds>& sec, const femtoseconds&,
415
    time_point<std::chrono::duration<Rep, std::ratio<Num, 1>>>* tpp) {
416
  using D = std::chrono::duration<Rep, std::ratio<Num, 1>>;
417
  auto count = sec.time_since_epoch().count();
418
  if (count >= 0 || count % Num == 0) {
419
    count /= Num;
420
  } else {
421
    count /= Num;
422
    count -= 1;
423
  }
424
  if (count > (std::numeric_limits<Rep>::max)()) return false;
425
  if (count < (std::numeric_limits<Rep>::min)()) return false;
426
  *tpp = time_point<D>() + D{static_cast<Rep>(count)};
427
  return true;
428
}
429
430
template <typename Rep>
431
bool join_seconds(
432
    const time_point<seconds>& sec, const femtoseconds&,
433
    time_point<std::chrono::duration<Rep, std::ratio<1, 1>>>* tpp) {
434
  using D = std::chrono::duration<Rep, std::ratio<1, 1>>;
435
  auto count = sec.time_since_epoch().count();
436
  if (count > (std::numeric_limits<Rep>::max)()) return false;
437
  if (count < (std::numeric_limits<Rep>::min)()) return false;
438
  *tpp = time_point<D>() + D{static_cast<Rep>(count)};
439
  return true;
440
}
441
442
inline bool join_seconds(const time_point<seconds>& sec, const femtoseconds&,
443
0
                         time_point<seconds>* tpp) {
444
0
  *tpp = sec;
445
0
  return true;
446
0
}
447
448
}  // namespace detail
449
}  // namespace cctz
450
451
#endif  // CCTZ_TIME_ZONE_H_