aboutsummaryrefslogtreecommitdiffhomepage
path: root/absl/time/internal/cctz/include/cctz/time_zone.h
blob: d05147a17c9fabb01224d9a84bbf4693fd46c5d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   https://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

// A library for translating between absolute times (represented by
// std::chrono::time_points of the std::chrono::system_clock) and civil
// times (represented by cctz::civil_second) using the rules defined by
// a time zone (cctz::time_zone).

#ifndef ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_H_
#define ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_H_

#include <chrono>
#include <cstdint>
#include <string>
#include <utility>

#include "absl/base/config.h"
#include "absl/time/internal/cctz/include/cctz/civil_time.h"

namespace absl {
ABSL_NAMESPACE_BEGIN
namespace time_internal {
namespace cctz {

// Convenience aliases. Not intended as public API points.
template <typename D>
using time_point = std::chrono::time_point<std::chrono::system_clock, D>;
using seconds = std::chrono::duration<std::int_fast64_t>;
using sys_seconds = seconds;  // Deprecated.  Use cctz::seconds instead.

namespace detail {
template <typename D>
inline std::pair<time_point<seconds>, D> split_seconds(
    const time_point<D>& tp) {
  auto sec = std::chrono::time_point_cast<seconds>(tp);
  auto sub = tp - sec;
  if (sub.count() < 0) {
    sec -= seconds(1);
    sub += seconds(1);
  }
  return {sec, std::chrono::duration_cast<D>(sub)};
}
inline std::pair<time_point<seconds>, seconds> split_seconds(
    const time_point<seconds>& tp) {
  return {tp, seconds::zero()};
}
}  // namespace detail

// cctz::time_zone is an opaque, small, value-type class representing a
// geo-political region within which particular rules are used for mapping
// between absolute and civil times. Time zones are named using the TZ
// identifiers from the IANA Time Zone Database, such as "America/Los_Angeles"
// or "Australia/Sydney". Time zones are created from factory functions such
// as load_time_zone(). Note: strings like "PST" and "EDT" are not valid TZ
// identifiers.
//
// Example:
//   cctz::time_zone utc = cctz::utc_time_zone();
//   cctz::time_zone pst = cctz::fixed_time_zone(std::chrono::hours(-8));
//   cctz::time_zone loc = cctz::local_time_zone();
//   cctz::time_zone lax;
//   if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
//
// See also:
// - http://www.iana.org/time-zones
// - https://en.wikipedia.org/wiki/Zoneinfo
class time_zone {
 public:
  time_zone() : time_zone(nullptr) {}  // Equivalent to UTC
  time_zone(const time_zone&) = default;
  time_zone& operator=(const time_zone&) = default;

  std::string name() const;

  // An absolute_lookup represents the civil time (cctz::civil_second) within
  // this time_zone at the given absolute time (time_point). There are
  // additionally a few other fields that may be useful when working with
  // older APIs, such as std::tm.
  //
  // Example:
  //   const cctz::time_zone tz = ...
  //   const auto tp = std::chrono::system_clock::now();
  //   const cctz::time_zone::absolute_lookup al = tz.lookup(tp);
  struct absolute_lookup {
    civil_second cs;
    // Note: The following fields exist for backward compatibility with older
    // APIs. Accessing these fields directly is a sign of imprudent logic in
    // the calling code. Modern time-related code should only access this data
    // indirectly by way of cctz::format().
    int offset;        // civil seconds east of UTC
    bool is_dst;       // is offset non-standard?
    const char* abbr;  // time-zone abbreviation (e.g., "PST")
  };
  absolute_lookup lookup(const time_point<seconds>& tp) const;
  template <typename D>
  absolute_lookup lookup(const time_point<D>& tp) const {
    return lookup(detail::split_seconds(tp).first);
  }

