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