Coverage Report

Created: 2026-07-16 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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