Bug Summary

File:root/firefox-clang/obj-x86_64-pc-linux-gnu/third_party/abseil-cpp/absl/time/internal/cctz/time_zone_gn/./../../../../../../../../third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format.cc
Warning:line 205, column 21
Although the value stored to 'offset' is used in the enclosing expression, the value is never actually read from 'offset'

Annotated Source Code

Press '?' to see keyboard shortcuts

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