  // A civil_lookup represents the absolute time(s) (time_point) that
  // correspond to the given civil time (cctz::civil_second) within this
  // time_zone. Usually the given civil time represents a unique instant
  // in time, in which case the conversion is unambiguous. However,
  // within this time zone, the given civil time may be skipped (e.g.,
  // during a positive UTC offset shift), or repeated (e.g., during a
  // negative UTC offset shift). To account for these possibilities,
  // civil_lookup is richer than just a single time_point.
  //
  // In all cases the civil_lookup::kind enum will indicate the nature
  // of the given civil-time argument, and the pre, trans, and post
  // members will give the absolute time answers using the pre-transition
  // offset, the transition point itself, and the post-transition offset,
  // respectively (all three times are equal if kind == UNIQUE). If any
  // of these three absolute times is outside the representable range of a
  // time_point<seconds> the field is set to its maximum/minimum value.
  //
  // Example:
  //   cctz::time_zone lax;
  //   if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
  //
  //   // A unique civil time.
  //   auto jan01 = lax.lookup(cctz::civil_second(2011, 1, 1, 0, 0, 0));
  //   // jan01.kind == cctz::time_zone::civil_lookup::UNIQUE
  //   // jan01.pre    is 2011/01/01 00:00:00 -0800
  //   // jan01.trans  is 2011/01/01 00:00:00 -0800
  //   // jan01.post   is 2011/01/01 00:00:00 -0800
  //
  //   // A Spring DST transition, when there is a gap in civil time.
  //   auto mar13 = lax.lookup(cctz::civil_second(2011, 3, 13, 2, 15, 0));
  //   // mar13.kind == cctz::time_zone::civil_lookup::SKIPPED
  //   // mar13.pre   is 2011/03/13 03:15:00 -0700
  //   // mar13.trans is 2011/03/13 03:00:00 -0700
  //   // mar13.post  is 2011/03/13 01:15:00 -0800
  //
  //   // A Fall DST transition, when civil times are repeated.
  //   auto nov06 = lax.lookup(cctz::civil_second(2011, 11, 6, 1, 15, 0));
  //   // nov06.kind == cctz::time_zone::civil_lookup::REPEATED
  //   // nov06.pre   is 2011/11/06 01:15:00 -0700
  //   // nov06.trans is 2011/11/06 01:00:00 -0800
  //   // nov06.post  is 2011/11/06 01:15:00 -0800
  struct civil_lookup {
    enum civil_kind {
      UNIQUE,    // the civil time was singular (pre == trans == post)
      SKIPPED,   // the civil time did not exist (pre >= trans > post)
      REPEATED,  // the civil time was ambiguous (pre < trans <= post)
    } kind;
    time_point<seconds> pre;    // uses the pre-transition offset
    time_point<seconds> trans;  // instant of civil-offset change
    time_point<seconds> post;   // uses the post-transition offset
  };
  civil_lookup lookup(const civil_second& cs) const;

  // Finds the time of the next/previous offset change in this time zone.
  //
  // By definition, next_transition(tp, &trans) returns false when tp has
  // its maximum value, and prev_transition(tp, &trans) returns false
  // when tp has its minimum value. If the zone has no transitions, the
  // result will also be false no matter what the argument.
  //
  // Otherwise, when tp has its minimum value, next_transition(tp, &trans)
  // returns true and sets trans to the first recorded transition. Chains
  // of calls to next_transition()/prev_transition() will eventually return
  // false, but it is unspecified exactly when next_transition(tp, &trans)
  // jumps to false, or what time is set by prev_transition(tp, &trans) for
  // a very distant tp.
  //
  // Note: Enumeration of time-zone transitions is for informational purposes
  // only. Modern time-related code should not care about when offset changes
  // occur.
  //
  // Example:
  //   cctz::time_zone nyc;
  //   if (!cctz::load_time_zone("America/New_York", &nyc)) { ... }
  //   const auto now = std::chrono::system_clock::now();
  //   auto tp = cctz::time_point<cctz::seconds>::min();
  //   cctz::time_zone::civil_transition trans;
  //   while (tp <= now && nyc.next_transition(tp, &trans)) {
  //     // transition: trans.from -> trans.to
  //     tp = nyc.lookup(trans.to).trans;
  //   }
  struct civil_transition {
    civil_second from;  // the civil time we jump from
    civil_second to;    // the civil time we jump to
  };
  bool next_transition(const time_point<seconds>& tp,
                       civil_transition* trans) const;
  template <typename D>
  bool next_transition(const time_point<D>& tp, civil_transition* trans) const {
    return next_transition(detail::split_seconds(tp).first, trans);
  }
  bool prev_transition(const time_point<seconds>& tp,
                       civil_transition* trans) const;
  template <typename D>
  bool prev_transition(const time_point<D>& tp, civil_transition* trans) const {
    return prev_transition(detail::split_seconds(tp).first, trans);
  }

