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