/src/cctz/src/time_zone_format.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(HAS_STRPTIME) |
16 | | # if defined(_MSC_VER) || defined(__MINGW32__) || defined(__VXWORKS__) |
17 | | # define HAS_STRPTIME 0 |
18 | | # else |
19 | | # define HAS_STRPTIME 1 // Assume everyone else has strptime(). |
20 | | # endif |
21 | | #endif |
22 | | |
23 | | #if HAS_STRPTIME |
24 | | # if !defined(_XOPEN_SOURCE) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && \ |
25 | | !defined(__APPLE__) |
26 | | # define _XOPEN_SOURCE 500 // Exposes definitions for SUSv2 (UNIX 98). |
27 | | # endif |
28 | | #endif |
29 | | |
30 | | #include "cctz/time_zone.h" |
31 | | |
32 | | // Include time.h directly since, by C++ standards, ctime doesn't have to |
33 | | // declare strptime. |
34 | | #include <time.h> |
35 | | |
36 | | #include <cctype> |
37 | | #include <chrono> |
38 | | #include <cstddef> |
39 | | #include <cstdint> |
40 | | #include <cstring> |
41 | | #include <ctime> |
42 | | #include <limits> |
43 | | #include <string> |
44 | | #if !HAS_STRPTIME |
45 | | #include <iomanip> |
46 | | #include <sstream> |
47 | | #endif |
48 | | |
49 | | #include "cctz/civil_time.h" |
50 | | #include "time_zone_if.h" |
51 | | |
52 | | namespace cctz { |
53 | | namespace detail { |
54 | | |
55 | | namespace { |
56 | | |
57 | | #if !HAS_STRPTIME |
58 | | // Build a strptime() using C++11's std::get_time(). |
59 | | char* strptime(const char* s, const char* fmt, std::tm* tm) { |
60 | | std::istringstream input(s); |
61 | | input >> std::get_time(tm, fmt); |
62 | | if (input.fail()) return nullptr; |
63 | | return const_cast<char*>(s) + |
64 | | (input.eof() ? strlen(s) : static_cast<std::size_t>(input.tellg())); |
65 | | } |
66 | | #endif |
67 | | |
68 | | // Convert a cctz::weekday to a tm_wday value (0-6, Sunday = 0). |
69 | 8.48k | int ToTmWday(weekday wd) { |
70 | 8.48k | switch (wd) { |
71 | 489 | case weekday::sunday: |
72 | 489 | return 0; |
73 | 440 | case weekday::monday: |
74 | 440 | return 1; |
75 | 5.82k | case weekday::tuesday: |
76 | 5.82k | return 2; |
77 | 442 | case weekday::wednesday: |
78 | 442 | return 3; |
79 | 492 | case weekday::thursday: |
80 | 492 | return 4; |
81 | 374 | case weekday::friday: |
82 | 374 | return 5; |
83 | 424 | case weekday::saturday: |
84 | 424 | return 6; |
85 | 8.48k | } |
86 | 0 | return 0; /*NOTREACHED*/ |
87 | 8.48k | } |
88 | | |
89 | | // Convert a tm_wday value (0-6, Sunday = 0) to a cctz::weekday. |
90 | 282 | weekday FromTmWday(int tm_wday) { |
91 | 282 | switch (tm_wday) { |
92 | 7 | case 0: |
93 | 7 | return weekday::sunday; |
94 | 14 | case 1: |
95 | 14 | return weekday::monday; |
96 | 1 | case 2: |
97 | 1 | return weekday::tuesday; |
98 | 3 | case 3: |
99 | 3 | return weekday::wednesday; |
100 | 248 | case 4: |
101 | 248 | return weekday::thursday; |
102 | 4 | case 5: |
103 | 4 | return weekday::friday; |
104 | 5 | case 6: |
105 | 5 | return weekday::saturday; |
106 | 282 | } |
107 | 0 | return weekday::sunday; /*NOTREACHED*/ |
108 | 282 | } |
109 | | |
110 | 8.48k | std::tm ToTM(const time_zone::absolute_lookup& al) { |
111 | 8.48k | std::tm tm{}; |
112 | 8.48k | tm.tm_sec = al.cs.second(); |
113 | 8.48k | tm.tm_min = al.cs.minute(); |
114 | 8.48k | tm.tm_hour = al.cs.hour(); |
115 | 8.48k | tm.tm_mday = al.cs.day(); |
116 | 8.48k | tm.tm_mon = al.cs.month() - 1; |
117 | | |
118 | | // Saturate tm.tm_year in cases of over/underflow. |
119 | 8.48k | if (al.cs.year() < std::numeric_limits<int>::min() + 1900) { |
120 | 0 | tm.tm_year = std::numeric_limits<int>::min(); |
121 | 8.48k | } else if (al.cs.year() - 1900 > std::numeric_limits<int>::max()) { |
122 | 620 | tm.tm_year = std::numeric_limits<int>::max(); |
123 | 7.86k | } else { |
124 | 7.86k | tm.tm_year = static_cast<int>(al.cs.year() - 1900); |
125 | 7.86k | } |
126 | | |
127 | 8.48k | tm.tm_wday = ToTmWday(get_weekday(al.cs)); |
128 | 8.48k | tm.tm_yday = get_yearday(al.cs) - 1; |
129 | 8.48k | tm.tm_isdst = al.is_dst ? 1 : 0; |
130 | 8.48k | return tm; |
131 | 8.48k | } |
132 | | |
133 | | // Returns the week of the year [0:53] given a civil day and the day on |
134 | | // which weeks are defined to start. |
135 | 4.39k | int ToWeek(const civil_day& cd, weekday week_start) { |
136 | 4.39k | const civil_day d(cd.year() % 400, cd.month(), cd.day()); |
137 | 4.39k | return static_cast<int>((d - prev_weekday(civil_year(d), week_start)) / 7); |
138 | 4.39k | } |
139 | | |
140 | | const char kDigits[] = "0123456789"; |
141 | | |
142 | | // Formats a 64-bit integer in the given field width. Note that it is up |
143 | | // to the caller of Format64() [and Format02d()/FormatOffset()] to ensure |
144 | | // that there is sufficient space before ep to hold the conversion. |
145 | 72.8k | char* Format64(char* ep, int width, std::int_fast64_t v) { |
146 | 72.8k | bool neg = false; |
147 | 72.8k | if (v < 0) { |
148 | 869 | --width; |
149 | 869 | neg = true; |
150 | 869 | if (v == std::numeric_limits<std::int_fast64_t>::min()) { |
151 | | // Avoid negating minimum value. |
152 | 0 | std::int_fast64_t last_digit = -(v % 10); |
153 | 0 | v /= 10; |
154 | 0 | if (last_digit < 0) { |
155 | 0 | ++v; |
156 | 0 | last_digit += 10; |
157 | 0 | } |
158 | 0 | --width; |
159 | 0 | *--ep = kDigits[last_digit]; |
160 | 0 | } |
161 | 869 | v = -v; |
162 | 869 | } |
163 | 113k | do { |
164 | 113k | --width; |
165 | 113k | *--ep = kDigits[v % 10]; |
166 | 113k | } while (v /= 10); |
167 | 976k | while (--width >= 0) *--ep = '0'; // zero pad |
168 | 72.8k | if (neg) *--ep = '-'; |
169 | 72.8k | return ep; |
170 | 72.8k | } |
171 | | |
172 | | // Formats [0 .. 99] as %02d. |
173 | 136k | char* Format02d(char* ep, int v) { |
174 | 136k | *--ep = kDigits[v % 10]; |
175 | 136k | *--ep = kDigits[(v / 10) % 10]; |
176 | 136k | return ep; |
177 | 136k | } |
178 | | |
179 | | // Formats a UTC offset, like +00:00. |
180 | 25.7k | char* FormatOffset(char* ep, int offset, const char* mode) { |
181 | | // TODO: Follow the RFC3339 "Unknown Local Offset Convention" and |
182 | | // generate a "negative zero" when we're formatting a zero offset |
183 | | // as the result of a failed load_time_zone(). |
184 | 25.7k | char sign = '+'; |
185 | 25.7k | if (offset < 0) { |
186 | 6.13k | offset = -offset; // bounded by 24h so no overflow |
187 | 6.13k | sign = '-'; |
188 | 6.13k | } |
189 | 25.7k | const int seconds = offset % 60; |
190 | 25.7k | const int minutes = (offset /= 60) % 60; |
191 | 25.7k | const int hours = offset /= 60; |
192 | 25.7k | const char sep = mode[0]; |
193 | 25.7k | const bool ext = (sep != '\0' && mode[1] == '*'); |
194 | 25.7k | const bool ccc = (ext && mode[2] == ':'); |
195 | 25.7k | if (ext && (!ccc || seconds != 0)) { |
196 | 10.6k | ep = Format02d(ep, seconds); |
197 | 10.6k | *--ep = sep; |
198 | 15.0k | } else { |
199 | | // If we're not rendering seconds, sub-minute negative offsets |
200 | | // should get a positive sign (e.g., offset=-10s => "+00:00"). |
201 | 15.0k | if (hours == 0 && minutes == 0) sign = '+'; |
202 | 15.0k | } |
203 | 25.7k | if (!ccc || minutes != 0 || seconds != 0) { |
204 | 24.7k | ep = Format02d(ep, minutes); |
205 | 24.7k | if (sep != '\0') *--ep = sep; |
206 | 24.7k | } |
207 | 25.7k | ep = Format02d(ep, hours); |
208 | 25.7k | *--ep = sign; |
209 | 25.7k | return ep; |
210 | 25.7k | } |
211 | | |
212 | | // Formats a std::tm using strftime(3). |
213 | 129k | void FormatTM(std::string* out, const std::string& fmt, const std::tm& tm) { |
214 | | // We assume that 16 times the length of the format string will |
215 | | // be sufficient to store the result. The extreme case appears |
216 | | // to be "%c" (2 chars), which, in the POSIX locale, produces |
217 | | // "Thu Jan 1 00:00:00 1970" (24 chars). |
218 | 129k | auto out_size = out->size(); |
219 | 129k | auto buf_size = (16 * fmt.size()) + 1; |
220 | 129k | out->resize(out_size + buf_size); |
221 | 129k | auto len = strftime(&(*out)[out_size], buf_size, fmt.c_str(), &tm); |
222 | 129k | out->resize(out_size + len); |
223 | 129k | } |
224 | | |
225 | | // Used for %E#S/%E#f specifiers and for data values in parse(). |
226 | | template <typename T> |
227 | 42.0k | const char* ParseInt(const char* dp, int width, T min, T max, T* vp) { |
228 | 42.0k | if (dp != nullptr) { |
229 | 41.2k | const T kmin = std::numeric_limits<T>::min(); |
230 | 41.2k | bool erange = false; |
231 | 41.2k | bool neg = false; |
232 | 41.2k | T value = 0; |
233 | 41.2k | if (*dp == '-') { |
234 | 4.00k | neg = true; |
235 | 4.00k | if (width <= 0 || --width != 0) { |
236 | 4.00k | ++dp; |
237 | 4.00k | } else { |
238 | 0 | dp = nullptr; // width was 1 |
239 | 0 | } |
240 | 4.00k | } |
241 | 41.2k | if (const char* const bp = dp) { |
242 | 194k | while (const char* cp = strchr(kDigits, *dp)) { |
243 | 176k | int d = static_cast<int>(cp - kDigits); |
244 | 176k | if (d >= 10) break; |
245 | 172k | if (value < kmin / 10) { |
246 | 4.53k | erange = true; |
247 | 4.53k | break; |
248 | 4.53k | } |
249 | 167k | value *= 10; |
250 | 167k | if (value < kmin + d) { |
251 | 440 | erange = true; |
252 | 440 | break; |
253 | 440 | } |
254 | 167k | value -= d; |
255 | 167k | dp += 1; |
256 | 167k | if (width > 0 && --width == 0) break; |
257 | 167k | } |
258 | 41.2k | if (dp != bp && !erange && (neg || value != kmin)) { |
259 | 33.3k | if (!neg || value != 0) { |
260 | 31.9k | if (!neg) value = -value; // make positive |
261 | 31.9k | if (min <= value && value <= max) { |
262 | 26.2k | *vp = value; |
263 | 26.2k | } else { |
264 | 5.72k | dp = nullptr; |
265 | 5.72k | } |
266 | 31.9k | } else { |
267 | 1.39k | dp = nullptr; |
268 | 1.39k | } |
269 | 33.3k | } else { |
270 | 7.92k | dp = nullptr; |
271 | 7.92k | } |
272 | 41.2k | } |
273 | 41.2k | } |
274 | 42.0k | return dp; |
275 | 42.0k | } time_zone_format.cc:char const* cctz::detail::(anonymous namespace)::ParseInt<int>(char const*, int, int, int, int*) Line | Count | Source | 227 | 38.6k | const char* ParseInt(const char* dp, int width, T min, T max, T* vp) { | 228 | 38.6k | if (dp != nullptr) { | 229 | 37.8k | const T kmin = std::numeric_limits<T>::min(); | 230 | 37.8k | bool erange = false; | 231 | 37.8k | bool neg = false; | 232 | 37.8k | T value = 0; | 233 | 37.8k | if (*dp == '-') { | 234 | 2.40k | neg = true; | 235 | 2.40k | if (width <= 0 || --width != 0) { | 236 | 2.40k | ++dp; | 237 | 2.40k | } else { | 238 | 0 | dp = nullptr; // width was 1 | 239 | 0 | } | 240 | 2.40k | } | 241 | 37.8k | if (const char* const bp = dp) { | 242 | 161k | while (const char* cp = strchr(kDigits, *dp)) { | 243 | 143k | int d = static_cast<int>(cp - kDigits); | 244 | 143k | if (d >= 10) break; | 245 | 141k | if (value < kmin / 10) { | 246 | 4.38k | erange = true; | 247 | 4.38k | break; | 248 | 4.38k | } | 249 | 137k | value *= 10; | 250 | 137k | if (value < kmin + d) { | 251 | 437 | erange = true; | 252 | 437 | break; | 253 | 437 | } | 254 | 137k | value -= d; | 255 | 137k | dp += 1; | 256 | 137k | if (width > 0 && --width == 0) break; | 257 | 137k | } | 258 | 37.8k | if (dp != bp && !erange && (neg || value != kmin)) { | 259 | 30.1k | if (!neg || value != 0) { | 260 | 28.7k | if (!neg) value = -value; // make positive | 261 | 28.7k | if (min <= value && value <= max) { | 262 | 23.0k | *vp = value; | 263 | 23.0k | } else { | 264 | 5.72k | dp = nullptr; | 265 | 5.72k | } | 266 | 28.7k | } else { | 267 | 1.38k | dp = nullptr; | 268 | 1.38k | } | 269 | 30.1k | } else { | 270 | 7.69k | dp = nullptr; | 271 | 7.69k | } | 272 | 37.8k | } | 273 | 37.8k | } | 274 | 38.6k | return dp; | 275 | 38.6k | } |
time_zone_format.cc:char const* cctz::detail::(anonymous namespace)::ParseInt<long>(char const*, int, long, long, long*) Line | Count | Source | 227 | 3.42k | const char* ParseInt(const char* dp, int width, T min, T max, T* vp) { | 228 | 3.42k | if (dp != nullptr) { | 229 | 3.42k | const T kmin = std::numeric_limits<T>::min(); | 230 | 3.42k | bool erange = false; | 231 | 3.42k | bool neg = false; | 232 | 3.42k | T value = 0; | 233 | 3.42k | if (*dp == '-') { | 234 | 1.60k | neg = true; | 235 | 1.60k | if (width <= 0 || --width != 0) { | 236 | 1.60k | ++dp; | 237 | 1.60k | } else { | 238 | 0 | dp = nullptr; // width was 1 | 239 | 0 | } | 240 | 1.60k | } | 241 | 3.42k | if (const char* const bp = dp) { | 242 | 33.2k | while (const char* cp = strchr(kDigits, *dp)) { | 243 | 32.4k | int d = static_cast<int>(cp - kDigits); | 244 | 32.4k | if (d >= 10) break; | 245 | 30.1k | if (value < kmin / 10) { | 246 | 144 | erange = true; | 247 | 144 | break; | 248 | 144 | } | 249 | 30.0k | value *= 10; | 250 | 30.0k | if (value < kmin + d) { | 251 | 3 | erange = true; | 252 | 3 | break; | 253 | 3 | } | 254 | 29.9k | value -= d; | 255 | 29.9k | dp += 1; | 256 | 29.9k | if (width > 0 && --width == 0) break; | 257 | 29.9k | } | 258 | 3.42k | if (dp != bp && !erange && (neg || value != kmin)) { | 259 | 3.19k | if (!neg || value != 0) { | 260 | 3.18k | if (!neg) value = -value; // make positive | 261 | 3.18k | if (min <= value && value <= max) { | 262 | 3.18k | *vp = value; | 263 | 3.18k | } else { | 264 | 0 | dp = nullptr; | 265 | 0 | } | 266 | 3.18k | } else { | 267 | 7 | dp = nullptr; | 268 | 7 | } | 269 | 3.19k | } else { | 270 | 235 | dp = nullptr; | 271 | 235 | } | 272 | 3.42k | } | 273 | 3.42k | } | 274 | 3.42k | return dp; | 275 | 3.42k | } |
|
276 | | |
277 | | // The number of base-10 digits that can be represented by a signed 64-bit |
278 | | // integer. That is, 10^kDigits10_64 <= 2^63 - 1 < 10^(kDigits10_64 + 1). |
279 | | const int kDigits10_64 = 18; |
280 | | |
281 | | // 10^n for everything that can be represented by a signed 64-bit integer. |
282 | | const std::int_fast64_t kExp10[kDigits10_64 + 1] = { |
283 | | 1, |
284 | | 10, |
285 | | 100, |
286 | | 1000, |
287 | | 10000, |
288 | | 100000, |
289 | | 1000000, |
290 | | 10000000, |
291 | | 100000000, |
292 | | 1000000000, |
293 | | 10000000000, |
294 | | 100000000000, |
295 | | 1000000000000, |
296 | | 10000000000000, |
297 | | 100000000000000, |
298 | | 1000000000000000, |
299 | | 10000000000000000, |
300 | | 100000000000000000, |
301 | | 1000000000000000000, |
302 | | }; |
303 | | |
304 | | } // namespace |
305 | | |
306 | | // Uses strftime(3) to format the given Time. The following extended format |
307 | | // specifiers are also supported: |
308 | | // |
309 | | // - %Ez - RFC3339-compatible numeric UTC offset (+hh:mm or -hh:mm) |
310 | | // - %E*z - Full-resolution numeric UTC offset (+hh:mm:ss or -hh:mm:ss) |
311 | | // - %E#S - Seconds with # digits of fractional precision |
312 | | // - %E*S - Seconds with full fractional precision (a literal '*') |
313 | | // - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999) |
314 | | // - %ET - The RFC3339 "date-time" separator "T" |
315 | | // |
316 | | // The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are |
317 | | // handled internally for performance reasons. strftime(3) is slow due to |
318 | | // a POSIX requirement to respect changes to ${TZ}. |
319 | | // |
320 | | // The TZ/GNU %s extension is handled internally because strftime() has |
321 | | // to use mktime() to generate it, and that assumes the local time zone. |
322 | | // |
323 | | // We also handle the %z and %Z specifiers to accommodate platforms that do |
324 | | // not support the tm_gmtoff and tm_zone extensions to std::tm. |
325 | | // |
326 | | // Requires that zero() <= fs < seconds(1). |
327 | | std::string format(const std::string& format, const time_point<seconds>& tp, |
328 | 8.48k | const detail::femtoseconds& fs, const time_zone& tz) { |
329 | 8.48k | std::string result; |
330 | 8.48k | result.reserve(2 * format.size()); // A guess for the result size. |
331 | 8.48k | const time_zone::absolute_lookup al = tz.lookup(tp); |
332 | 8.48k | const std::tm tm = ToTM(al); |
333 | | |
334 | | // Scratch buffer for internal conversions. |
335 | 8.48k | char buf[6 + (kDigits10_64 + 2)]; // enough for longest conversion (%F) |
336 | 8.48k | char* const ep = buf + sizeof(buf); |
337 | 8.48k | char* bp; // works back from ep |
338 | | |
339 | | // Maintain three, disjoint subsequences that span format. |
340 | | // [format.begin() ... pending) : already formatted into result |
341 | | // [pending ... cur) : formatting pending, but no special cases |
342 | | // [cur ... format.end()) : unexamined |
343 | | // Initially, everything is in the unexamined part. |
344 | 8.48k | const char* pending = format.data(); |
345 | 8.48k | const char* cur = pending; |
346 | 8.48k | const char* const end = pending + format.size(); |
347 | | |
348 | 192k | while (cur != end) { // while something is unexamined |
349 | | // Moves cur to the next percent sign. |
350 | 183k | const char* start = cur; |
351 | 31.1M | while (cur != end && *cur != '%') { |
352 | 31.0M | if (*cur == '\0' && pending != start) { |
353 | 8.02k | FormatTM(&result, std::string(pending, cur), tm); |
354 | 8.02k | pending = start = cur; |
355 | 8.02k | } |
356 | 31.0M | ++cur; |
357 | 31.0M | } |
358 | | |
359 | | // If the new pending text is all ordinary, copy it out. |
360 | 183k | if (cur != start && pending == start) { |
361 | 22.0k | result.append(pending, cur); |
362 | 22.0k | pending = start = cur; |
363 | 22.0k | } |
364 | | |
365 | | // Span the sequential percent signs. |
366 | 183k | const char* const percent = cur; |
367 | 408k | while (cur != end && *cur == '%') ++cur; |
368 | | |
369 | | // If the new pending text is all percents, copy out one |
370 | | // percent for every matched pair, then skip those pairs. |
371 | 183k | if (cur != start && pending == start) { |
372 | 122k | std::size_t escaped = static_cast<std::size_t>(cur - pending) / 2; |
373 | 122k | result.append(pending, escaped); |
374 | 122k | pending += escaped * 2; |
375 | | // Also copy out a single trailing percent. |
376 | 122k | if (pending != cur && cur == end) { |
377 | 79 | result.push_back(*pending++); |
378 | 79 | } |
379 | 122k | } |
380 | | |
381 | | // Loop unless we have an unescaped percent. |
382 | 183k | if (cur == end || (cur - percent) % 2 == 0) continue; |
383 | | |
384 | | // Simple specifiers that we handle ourselves. |
385 | 178k | if (*cur == '\0' || strchr("YmdeFUuWwHMSTzZs%", *cur)) { |
386 | 19.1k | FormatTM(&result, std::string(pending, cur - 1), tm); |
387 | 19.1k | switch (*cur) { |
388 | 1.77k | case '\0': |
389 | | // Because we allow NULs in the format string, we must give |
390 | | // some meaning to the "%\0" specifier. We choose the common |
391 | | // (but undefined) strftime() behavior of echoing unknown |
392 | | // specifiers. |
393 | 1.77k | result.push_back('%'); |
394 | 1.77k | result.push_back('\0'); |
395 | 1.77k | break; |
396 | 635 | case 'Y': |
397 | | // This avoids the tm.tm_year overflow problem for %Y, however |
398 | | // tm.tm_year will still be used by other specifiers like %D. |
399 | 635 | bp = Format64(ep, 0, al.cs.year()); |
400 | 635 | result.append(bp, ep); |
401 | 635 | break; |
402 | 213 | case 'm': |
403 | 213 | bp = Format02d(ep, al.cs.month()); |
404 | 213 | result.append(bp, ep); |
405 | 213 | break; |
406 | 346 | case 'd': |
407 | 1.06k | case 'e': |
408 | 1.06k | bp = Format02d(ep, al.cs.day()); |
409 | 1.06k | if (*cur == 'e' && *bp == '0') *bp = ' '; // for Windows |
410 | 1.06k | result.append(bp, ep); |
411 | 1.06k | break; |
412 | 471 | case 'F': |
413 | 471 | bp = Format02d(ep, al.cs.day()); |
414 | 471 | *--bp = '-'; |
415 | 471 | bp = Format02d(bp, al.cs.month()); |
416 | 471 | *--bp = '-'; |
417 | 471 | bp = Format64(bp, 0, al.cs.year()); |
418 | 471 | result.append(bp, ep); |
419 | 471 | break; |
420 | 1.71k | case 'U': |
421 | 1.71k | bp = Format02d(ep, ToWeek(civil_day(al.cs), weekday::sunday)); |
422 | 1.71k | result.append(bp, ep); |
423 | 1.71k | break; |
424 | 604 | case 'u': |
425 | 604 | bp = Format64(ep, 0, tm.tm_wday ? tm.tm_wday : 7); |
426 | 604 | result.append(bp, ep); |
427 | 604 | break; |
428 | 2.67k | case 'W': |
429 | 2.67k | bp = Format02d(ep, ToWeek(civil_day(al.cs), weekday::monday)); |
430 | 2.67k | result.append(bp, ep); |
431 | 2.67k | break; |
432 | 198 | case 'w': |
433 | 198 | bp = Format64(ep, 0, tm.tm_wday); |
434 | 198 | result.append(bp, ep); |
435 | 198 | break; |
436 | 229 | case 'H': |
437 | 229 | bp = Format02d(ep, al.cs.hour()); |
438 | 229 | result.append(bp, ep); |
439 | 229 | break; |
440 | 789 | case 'M': |
441 | 789 | bp = Format02d(ep, al.cs.minute()); |
442 | 789 | result.append(bp, ep); |
443 | 789 | break; |
444 | 1.42k | case 'S': |
445 | 1.42k | bp = Format02d(ep, al.cs.second()); |
446 | 1.42k | result.append(bp, ep); |
447 | 1.42k | break; |
448 | 498 | case 'T': |
449 | 498 | bp = Format02d(ep, al.cs.second()); |
450 | 498 | *--bp = ':'; |
451 | 498 | bp = Format02d(bp, al.cs.minute()); |
452 | 498 | *--bp = ':'; |
453 | 498 | bp = Format02d(bp, al.cs.hour()); |
454 | 498 | result.append(bp, ep); |
455 | 498 | break; |
456 | 5.09k | case 'z': |
457 | 5.09k | bp = FormatOffset(ep, al.offset, ""); |
458 | 5.09k | result.append(bp, ep); |
459 | 5.09k | break; |
460 | 761 | case 'Z': |
461 | 761 | result.append(al.abbr); |
462 | 761 | break; |
463 | 966 | case 's': |
464 | 966 | bp = Format64(ep, 0, ToUnixSeconds(tp)); |
465 | 966 | result.append(bp, ep); |
466 | 966 | break; |
467 | 0 | case '%': |
468 | 0 | result.push_back('%'); |
469 | 0 | break; |
470 | 19.1k | } |
471 | 19.1k | pending = ++cur; |
472 | 19.1k | continue; |
473 | 19.1k | } |
474 | | |
475 | | // More complex specifiers that we handle ourselves. |
476 | 159k | if (*cur == ':' && cur + 1 != end) { |
477 | 18.5k | if (*(cur + 1) == 'z') { |
478 | | // Formats %:z. |
479 | 4.37k | FormatTM(&result, std::string(pending, cur - 1), tm); |
480 | 4.37k | bp = FormatOffset(ep, al.offset, ":"); |
481 | 4.37k | result.append(bp, ep); |
482 | 4.37k | pending = cur += 2; |
483 | 4.37k | continue; |
484 | 4.37k | } |
485 | 14.1k | if (*(cur + 1) == ':' && cur + 2 != end) { |
486 | 9.22k | if (*(cur + 2) == 'z') { |
487 | | // Formats %::z. |
488 | 4.02k | FormatTM(&result, std::string(pending, cur - 1), tm); |
489 | 4.02k | bp = FormatOffset(ep, al.offset, ":*"); |
490 | 4.02k | result.append(bp, ep); |
491 | 4.02k | pending = cur += 3; |
492 | 4.02k | continue; |
493 | 4.02k | } |
494 | 5.20k | if (*(cur + 2) == ':' && cur + 3 != end) { |
495 | 2.94k | if (*(cur + 3) == 'z') { |
496 | | // Formats %:::z. |
497 | 1.58k | FormatTM(&result, std::string(pending, cur - 1), tm); |
498 | 1.58k | bp = FormatOffset(ep, al.offset, ":*:"); |
499 | 1.58k | result.append(bp, ep); |
500 | 1.58k | pending = cur += 4; |
501 | 1.58k | continue; |
502 | 1.58k | } |
503 | 2.94k | } |
504 | 5.20k | } |
505 | 14.1k | } |
506 | | |
507 | | // Loop if there is no E modifier. |
508 | 149k | if (*cur != 'E' || ++cur == end) continue; |
509 | | |
510 | | // Format our extensions. |
511 | 107k | if (*cur == 'T') { |
512 | | // Formats %ET. |
513 | 2.51k | FormatTM(&result, std::string(pending, cur - 2), tm); |
514 | 2.51k | result.append("T"); |
515 | 2.51k | pending = ++cur; |
516 | 105k | } else if (*cur == 'z') { |
517 | | // Formats %Ez. |
518 | 4.39k | FormatTM(&result, std::string(pending, cur - 2), tm); |
519 | 4.39k | bp = FormatOffset(ep, al.offset, ":"); |
520 | 4.39k | result.append(bp, ep); |
521 | 4.39k | pending = ++cur; |
522 | 100k | } else if (*cur == '*' && cur + 1 != end && *(cur + 1) == 'z') { |
523 | | // Formats %E*z. |
524 | 6.25k | FormatTM(&result, std::string(pending, cur - 2), tm); |
525 | 6.25k | bp = FormatOffset(ep, al.offset, ":*"); |
526 | 6.25k | result.append(bp, ep); |
527 | 6.25k | pending = cur += 2; |
528 | 94.3k | } else if (*cur == '*' && cur + 1 != end && |
529 | 65.4k | (*(cur + 1) == 'S' || *(cur + 1) == 'f')) { |
530 | | // Formats %E*S or %E*F. |
531 | 63.7k | FormatTM(&result, std::string(pending, cur - 2), tm); |
532 | 63.7k | char* cp = ep; |
533 | 63.7k | bp = Format64(cp, 15, fs.count()); |
534 | 1.02M | while (cp != bp && cp[-1] == '0') --cp; |
535 | 63.7k | switch (*(cur + 1)) { |
536 | 62.2k | case 'S': |
537 | 62.2k | if (cp != bp) *--bp = '.'; |
538 | 62.2k | bp = Format02d(bp, al.cs.second()); |
539 | 62.2k | break; |
540 | 1.49k | case 'f': |
541 | 1.49k | if (cp == bp) *--bp = '0'; |
542 | 1.49k | break; |
543 | 63.7k | } |
544 | 63.7k | result.append(bp, cp); |
545 | 63.7k | pending = cur += 2; |
546 | 63.7k | } else if (*cur == '4' && cur + 1 != end && *(cur + 1) == 'Y') { |
547 | | // Formats %E4Y. |
548 | 3.26k | FormatTM(&result, std::string(pending, cur - 2), tm); |
549 | 3.26k | bp = Format64(ep, 4, al.cs.year()); |
550 | 3.26k | result.append(bp, ep); |
551 | 3.26k | pending = cur += 2; |
552 | 27.3k | } else if (std::isdigit(*cur)) { |
553 | | // Possibly found %E#S or %E#f. |
554 | 17.3k | int n = 0; |
555 | 17.3k | if (const char* np = ParseInt(cur, 0, 0, 1024, &n)) { |
556 | 7.42k | if (*np == 'S' || *np == 'f') { |
557 | | // Formats %E#S or %E#f. |
558 | 3.92k | FormatTM(&result, std::string(pending, cur - 2), tm); |
559 | 3.92k | bp = ep; |
560 | 3.92k | if (n > 0) { |
561 | 2.93k | if (n > kDigits10_64) n = kDigits10_64; |
562 | 2.93k | bp = Format64(bp, n, (n > 15) ? fs.count() * kExp10[n - 15] |
563 | 2.93k | : fs.count() / kExp10[15 - n]); |
564 | 2.93k | if (*np == 'S') *--bp = '.'; |
565 | 2.93k | } |
566 | 3.92k | if (*np == 'S') bp = Format02d(bp, al.cs.second()); |
567 | 3.92k | result.append(bp, ep); |
568 | 3.92k | pending = cur = ++np; |
569 | 3.92k | } |
570 | 7.42k | } |
571 | 17.3k | } |
572 | 107k | } |
573 | | |
574 | | // Formats any remaining data. |
575 | 8.48k | FormatTM(&result, std::string(pending, end), tm); |
576 | | |
577 | 8.48k | return result; |
578 | 8.48k | } |
579 | | |
580 | | namespace { |
581 | | |
582 | 6.93k | const char* ParseOffset(const char* dp, const char* mode, int* offset) { |
583 | 6.93k | if (dp != nullptr) { |
584 | 6.93k | const char first = *dp++; |
585 | 6.93k | if (first == '+' || first == '-') { |
586 | 5.50k | char sep = mode[0]; |
587 | 5.50k | int hours = 0; |
588 | 5.50k | int minutes = 0; |
589 | 5.50k | int seconds = 0; |
590 | 5.50k | const char* ap = ParseInt(dp, 2, 0, 23, &hours); |
591 | 5.50k | if (ap != nullptr && ap - dp == 2) { |
592 | 5.45k | dp = ap; |
593 | 5.45k | if (sep != '\0' && *ap == sep) ++ap; |
594 | 5.45k | const char* bp = ParseInt(ap, 2, 0, 59, &minutes); |
595 | 5.45k | if (bp != nullptr && bp - ap == 2) { |
596 | 2.45k | dp = bp; |
597 | 2.45k | if (sep != '\0' && *bp == sep) ++bp; |
598 | 2.45k | const char* cp = ParseInt(bp, 2, 0, 59, &seconds); |
599 | 2.45k | if (cp != nullptr && cp - bp == 2) dp = cp; |
600 | 2.45k | } |
601 | 5.45k | *offset = ((hours * 60 + minutes) * 60) + seconds; |
602 | 5.45k | if (first == '-') *offset = -*offset; |
603 | 5.45k | } else { |
604 | 42 | dp = nullptr; |
605 | 42 | } |
606 | 5.50k | } else if (first == 'Z' || first == 'z') { // Zulu |
607 | 1.36k | *offset = 0; |
608 | 1.36k | } else { |
609 | 69 | dp = nullptr; |
610 | 69 | } |
611 | 6.93k | } |
612 | 6.93k | return dp; |
613 | 6.93k | } |
614 | | |
615 | 626 | const char* ParseZone(const char* dp, std::string* zone) { |
616 | 626 | zone->clear(); |
617 | 626 | if (dp != nullptr) { |
618 | 1.05M | while (*dp != '\0' && !std::isspace(*dp)) zone->push_back(*dp++); |
619 | 626 | if (zone->empty()) dp = nullptr; |
620 | 626 | } |
621 | 626 | return dp; |
622 | 626 | } |
623 | | |
624 | 1.80k | const char* ParseSubSeconds(const char* dp, detail::femtoseconds* subseconds) { |
625 | 1.80k | if (dp != nullptr) { |
626 | 1.80k | std::int_fast64_t v = 0; |
627 | 1.80k | std::int_fast64_t exp = 0; |
628 | 1.80k | const char* const bp = dp; |
629 | 5.13k | while (const char* cp = strchr(kDigits, *dp)) { |
630 | 3.37k | int d = static_cast<int>(cp - kDigits); |
631 | 3.37k | if (d >= 10) break; |
632 | 3.33k | if (exp < 15) { |
633 | 3.00k | exp += 1; |
634 | 3.00k | v *= 10; |
635 | 3.00k | v += d; |
636 | 3.00k | } |
637 | 3.33k | ++dp; |
638 | 3.33k | } |
639 | 1.80k | if (dp != bp) { |
640 | 1.79k | v *= kExp10[15 - exp]; |
641 | 1.79k | *subseconds = detail::femtoseconds(v); |
642 | 1.79k | } else { |
643 | 4 | dp = nullptr; |
644 | 4 | } |
645 | 1.80k | } |
646 | 1.80k | return dp; |
647 | 1.80k | } |
648 | | |
649 | | // Parses a string into a std::tm using strptime(3). |
650 | 1.27k | const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) { |
651 | 1.27k | if (dp != nullptr) { |
652 | 1.27k | dp = strptime(dp, fmt, tm); |
653 | 1.27k | } |
654 | 1.27k | return dp; |
655 | 1.27k | } |
656 | | |
657 | | // Sets year, tm_mon and tm_mday given the year, week_num, and tm_wday, |
658 | | // and the day on which weeks are defined to start. Returns false if year |
659 | | // would need to move outside its bounds. |
660 | 282 | bool FromWeek(int week_num, weekday week_start, year_t* year, std::tm* tm) { |
661 | 282 | const civil_year y(*year % 400); |
662 | 282 | civil_day cd = prev_weekday(y, week_start); // week 0 |
663 | 282 | cd = next_weekday(cd - 1, FromTmWday(tm->tm_wday)) + (week_num * 7); |
664 | 282 | if (const year_t shift = cd.year() - y.year()) { |
665 | 170 | if (shift > 0) { |
666 | 36 | if (*year > std::numeric_limits<year_t>::max() - shift) return false; |
667 | 134 | } else { |
668 | 134 | if (*year < std::numeric_limits<year_t>::min() - shift) return false; |
669 | 134 | } |
670 | 168 | *year += shift; |
671 | 168 | } |
672 | 280 | tm->tm_mon = cd.month() - 1; |
673 | 280 | tm->tm_mday = cd.day(); |
674 | 280 | return true; |
675 | 282 | } |
676 | | |
677 | | } // namespace |
678 | | |
679 | | // Uses strptime(3) to parse the given input. Supports the same extended |
680 | | // format specifiers as format(), although %E#S and %E*S are treated |
681 | | // identically (and similarly for %E#f and %E*f). %Ez and %E*z also accept |
682 | | // the same inputs. %ET accepts either 'T' or 't'. |
683 | | // |
684 | | // The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are |
685 | | // handled internally so that we can normally avoid strptime() altogether |
686 | | // (which is particularly helpful when the native implementation is broken). |
687 | | // |
688 | | // The TZ/GNU %s extension is handled internally because strptime() has to |
689 | | // use localtime_r() to generate it, and that assumes the local time zone. |
690 | | // |
691 | | // We also handle the %z specifier to accommodate platforms that do not |
692 | | // support the tm_gmtoff extension to std::tm. %Z is parsed but ignored. |
693 | | bool parse(const std::string& format, const std::string& input, |
694 | | const time_zone& tz, time_point<seconds>* sec, |
695 | 8.48k | detail::femtoseconds* fs, std::string* err) { |
696 | | // The unparsed input. Even though we allow NULs in input, and |
697 | | // match them against corresponding NULs in format, we depend on |
698 | | // *edata being a NUL so that we can call strptime(). This also |
699 | | // makes our handling of input easier. |
700 | 8.48k | const char* data = input.c_str(); // NUL terminated |
701 | 8.48k | const char* const edata = data + input.size(); |
702 | | |
703 | | // Skips leading whitespace. |
704 | 8.86k | while (std::isspace(*data)) ++data; |
705 | | |
706 | 8.48k | const year_t kyearmax = std::numeric_limits<year_t>::max(); |
707 | 8.48k | const year_t kyearmin = std::numeric_limits<year_t>::min(); |
708 | | |
709 | | // Sets default values for unspecified fields. |
710 | 8.48k | bool saw_year = false; |
711 | 8.48k | year_t year = 1970; |
712 | 8.48k | std::tm tm{}; |
713 | 8.48k | tm.tm_year = 1970 - 1900; |
714 | 8.48k | tm.tm_mon = 1 - 1; // Jan |
715 | 8.48k | tm.tm_mday = 1; |
716 | 8.48k | tm.tm_hour = 0; |
717 | 8.48k | tm.tm_min = 0; |
718 | 8.48k | tm.tm_sec = 0; |
719 | 8.48k | tm.tm_wday = 4; // Thu |
720 | 8.48k | tm.tm_yday = 0; |
721 | 8.48k | tm.tm_isdst = 0; |
722 | 8.48k | auto subseconds = detail::femtoseconds::zero(); |
723 | 8.48k | bool saw_offset = false; |
724 | 8.48k | int offset = 0; // No offset from passed tz. |
725 | 8.48k | std::string zone = "UTC"; |
726 | | |
727 | | // Even though we allow NULs in format, and match them against |
728 | | // corresponding NULs in input, we simplify its handling by also |
729 | | // ensuring that *efmt is a NUL. |
730 | 8.48k | const char* fmt = format.c_str(); // NUL terminated |
731 | 8.48k | const char* const efmt = fmt + format.size(); |
732 | 8.48k | bool twelve_hour = false; |
733 | 8.48k | bool afternoon = false; |
734 | 8.48k | int week_num = -1; |
735 | 8.48k | weekday week_start = weekday::sunday; |
736 | | |
737 | 8.48k | bool saw_percent_s = false; |
738 | 8.48k | std::int_fast64_t percent_s = 0; |
739 | | |
740 | | // Steps through format, one specifier at a time. |
741 | 33.0k | while (data != nullptr && fmt != efmt) { |
742 | 24.5k | if (std::isspace(*fmt)) { |
743 | 2.21k | while (std::isspace(*data)) ++data; |
744 | 1.27k | while (std::isspace(*++fmt)) continue; |
745 | 1.07k | continue; |
746 | 1.07k | } |
747 | | |
748 | 23.4k | if (*fmt != '%') { |
749 | 1.59k | if (data != edata && *data == *fmt) { |
750 | 1.33k | ++data; |
751 | 1.33k | ++fmt; |
752 | 1.33k | } else { |
753 | 262 | data = nullptr; |
754 | 262 | } |
755 | 1.59k | continue; |
756 | 1.59k | } |
757 | | |
758 | 21.8k | const char* const percent = fmt; |
759 | 21.8k | if (++fmt == efmt) { |
760 | 20 | data = nullptr; |
761 | 20 | continue; |
762 | 20 | } |
763 | 21.8k | switch (*fmt++) { |
764 | 2 | case '\0': |
765 | | // Because we allow NULs in the format string, we must give |
766 | | // some meaning to the "%\0" specifier. We choose the common |
767 | | // (but undefined) strptime() behavior of failing on unknown |
768 | | // specifiers. |
769 | 2 | data = nullptr; |
770 | 2 | continue; |
771 | 1.93k | case 'Y': |
772 | | // Symmetrically with format(), directly handing %Y avoids the |
773 | | // tm.tm_year overflow problem. However, tm.tm_year will still be |
774 | | // used by other specifiers like %D. |
775 | 1.93k | data = ParseInt(data, 0, kyearmin, kyearmax, &year); |
776 | 1.93k | if (data != nullptr) saw_year = true; |
777 | 1.93k | continue; |
778 | 357 | case 'm': |
779 | 357 | data = ParseInt(data, 2, 1, 12, &tm.tm_mon); |
780 | 357 | if (data != nullptr) tm.tm_mon -= 1; |
781 | 357 | week_num = -1; |
782 | 357 | continue; |
783 | 118 | case 'd': |
784 | 410 | case 'e': |
785 | 410 | data = ParseInt(data, 2, 1, 31, &tm.tm_mday); |
786 | 410 | week_num = -1; |
787 | 410 | continue; |
788 | 641 | case 'F': |
789 | 641 | data = ParseInt(data, 0, kyearmin, kyearmax, &year); |
790 | 641 | if (data != nullptr) { |
791 | 595 | saw_year = true; |
792 | 595 | data = (*data == '-' ? data + 1 : nullptr); |
793 | 595 | } |
794 | 641 | data = ParseInt(data, 2, 1, 12, &tm.tm_mon); |
795 | 641 | if (data != nullptr) { |
796 | 321 | tm.tm_mon -= 1; |
797 | 321 | data = (*data == '-' ? data + 1 : nullptr); |
798 | 321 | } |
799 | 641 | data = ParseInt(data, 2, 1, 31, &tm.tm_mday); |
800 | 641 | week_num = -1; |
801 | 641 | continue; |
802 | 441 | case 'U': |
803 | 441 | data = ParseInt(data, 0, 0, 53, &week_num); |
804 | 441 | week_start = weekday::sunday; |
805 | 441 | continue; |
806 | 372 | case 'W': |
807 | 372 | data = ParseInt(data, 0, 0, 53, &week_num); |
808 | 372 | week_start = weekday::monday; |
809 | 372 | continue; |
810 | 260 | case 'u': |
811 | 260 | data = ParseInt(data, 0, 1, 7, &tm.tm_wday); |
812 | 260 | if (data != nullptr) tm.tm_wday %= 7; |
813 | 260 | continue; |
814 | 303 | case 'w': |
815 | 303 | data = ParseInt(data, 0, 0, 6, &tm.tm_wday); |
816 | 303 | continue; |
817 | 285 | case 'H': |
818 | 285 | data = ParseInt(data, 2, 0, 23, &tm.tm_hour); |
819 | 285 | twelve_hour = false; |
820 | 285 | continue; |
821 | 539 | case 'M': |
822 | 539 | data = ParseInt(data, 2, 0, 59, &tm.tm_min); |
823 | 539 | continue; |
824 | 197 | case 'S': |
825 | 197 | data = ParseInt(data, 2, 0, 60, &tm.tm_sec); |
826 | 197 | continue; |
827 | 458 | case 'T': |
828 | 458 | data = ParseInt(data, 2, 0, 23, &tm.tm_hour); |
829 | 458 | twelve_hour = false; |
830 | 458 | data = (data != nullptr && *data == ':' ? data + 1 : nullptr); |
831 | 458 | data = ParseInt(data, 2, 0, 59, &tm.tm_min); |
832 | 458 | data = (data != nullptr && *data == ':' ? data + 1 : nullptr); |
833 | 458 | data = ParseInt(data, 2, 0, 60, &tm.tm_sec); |
834 | 458 | continue; |
835 | 47 | case 'I': |
836 | 119 | case 'l': |
837 | 131 | case 'r': // probably uses %I |
838 | 131 | twelve_hour = true; |
839 | 131 | break; |
840 | 29 | case 'R': // uses %H |
841 | 30 | case 'c': // probably uses %H |
842 | 45 | case 'X': // probably uses %H |
843 | 45 | twelve_hour = false; |
844 | 45 | break; |
845 | 2.36k | case 'z': |
846 | 2.36k | data = ParseOffset(data, "", &offset); |
847 | 2.36k | if (data != nullptr) saw_offset = true; |
848 | 2.36k | continue; |
849 | 626 | case 'Z': // ignored; zone abbreviations are ambiguous |
850 | 626 | data = ParseZone(data, &zone); |
851 | 626 | continue; |
852 | 604 | case 's': |
853 | 604 | data = ParseInt(data, 0, |
854 | 604 | std::numeric_limits<std::int_fast64_t>::min(), |
855 | 604 | std::numeric_limits<std::int_fast64_t>::max(), |
856 | 604 | &percent_s); |
857 | 604 | if (data != nullptr) saw_percent_s = true; |
858 | 604 | continue; |
859 | 1.21k | case ':': |
860 | 1.21k | if (fmt[0] == 'z' || |
861 | 424 | (fmt[0] == ':' && |
862 | 1.18k | (fmt[1] == 'z' || (fmt[1] == ':' && fmt[2] == 'z')))) { |
863 | 1.18k | data = ParseOffset(data, ":", &offset); |
864 | 1.18k | if (data != nullptr) saw_offset = true; |
865 | 1.18k | fmt += (fmt[0] == 'z') ? 1 : (fmt[1] == 'z') ? 2 : 3; |
866 | 1.18k | continue; |
867 | 1.18k | } |
868 | 34 | break; |
869 | 200 | case '%': |
870 | 200 | data = (*data == '%' ? data + 1 : nullptr); |
871 | 200 | continue; |
872 | 9.78k | case 'E': |
873 | 9.78k | if (fmt[0] == 'T') { |
874 | 398 | if (*data == 'T' || *data == 't') { |
875 | 388 | ++data; |
876 | 388 | ++fmt; |
877 | 388 | } else { |
878 | 10 | data = nullptr; |
879 | 10 | } |
880 | 398 | continue; |
881 | 398 | } |
882 | 9.38k | if (fmt[0] == 'z' || (fmt[0] == '*' && fmt[1] == 'z')) { |
883 | 3.38k | data = ParseOffset(data, ":", &offset); |
884 | 3.38k | if (data != nullptr) saw_offset = true; |
885 | 3.38k | fmt += (fmt[0] == 'z') ? 1 : 2; |
886 | 3.38k | continue; |
887 | 3.38k | } |
888 | 6.00k | if (fmt[0] == '*' && fmt[1] == 'S') { |
889 | 371 | data = ParseInt(data, 2, 0, 60, &tm.tm_sec); |
890 | 371 | if (data != nullptr && *data == '.') { |
891 | 3 | data = ParseSubSeconds(data + 1, &subseconds); |
892 | 3 | } |
893 | 371 | fmt += 2; |
894 | 371 | continue; |
895 | 371 | } |
896 | 5.63k | if (fmt[0] == '*' && fmt[1] == 'f') { |
897 | 3.84k | if (data != nullptr && std::isdigit(*data)) { |
898 | 1.42k | data = ParseSubSeconds(data, &subseconds); |
899 | 1.42k | } |
900 | 3.84k | fmt += 2; |
901 | 3.84k | continue; |
902 | 3.84k | } |
903 | 1.78k | if (fmt[0] == '4' && fmt[1] == 'Y') { |
904 | 248 | const char* bp = data; |
905 | 248 | data = ParseInt(data, 4, year_t{-999}, year_t{9999}, &year); |
906 | 248 | if (data != nullptr) { |
907 | 232 | if (data - bp == 4) { |
908 | 184 | saw_year = true; |
909 | 184 | } else { |
910 | 48 | data = nullptr; // stopped too soon |
911 | 48 | } |
912 | 232 | } |
913 | 248 | fmt += 2; |
914 | 248 | continue; |
915 | 248 | } |
916 | 1.54k | if (std::isdigit(*fmt)) { |
917 | 1.43k | int n = 0; // value ignored |
918 | 1.43k | if (const char* np = ParseInt(fmt, 0, 0, 1024, &n)) { |
919 | 1.30k | if (*np == 'S') { |
920 | 200 | data = ParseInt(data, 2, 0, 60, &tm.tm_sec); |
921 | 200 | if (data != nullptr && *data == '.') { |
922 | 3 | data = ParseSubSeconds(data + 1, &subseconds); |
923 | 3 | } |
924 | 200 | fmt = ++np; |
925 | 200 | continue; |
926 | 200 | } |
927 | 1.10k | if (*np == 'f') { |
928 | 1.05k | if (data != nullptr && std::isdigit(*data)) { |
929 | 369 | data = ParseSubSeconds(data, &subseconds); |
930 | 369 | } |
931 | 1.05k | fmt = ++np; |
932 | 1.05k | continue; |
933 | 1.05k | } |
934 | 1.10k | } |
935 | 1.43k | } |
936 | 287 | if (*fmt == 'c') twelve_hour = false; // probably uses %H |
937 | 287 | if (*fmt == 'X') twelve_hour = false; // probably uses %H |
938 | 287 | if (*fmt != '\0') ++fmt; |
939 | 287 | break; |
940 | 91 | case 'O': |
941 | 91 | if (*fmt == 'H') twelve_hour = false; |
942 | 91 | if (*fmt == 'I') twelve_hour = true; |
943 | 91 | if (*fmt != '\0') ++fmt; |
944 | 91 | break; |
945 | 21.8k | } |
946 | | |
947 | | // Parses the current specifier. |
948 | 1.18k | const char* const orig_data = data; |
949 | 1.18k | std::string spec(percent, fmt); |
950 | 1.18k | data = ParseTM(data, spec.c_str(), &tm); |
951 | | |
952 | | // If we successfully parsed %p we need to remember whether the result |
953 | | // was AM or PM so that we can adjust tm_hour before time_zone::lookup(). |
954 | | // So reparse the input with a known AM hour, and check if it is shifted |
955 | | // to a PM hour. |
956 | 1.18k | if (spec == "%p" && data != nullptr) { |
957 | 95 | std::string test_input = "1"; |
958 | 95 | test_input.append(orig_data, data); |
959 | 95 | std::tm tmp{}; |
960 | 95 | ParseTM(test_input.c_str(), "%I%p", &tmp); |
961 | 95 | afternoon = (tmp.tm_hour == 13); |
962 | 95 | } |
963 | 1.18k | } |
964 | | |
965 | | // Adjust a 12-hour tm_hour value if it should be in the afternoon. |
966 | 8.48k | if (twelve_hour && afternoon && tm.tm_hour < 12) { |
967 | 3 | tm.tm_hour += 12; |
968 | 3 | } |
969 | | |
970 | 8.48k | if (data == nullptr) { |
971 | 2.55k | if (err != nullptr) *err = "Failed to parse input"; |
972 | 2.55k | return false; |
973 | 2.55k | } |
974 | | |
975 | | // Skip any remaining whitespace. |
976 | 6.12k | while (std::isspace(*data)) ++data; |
977 | | |
978 | | // parse() must consume the entire input string. |
979 | 5.93k | if (data != edata) { |
980 | 281 | if (err != nullptr) *err = "Illegal trailing data in input string"; |
981 | 281 | return false; |
982 | 281 | } |
983 | | |
984 | | // If we saw %s then we ignore anything else and return that time. |
985 | 5.64k | if (saw_percent_s) { |
986 | 254 | *sec = FromUnixSeconds(percent_s); |
987 | 254 | *fs = detail::femtoseconds::zero(); |
988 | 254 | return true; |
989 | 254 | } |
990 | | |
991 | | // If we saw %z, %Ez, or %E*z then we want to interpret the parsed fields |
992 | | // in UTC and then shift by that offset. Otherwise we want to interpret |
993 | | // the fields directly in the passed time_zone. |
994 | 5.39k | time_zone ptz = saw_offset ? utc_time_zone() : tz; |
995 | | |
996 | | // Allows a leap second of 60 to normalize forward to the following ":00". |
997 | 5.39k | if (tm.tm_sec == 60) { |
998 | 31 | tm.tm_sec -= 1; |
999 | 31 | offset -= 1; |
1000 | 31 | subseconds = detail::femtoseconds::zero(); |
1001 | 31 | } |
1002 | | |
1003 | 5.39k | if (!saw_year) { |
1004 | 3.68k | year = year_t{tm.tm_year}; |
1005 | 3.68k | if (year > kyearmax - 1900) { |
1006 | | // Platform-dependent, maybe unreachable. |
1007 | 0 | if (err != nullptr) *err = "Out-of-range year"; |
1008 | 0 | return false; |
1009 | 0 | } |
1010 | 3.68k | year += 1900; |
1011 | 3.68k | } |
1012 | | |
1013 | | // Compute year, tm.tm_mon and tm.tm_mday if we parsed a week number. |
1014 | 5.39k | if (week_num != -1) { |
1015 | 282 | if (!FromWeek(week_num, week_start, &year, &tm)) { |
1016 | 2 | if (err != nullptr) *err = "Out-of-range field"; |
1017 | 2 | return false; |
1018 | 2 | } |
1019 | 282 | } |
1020 | | |
1021 | 5.39k | const int month = tm.tm_mon + 1; |
1022 | 5.39k | civil_second cs(year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); |
1023 | | |
1024 | | // parse() should not allow normalization. Due to the restricted field |
1025 | | // ranges above (see ParseInt()), the only possibility is for days to roll |
1026 | | // into months. That is, parsing "Sep 31" should not produce "Oct 1". |
1027 | 5.39k | if (cs.month() != month || cs.day() != tm.tm_mday) { |
1028 | 3 | if (err != nullptr) *err = "Out-of-range field"; |
1029 | 3 | return false; |
1030 | 3 | } |
1031 | | |
1032 | | // Accounts for the offset adjustment before converting to absolute time. |
1033 | 5.39k | if ((offset < 0 && cs > civil_second::max() + offset) || |
1034 | 5.39k | (offset > 0 && cs < civil_second::min() + offset)) { |
1035 | 1 | if (err != nullptr) *err = "Out-of-range field"; |
1036 | 1 | return false; |
1037 | 1 | } |
1038 | 5.38k | cs -= offset; |
1039 | | |
1040 | 5.38k | const auto tp = ptz.lookup(cs).pre; |
1041 | | // Checks for overflow/underflow and returns an error as necessary. |
1042 | 5.38k | if (tp == time_point<seconds>::max()) { |
1043 | 381 | const auto al = ptz.lookup(time_point<seconds>::max()); |
1044 | 381 | if (cs > al.cs) { |
1045 | 224 | if (err != nullptr) *err = "Out-of-range field"; |
1046 | 224 | return false; |
1047 | 224 | } |
1048 | 381 | } |
1049 | 5.16k | if (tp == time_point<seconds>::min()) { |
1050 | 363 | const auto al = ptz.lookup(time_point<seconds>::min()); |
1051 | 363 | if (cs < al.cs) { |
1052 | 167 | if (err != nullptr) *err = "Out-of-range field"; |
1053 | 167 | return false; |
1054 | 167 | } |
1055 | 363 | } |
1056 | | |
1057 | 4.99k | *sec = tp; |
1058 | 4.99k | *fs = subseconds; |
1059 | 4.99k | return true; |
1060 | 5.16k | } |
1061 | | |
1062 | | } // namespace detail |
1063 | | } // namespace cctz |