Coverage Report

Created: 2023-09-25 06:17

/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