  // version() and description() provide additional information about the
  // time zone. The content of each of the returned strings is unspecified,
  // however, when the IANA Time Zone Database is the underlying data source
  // the version() std::string will be in the familar form (e.g, "2018e") or
  // empty when unavailable.
  //
  // Note: These functions are for informational or testing purposes only.
  std::string version() const;  // empty when unknown
  std::string description() const;

  // Relational operators.
  friend bool operator==(time_zone lhs, time_zone rhs) {
    return &lhs.effective_impl() == &rhs.effective_impl();
  }
  friend bool operator!=(time_zone lhs, time_zone rhs) { return !(lhs == rhs); }

  template <typename H>
  friend H AbslHashValue(H h, time_zone tz) {
    return H::combine(std::move(h), &tz.effective_impl());
  }

  class Impl;

 private:
  explicit time_zone(const Impl* impl) : impl_(impl) {}
  const Impl& effective_impl() const;  // handles implicit UTC
  const Impl* impl_;
};

// Loads the named time zone. May perform I/O on the initial load.
// If the name is invalid, or some other kind of error occurs, returns
// false and "*tz" is set to the UTC time zone.
bool load_time_zone(const std::string& name, time_zone* tz);

// Returns a time_zone representing UTC. Cannot fail.
time_zone utc_time_zone();

// Returns a time zone that is a fixed offset (seconds east) from UTC.
// Note: If the absolute value of the offset is greater than 24 hours
// you'll get UTC (i.e., zero offset) instead.
time_zone fixed_time_zone(const seconds& offset);

// Returns a time zone representing the local time zone. Falls back to UTC.
// Note: local_time_zone.name() may only be something like "localtime".
time_zone local_time_zone();

// Returns the civil time (cctz::civil_second) within the given time zone at
// the given absolute time (time_point). Since the additional fields provided
// by the time_zone::absolute_lookup struct should rarely be needed in modern
// code, this convert() function is simpler and should be preferred.
template <typename D>
inline civil_second convert(const time_point<D>& tp, const time_zone& tz) {
  return tz.lookup(tp).cs;
}

// Returns the absolute time (time_point) that corresponds to the given civil
// time within the given time zone. If the civil time is not unique (i.e., if
// it was either repeated or non-existent), then the returned time_point is
// the best estimate that preserves relative order. That is, this function
// guarantees that if cs1 < cs2, then convert(cs1, tz) <= convert(cs2, tz).
inline time_point<seconds> convert(const civil_second& cs,
                                   const time_zone& tz) {
  const time_zone::civil_lookup cl = tz.lookup(cs);
  if (cl.kind == time_zone::civil_lookup::SKIPPED) return cl.trans;
  return cl.pre;
}

namespace detail {
using femtoseconds = std::chrono::duration<std::int_fast64_t, std::femto>;
std::string format(const std::string&, const time_point<seconds>&,
                   const femtoseconds&, const time_zone&);
bool parse(const std::string&, const std::string&, const time_zone&,
           time_point<seconds>*, femtoseconds*, std::string* err = nullptr);
}  // namespace detail

// Formats the given time_point in the given cctz::time_zone according to
// the provided format string. Uses strftime()-like formatting options,
// with the following extensions:
//
//   - %Ez  - RFC3339-compatible numeric UTC offset (+hh:mm or -hh:mm)
//   - %E*z - Full-resolution numeric UTC offset (+hh:mm:ss or -hh:mm:ss)
//   - %E#S - Seconds with # digits of fractional precision
//   - %E*S - Seconds with full fractional precision (a literal '*')
//   - %E#f - Fractional seconds with # digits of precision
//   - %E*f - Fractional seconds with full precision (a literal '*')
//   - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999)
//
// Note that %E0S behaves like %S, and %E0f produces no characters. In
// contrast %E*f always produces at least one digit, which may be '0'.
//
// Note that %Y produces as many characters as it takes to fully render the
// year. A year outside of [-999:9999] when formatted with %E4Y will produce
// more than four characters, just like %Y.
//
// Tip: Format strings should include the UTC offset (e.g., %z, %Ez, or %E*z)
// so that the resulting string uniquely identifies an absolute time.
//
// Example:
//   cctz::time_zone lax;
//   if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
//   auto tp = cctz::convert(cctz::civil_second(2013, 1, 2, 3, 4, 5), lax);
//   std::string f = cctz::format("%H:%M:%S", tp, lax);  // "03:04:05"
//   f = cctz::format("%H:%M:%E3S", tp, lax);            // "03:04:05.000"
template <typename D>
inline std::string format(const std::string& fmt, const time_point<D>& tp,
                          const time_zone& tz) {
  const auto p = detail::split_seconds(tp);
  const auto n = std::chrono::duration_cast<detail::femtoseconds>(p.second);
  return detail::format(fmt, p.first, n, tz);
}

// Parses an input string according to the provided format string and
// returns the corresponding time_point. Uses strftime()-like formatting
// options, with the same extensions as cctz::format(), but with the
// exceptions that %E#S is interpreted as %E*S, and %E#f as %E*f. %Ez
// and %E*z also accept the same inputs.
//
// %Y consumes as many numeric characters as it can, so the matching data
// should always be terminated with a non-numeric. %E4Y always consumes
// exactly four characters, including any sign.
//
// Unspecified fields are taken from the default date and time of ...
//
//   "1970-01-01 00:00:00.0 +0000"
//
// For example, parsing a string of "15:45" (%H:%M) will return a time_point
// that represents "1970-01-01 15:45:00.0 +0000".
//
// Note that parse() returns time instants, so it makes most sense to parse
// fully-specified date/time strings that include a UTC offset (%z, %Ez, or
// %E*z).
//
// Note also that parse() only heeds the fields year, month, day, hour,
// minute, (fractional) second, and UTC offset. Other fields, like weekday (%a
// or %A), while parsed for syntactic validity, are ignored in the conversion.
//
// Date and time fields that are out-of-range will be treated as errors rather
// than normalizing them like cctz::civil_second() would do. For example, it
// is an error to parse the date "Oct 32, 2013" because 32 is out of range.
//
// A second of ":60" is normalized to ":00" of the following minute with
// fractional seconds discarded. The following table shows how the given
// seconds and subseconds will be parsed:
//
//   "59.x" -> 59.x  // exact
//   "60.x" -> 00.0  // normalized
//   "00.x" -> 00.x  // exact
//
// Errors are indicated by returning false.
//
// Example:
//   const cctz::time_zone tz = ...
//   std::chrono::system_clock::time_point tp;
//   if (cctz::parse("%Y-%m-%d", "2015-10-09", tz, &tp)) {
//     ...
//   }
template <typename D>
inline bool parse(const std::string& fmt, const std::string& input,
                  const time_zone& tz, time_point<D>* tpp) {
  time_point<seconds> sec;
  detail::femtoseconds fs;
  const bool b = detail::parse(fmt, input, tz, &sec, &fs);
  if (b) {
    // TODO: Return false if unrepresentable as a time_point<D>.
    *tpp = std::chrono::time_point_cast<D>(sec);
    *tpp += std::chrono::duration_cast<D>(fs);
  }
  return b;
}

}  // namespace cctz
}  // namespace time_internal
ABSL_NAMESPACE_END
}  // namespace absl

#endif  // ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_H_