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// 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// 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 <string>
26#include <utility>
27
28#include "cctz/civil_time.h"
29
30namespace cctz {
31
32// Convenience aliases. Not intended as public API points.
33template <typename D>
34using time_point = std::chrono::time_point<std::chrono::system_clock, D>;
35using sys_seconds = std::chrono::duration<std::int_fast64_t>;
36
37namespace detail {
38template <typename D>
39inline std::pair<time_point<sys_seconds>, D>
40split_seconds(const time_point<D>& tp) {
41 auto sec = std::chrono::time_point_cast<sys_seconds>(tp);
42 auto sub = tp - sec;
43 if (sub.count() < 0) {
44 sec -= sys_seconds(1);
45 sub += sys_seconds(1);
46 }
47 return {sec, std::chrono::duration_cast<D>(sub)};
48}
49inline std::pair<time_point<sys_seconds>, sys_seconds>
50split_seconds(const time_point<sys_seconds>& tp) {
51 return {tp, sys_seconds(0)};
52}
53} // namespace detail
54
55// cctz::time_zone is an opaque, small, value-type class representing a
56// geo-political region within which particular rules are used for mapping
57// between absolute and civil times. Time zones are named using the TZ
58// identifiers from the IANA Time Zone Database, such as "America/Los_Angeles"
59// or "Australia/Sydney". Time zones are created from factory functions such
60// as load_time_zone(). Note: strings like "PST" and "EDT" are not valid TZ
61// identifiers.
62//
63// Example:
64// cctz::time_zone utc = cctz::utc_time_zone();
65// cctz::time_zone pst = cctz::fixed_time_zone(std::chrono::hours(-8));
66// cctz::time_zone loc = cctz::local_time_zone();
67// cctz::time_zone lax;
68// if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
69//
70// See also:
71// - http://www.iana.org/time-zones
72// - http://en.wikipedia.org/wiki/Zoneinfo
73class time_zone {
74 public:
75 time_zone() : time_zone(nullptr) {} // Equivalent to UTC
76 time_zone(const time_zone&) = default;
77 time_zone& operator=(const time_zone&) = default;
78
79 std::string name() const;
80
81 // An absolute_lookup represents the civil time (cctz::civil_second) within
82 // this time_zone at the given absolute time (time_point). There are
83 // additionally a few other fields that may be useful when working with
84 // older APIs, such as std::tm.
85 //
86 // Example:
87 // const cctz::time_zone tz = ...
88 // const auto tp = std::chrono::system_clock::now();
89 // const cctz::time_zone::absolute_lookup al = tz.lookup(tp);
90 struct absolute_lookup {
91 civil_second cs;
92 // Note: The following fields exist for backward compatibility with older
93 // APIs. Accessing these fields directly is a sign of imprudent logic in
94 // the calling code. Modern time-related code should only access this data
95 // indirectly by way of cctz::format().
96 int offset; // civil seconds east of UTC
97 bool is_dst; // is offset non-standard?
98 const char* abbr; // time-zone abbreviation (e.g., "PST")
99 };
100 absolute_lookup lookup(const time_point<sys_seconds>& tp) const;
101 template <typename D>
102 absolute_lookup lookup(const time_point<D>& tp) const {
103 return lookup(detail::split_seconds(tp).first);
104 }
105
106 // A civil_lookup represents the absolute time(s) (time_point) that
107 // correspond to the given civil time (cctz::civil_second) within this
108 // time_zone. Usually the given civil time represents a unique instant
109 // in time, in which case the conversion is unambiguous. However,
110 // within this time zone, the given civil time may be skipped (e.g.,
111 // during a positive UTC offset shift), or repeated (e.g., during a
112 // negative UTC offset shift). To account for these possibilities,
113 // civil_lookup is richer than just a single time_point.
114 //
115 // In all cases the civil_lookup::kind enum will indicate the nature
116 // of the given civil-time argument, and the pre, trans, and post
117 // members will give the absolute time answers using the pre-transition
118 // offset, the transition point itself, and the post-transition offset,
119 // respectively (all three times are equal if kind == UNIQUE). If any
120 // of these three absolute times is outside the representable range of a
121 // time_point<sys_seconds> the field is set to its maximum/minimum value.
122 //
123 // Example:
124 // cctz::time_zone lax;
125 // if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
126 //
127 // // A unique civil time.
128 // auto jan01 = lax.lookup(cctz::civil_second(2011, 1, 1, 0, 0, 0));
129 // // jan01.kind == cctz::time_zone::civil_lookup::UNIQUE
130 // // jan01.pre is 2011/01/01 00:00:00 -0800
131 // // jan01.trans is 2011/01/01 00:00:00 -0800
132 // // jan01.post is 2011/01/01 00:00:00 -0800
133 //
134 // // A Spring DST transition, when there is a gap in civil time.
135 // auto mar13 = lax.lookup(cctz::civil_second(2011, 3, 13, 2, 15, 0));
136 // // mar13.kind == cctz::time_zone::civil_lookup::SKIPPED
137 // // mar13.pre is 2011/03/13 03:15:00 -0700
138 // // mar13.trans is 2011/03/13 03:00:00 -0700
139 // // mar13.post is 2011/03/13 01:15:00 -0800
140 //
141 // // A Fall DST transition, when civil times are repeated.
142 // auto nov06 = lax.lookup(cctz::civil_second(2011, 11, 6, 1, 15, 0));
143 // // nov06.kind == cctz::time_zone::civil_lookup::REPEATED
144 // // nov06.pre is 2011/11/06 01:15:00 -0700
145 // // nov06.trans is 2011/11/06 01:00:00 -0800
146 // // nov06.post is 2011/11/06 01:15:00 -0800
147 struct civil_lookup {
148 enum civil_kind {
149 UNIQUE, // the civil time was singular (pre == trans == post)
150 SKIPPED, // the civil time did not exist (pre >= trans > post)
151 REPEATED, // the civil time was ambiguous (pre < trans <= post)
152 } kind;
153 time_point<sys_seconds> pre; // uses the pre-transition offset
154 time_point<sys_seconds> trans; // instant of civil-offset change
155 time_point<sys_seconds> post; // uses the post-transition offset
156 };
157 civil_lookup lookup(const civil_second& cs) const;
158
159 class Impl;
160
161 private:
162 explicit time_zone(const Impl* impl) : impl_(impl) {}
163 const Impl* impl_;
164};
165
166// Relational operators.
167bool operator==(time_zone lhs, time_zone rhs);
168inline bool operator!=(time_zone lhs, time_zone rhs) { return !(lhs == rhs); }
169
170// Loads the named time zone. May perform I/O on the initial load.
171// If the name is invalid, or some other kind of error occurs, returns
172// false and "*tz" is set to the UTC time zone.
173bool load_time_zone(const std::string& name, time_zone* tz);
174
175// Returns a time_zone representing UTC. Cannot fail.
176time_zone utc_time_zone();
177
178// Returns a time zone that is a fixed offset (seconds east) from UTC.
179// Note: If the absolute value of the offset is greater than 24 hours
180// you'll get UTC (i.e., zero offset) instead.
181time_zone fixed_time_zone(const sys_seconds& offset);
182
183// Returns a time zone representing the local time zone. Falls back to UTC.
184time_zone local_time_zone();
185
186// Returns the civil time (cctz::civil_second) within the given time zone at
187// the given absolute time (time_point). Since the additional fields provided
188// by the time_zone::absolute_lookup struct should rarely be needed in modern
189// code, this convert() function is simpler and should be preferred.
190template <typename D>
191inline civil_second convert(const time_point<D>& tp, const time_zone& tz) {
192 return tz.lookup(tp).cs;
193}
194
195// Returns the absolute time (time_point) that corresponds to the given civil
196// time within the given time zone. If the civil time is not unique (i.e., if
197// it was either repeated or non-existent), then the returned time_point is
198// the best estimate that preserves relative order. That is, this function
199// guarantees that if cs1 < cs2, then convert(cs1, tz) <= convert(cs2, tz).
200inline time_point<sys_seconds> convert(const civil_second& cs,
201 const time_zone& tz) {
202 const time_zone::civil_lookup cl = tz.lookup(cs);
203 if (cl.kind == time_zone::civil_lookup::SKIPPED) return cl.trans;
204 return cl.pre;
205}
206
207namespace detail {
208using femtoseconds = std::chrono::duration<std::int_fast64_t, std::femto>;
209std::string format(const std::string&, const time_point<sys_seconds>&,
210 const femtoseconds&, const time_zone&);
211bool parse(const std::string&, const std::string&, const time_zone&,
212 time_point<sys_seconds>*, femtoseconds*, std::string* err = nullptr);
213} // namespace detail
214
215// Formats the given time_point in the given cctz::time_zone according to
216// the provided format string. Uses strftime()-like formatting options,
217// with the following extensions:
218//
219// - %Ez - RFC3339-compatible numeric time zone (+hh:mm or -hh:mm)
220// - %E#S - Seconds with # digits of fractional precision
221// - %E*S - Seconds with full fractional precision (a literal '*')
222// - %E#f - Fractional seconds with # digits of precision
223// - %E*f - Fractional seconds with full precision (a literal '*')
224// - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999)
225//
226// Note that %E0S behaves like %S, and %E0f produces no characters. In
227// contrast %E*f always produces at least one digit, which may be '0'.
228//
229// Note that %Y produces as many characters as it takes to fully render the
230// year. A year outside of [-999:9999] when formatted with %E4Y will produce
231// more than four characters, just like %Y.
232//
233// Tip: Format strings should include the UTC offset (e.g., %z or %Ez) so that
234// the resultng string uniquely identifies an absolute time.
235//
236// Example:
237// cctz::time_zone lax;
238// if (!cctz::load_time_zone("America/Los_Angeles", &lax)) { ... }
239// auto tp = cctz::convert(cctz::civil_second(2013, 1, 2, 3, 4, 5), lax);
240// std::string f = cctz::format("%H:%M:%S", tp, lax); // "03:04:05"
241// f = cctz::format("%H:%M:%E3S", tp, lax); // "03:04:05.000"
242template <typename D>
243inline std::string format(const std::string& fmt, const time_point<D>& tp,
244 const time_zone& tz) {
245 const auto p = detail::split_seconds(tp);
246 const auto n = std::chrono::duration_cast<detail::femtoseconds>(p.second);
247 return detail::format(fmt, p.first, n, tz);
248}
249
250// Parses an input string according to the provided format string and
251// returns the corresponding time_point. Uses strftime()-like formatting
252// options, with the same extensions as cctz::format(), but with the
253// exceptions that %E#S is interpreted as %E*S, and %E#f as %E*f.
254//
255// %Y consumes as many numeric characters as it can, so the matching data
256// should always be terminated with a non-numeric. %E4Y always consumes
257// exactly four characters, including any sign.
258//
259// Unspecified fields are taken from the default date and time of ...
260//
261// "1970-01-01 00:00:00.0 +0000"
262//
263// For example, parsing a string of "15:45" (%H:%M) will return a time_point
264// that represents "1970-01-01 15:45:00.0 +0000".
265//
266// Note that parse() returns time instants, so it makes most sense to parse
267// fully-specified date/time strings that include a UTC offset (%z or %Ez).
268//
269// Note also that parse() only heeds the fields year, month, day, hour,
270// minute, (fractional) second, and UTC offset. Other fields, like weekday (%a
271// or %A), while parsed for syntactic validity, are ignored in the conversion.
272//
273// Date and time fields that are out-of-range will be treated as errors rather
274// than normalizing them like cctz::civil_second() would do. For example, it
275// is an error to parse the date "Oct 32, 2013" because 32 is out of range.
276//
277// A second of ":60" is normalized to ":00" of the following minute with
278// fractional seconds discarded. The following table shows how the given
279// seconds and subseconds will be parsed:
280//
281// "59.x" -> 59.x // exact
282// "60.x" -> 00.0 // normalized
283// "00.x" -> 00.x // exact
284//
285// Errors are indicated by returning false.
286//
287// Example:
288// const cctz::time_zone tz = ...
289// std::chrono::system_clock::time_point tp;
290// if (cctz::parse("%Y-%m-%d", "2015-10-09", tz, &tp)) {
291// ...
292// }
293template <typename D>
294inline bool parse(const std::string& fmt, const std::string& input,
295 const time_zone& tz, time_point<D>* tpp) {
296 time_point<sys_seconds> sec;
297 detail::femtoseconds fs;
298 const bool b = detail::parse(fmt, input, tz, &sec, &fs);
299 if (b) {
300 // TODO: Return false if unrepresentable as a time_point<D>.
301 *tpp = std::chrono::time_point_cast<D>(sec);
302 *tpp += std::chrono::duration_cast<D>(fs);
303 }
304 return b;
305}
306
307} // namespace cctz
308
309#endif // CCTZ_TIME_ZONE_H_
310