968a34ffda
-- 7fa1107161a03dac53fb84c2b06d8092616c7b13 by Abseil Team <absl-team@google.com>: Harden the generic stacktrace implementation for use during early program execution PiperOrigin-RevId: 226375950 -- 079f9969329f5eb66f647dd3c44b17541b1bf217 by Matt Kulukundis <kfm@google.com>: Workaround platforms that have over-aggressive warnings on -Wexit-time-destructors PiperOrigin-RevId: 226362948 -- 1447943f509be681ca5495add0162c750ef237f1 by Matt Kulukundis <kfm@google.com>: Switch from 64 to size_t atomics so they work on embedded platforms that do not have 64 bit atomics. PiperOrigin-RevId: 226210704 -- d14d49837ae2bcde74051e0c79c18ee0f43866b9 by Tom Manshreck <shreck@google.com>: Develop initial documentation for API breaking changes process: PiperOrigin-RevId: 226210021 -- 7ea3d7fe0e86979dab83a5fc9cc3bf1d6cb3bd53 by Abseil Team <absl-team@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 226195522 -- 7de873e880d7f016a4fa1e08d626f0535cc470af by Abseil Team <absl-team@google.com>: Make Abseil LICENSE files newline terminated, with a single trailing blank line. Also remove line-ending whitespace. PiperOrigin-RevId: 226182949 -- 7d00643fadfad7f0d992c68bd9d2ed2e5bc960b0 by Matt Kulukundis <kfm@google.com>: Internal cleanup PiperOrigin-RevId: 226045282 -- c4a0a11c0ce2875271191e477f3d36eaaeca4613 by Matt Kulukundis <kfm@google.com>: Internal cleanup PiperOrigin-RevId: 226038273 -- 8ee4ebbb1ae5cda119e436e5ff7e3aa966608c10 by Matt Kulukundis <kfm@google.com>: Adds a global sampler which tracks a fraction of live tables for collecting telemetry data. PiperOrigin-RevId: 226032080 -- d576446f050518cd1b0ae447d682d8552f0e7e30 by Mark Barolak <mbar@google.com>: Replace an internal CaseEqual function with calls to the identical absl::EqualsIgnoreCase. This closes out a rather old TODO. PiperOrigin-RevId: 226024779 -- 6b23f1ee028a5ffa608c920424f1220a117a8f3d by Abseil Team <absl-team@google.com>: Add December 2018 LTS branch to list of LTS branches. PiperOrigin-RevId: 226011333 -- bb0833a43bdaef4c8c059b17bcd27ba9a085a114 by Mark Barolak <mbar@google.com>: Explicitly state that when the SimpleAtoi family of functions encounter an error, the value of their output parameter is unspecified. Also standardize the name of the output parameter to be `out`. PiperOrigin-RevId: 225997035 -- 46c1876b1a248eabda7545daa61a74a4cdfe9077 by Abseil Team <absl-team@google.com>: Remove deprecated CMake function absl_test, absl_library and absl_header_library PiperOrigin-RevId: 225950041 GitOrigin-RevId: 7fa1107161a03dac53fb84c2b06d8092616c7b13 Change-Id: I2ca9d3aada9292614527d1339a7557494139b806
909 lines
28 KiB
C++
909 lines
28 KiB
C++
// Copyright 2016 Google Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#if !defined(HAS_STRPTIME)
|
|
# if !defined(_MSC_VER)
|
|
# define HAS_STRPTIME 1 // assume everyone has strptime() except windows
|
|
# endif
|
|
#endif
|
|
|
|
#include "absl/time/internal/cctz/include/cctz/time_zone.h"
|
|
|
|
#include <cctype>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <ctime>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
#if !HAS_STRPTIME
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#endif
|
|
|
|
#include "absl/time/internal/cctz/include/cctz/civil_time.h"
|
|
#include "time_zone_if.h"
|
|
|
|
namespace absl {
|
|
namespace time_internal {
|
|
namespace cctz {
|
|
namespace detail {
|
|
|
|
namespace {
|
|
|
|
#if !HAS_STRPTIME
|
|
// Build a strptime() using C++11's std::get_time().
|
|
char* strptime(const char* s, const char* fmt, std::tm* tm) {
|
|
std::istringstream input(s);
|
|
input >> std::get_time(tm, fmt);
|
|
if (input.fail()) return nullptr;
|
|
return const_cast<char*>(s) +
|
|
(input.eof() ? strlen(s) : static_cast<std::size_t>(input.tellg()));
|
|
}
|
|
#endif
|
|
|
|
std::tm ToTM(const time_zone::absolute_lookup& al) {
|
|
std::tm tm{};
|
|
tm.tm_sec = al.cs.second();
|
|
tm.tm_min = al.cs.minute();
|
|
tm.tm_hour = al.cs.hour();
|
|
tm.tm_mday = al.cs.day();
|
|
tm.tm_mon = al.cs.month() - 1;
|
|
|
|
// Saturate tm.tm_year is cases of over/underflow.
|
|
if (al.cs.year() < std::numeric_limits<int>::min() + 1900) {
|
|
tm.tm_year = std::numeric_limits<int>::min();
|
|
} else if (al.cs.year() - 1900 > std::numeric_limits<int>::max()) {
|
|
tm.tm_year = std::numeric_limits<int>::max();
|
|
} else {
|
|
tm.tm_year = static_cast<int>(al.cs.year() - 1900);
|
|
}
|
|
|
|
switch (get_weekday(civil_day(al.cs))) {
|
|
case weekday::sunday:
|
|
tm.tm_wday = 0;
|
|
break;
|
|
case weekday::monday:
|
|
tm.tm_wday = 1;
|
|
break;
|
|
case weekday::tuesday:
|
|
tm.tm_wday = 2;
|
|
break;
|
|
case weekday::wednesday:
|
|
tm.tm_wday = 3;
|
|
break;
|
|
case weekday::thursday:
|
|
tm.tm_wday = 4;
|
|
break;
|
|
case weekday::friday:
|
|
tm.tm_wday = 5;
|
|
break;
|
|
case weekday::saturday:
|
|
tm.tm_wday = 6;
|
|
break;
|
|
}
|
|
tm.tm_yday = get_yearday(civil_day(al.cs)) - 1;
|
|
tm.tm_isdst = al.is_dst ? 1 : 0;
|
|
return tm;
|
|
}
|
|
|
|
const char kDigits[] = "0123456789";
|
|
|
|
// Formats a 64-bit integer in the given field width. Note that it is up
|
|
// to the caller of Format64() [and Format02d()/FormatOffset()] to ensure
|
|
// that there is sufficient space before ep to hold the conversion.
|
|
char* Format64(char* ep, int width, std::int_fast64_t v) {
|
|
bool neg = false;
|
|
if (v < 0) {
|
|
--width;
|
|
neg = true;
|
|
if (v == std::numeric_limits<std::int_fast64_t>::min()) {
|
|
// Avoid negating minimum value.
|
|
std::int_fast64_t last_digit = -(v % 10);
|
|
v /= 10;
|
|
if (last_digit < 0) {
|
|
++v;
|
|
last_digit += 10;
|
|
}
|
|
--width;
|
|
*--ep = kDigits[last_digit];
|
|
}
|
|
v = -v;
|
|
}
|
|
do {
|
|
--width;
|
|
*--ep = kDigits[v % 10];
|
|
} while (v /= 10);
|
|
while (--width >= 0) *--ep = '0'; // zero pad
|
|
if (neg) *--ep = '-';
|
|
return ep;
|
|
}
|
|
|
|
// Formats [0 .. 99] as %02d.
|
|
char* Format02d(char* ep, int v) {
|
|
*--ep = kDigits[v % 10];
|
|
*--ep = kDigits[(v / 10) % 10];
|
|
return ep;
|
|
}
|
|
|
|
// Formats a UTC offset, like +00:00.
|
|
char* FormatOffset(char* ep, int offset, const char* mode) {
|
|
// TODO: Follow the RFC3339 "Unknown Local Offset Convention" and
|
|
// generate a "negative zero" when we're formatting a zero offset
|
|
// as the result of a failed load_time_zone().
|
|
char sign = '+';
|
|
if (offset < 0) {
|
|
offset = -offset; // bounded by 24h so no overflow
|
|
sign = '-';
|
|
}
|
|
const int seconds = offset % 60;
|
|
const int minutes = (offset /= 60) % 60;
|
|
const int hours = offset /= 60;
|
|
const char sep = mode[0];
|
|
const bool ext = (sep != '\0' && mode[1] == '*');
|
|
const bool ccc = (ext && mode[2] == ':');
|
|
if (ext && (!ccc || seconds != 0)) {
|
|
ep = Format02d(ep, seconds);
|
|
*--ep = sep;
|
|
} else {
|
|
// If we're not rendering seconds, sub-minute negative offsets
|
|
// should get a positive sign (e.g., offset=-10s => "+00:00").
|
|
if (hours == 0 && minutes == 0) sign = '+';
|
|
}
|
|
if (!ccc || minutes != 0 || seconds != 0) {
|
|
ep = Format02d(ep, minutes);
|
|
if (sep != '\0') *--ep = sep;
|
|
}
|
|
ep = Format02d(ep, hours);
|
|
*--ep = sign;
|
|
return ep;
|
|
}
|
|
|
|
// Formats a std::tm using strftime(3).
|
|
void FormatTM(std::string* out, const std::string& fmt, const std::tm& tm) {
|
|
// strftime(3) returns the number of characters placed in the output
|
|
// array (which may be 0 characters). It also returns 0 to indicate
|
|
// an error, like the array wasn't large enough. To accommodate this,
|
|
// the following code grows the buffer size from 2x the format std::string
|
|
// length up to 32x.
|
|
for (std::size_t i = 2; i != 32; i *= 2) {
|
|
std::size_t buf_size = fmt.size() * i;
|
|
std::vector<char> buf(buf_size);
|
|
if (std::size_t len = strftime(&buf[0], buf_size, fmt.c_str(), &tm)) {
|
|
out->append(&buf[0], len);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Used for %E#S/%E#f specifiers and for data values in parse().
|
|
template <typename T>
|
|
const char* ParseInt(const char* dp, int width, T min, T max, T* vp) {
|
|
if (dp != nullptr) {
|
|
const T kmin = std::numeric_limits<T>::min();
|
|
bool erange = false;
|
|
bool neg = false;
|
|
T value = 0;
|
|
if (*dp == '-') {
|
|
neg = true;
|
|
if (width <= 0 || --width != 0) {
|
|
++dp;
|
|
} else {
|
|
dp = nullptr; // width was 1
|
|
}
|
|
}
|
|
if (const char* const bp = dp) {
|
|
while (const char* cp = strchr(kDigits, *dp)) {
|
|
int d = static_cast<int>(cp - kDigits);
|
|
if (d >= 10) break;
|
|
if (value < kmin / 10) {
|
|
erange = true;
|
|
break;
|
|
}
|
|
value *= 10;
|
|
if (value < kmin + d) {
|
|
erange = true;
|
|
break;
|
|
}
|
|
value -= d;
|
|
dp += 1;
|
|
if (width > 0 && --width == 0) break;
|
|
}
|
|
if (dp != bp && !erange && (neg || value != kmin)) {
|
|
if (!neg || value != 0) {
|
|
if (!neg) value = -value; // make positive
|
|
if (min <= value && value <= max) {
|
|
*vp = value;
|
|
} else {
|
|
dp = nullptr;
|
|
}
|
|
} else {
|
|
dp = nullptr;
|
|
}
|
|
} else {
|
|
dp = nullptr;
|
|
}
|
|
}
|
|
}
|
|
return dp;
|
|
}
|
|
|
|
// The number of base-10 digits that can be represented by a signed 64-bit
|
|
// integer. That is, 10^kDigits10_64 <= 2^63 - 1 < 10^(kDigits10_64 + 1).
|
|
const int kDigits10_64 = 18;
|
|
|
|
// 10^n for everything that can be represented by a signed 64-bit integer.
|
|
const std::int_fast64_t kExp10[kDigits10_64 + 1] = {
|
|
1,
|
|
10,
|
|
100,
|
|
1000,
|
|
10000,
|
|
100000,
|
|
1000000,
|
|
10000000,
|
|
100000000,
|
|
1000000000,
|
|
10000000000,
|
|
100000000000,
|
|
1000000000000,
|
|
10000000000000,
|
|
100000000000000,
|
|
1000000000000000,
|
|
10000000000000000,
|
|
100000000000000000,
|
|
1000000000000000000,
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// Uses strftime(3) to format the given Time. The following extended format
|
|
// specifiers are also supported:
|
|
//
|
|
// - %Ez - RFC3339-compatible numeric UTC offset (+hh:mm or -hh:mm)
|
|
// - %E*z - Full-resolution numeric UTC offset (+hh:mm:ss or -hh:mm:ss)
|
|
// - %E#S - Seconds with # digits of fractional precision
|
|
// - %E*S - Seconds with full fractional precision (a literal '*')
|
|
// - %E4Y - Four-character years (-999 ... -001, 0000, 0001 ... 9999)
|
|
//
|
|
// The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are
|
|
// handled internally for performance reasons. strftime(3) is slow due to
|
|
// a POSIX requirement to respect changes to ${TZ}.
|
|
//
|
|
// The TZ/GNU %s extension is handled internally because strftime() has
|
|
// to use mktime() to generate it, and that assumes the local time zone.
|
|
//
|
|
// We also handle the %z and %Z specifiers to accommodate platforms that do
|
|
// not support the tm_gmtoff and tm_zone extensions to std::tm.
|
|
//
|
|
// Requires that zero() <= fs < seconds(1).
|
|
std::string format(const std::string& format, const time_point<seconds>& tp,
|
|
const detail::femtoseconds& fs, const time_zone& tz) {
|
|
std::string result;
|
|
result.reserve(format.size()); // A reasonable guess for the result size.
|
|
const time_zone::absolute_lookup al = tz.lookup(tp);
|
|
const std::tm tm = ToTM(al);
|
|
|
|
// Scratch buffer for internal conversions.
|
|
char buf[3 + kDigits10_64]; // enough for longest conversion
|
|
char* const ep = buf + sizeof(buf);
|
|
char* bp; // works back from ep
|
|
|
|
// Maintain three, disjoint subsequences that span format.
|
|
// [format.begin() ... pending) : already formatted into result
|
|
// [pending ... cur) : formatting pending, but no special cases
|
|
// [cur ... format.end()) : unexamined
|
|
// Initially, everything is in the unexamined part.
|
|
const char* pending = format.c_str(); // NUL terminated
|
|
const char* cur = pending;
|
|
const char* end = pending + format.length();
|
|
|
|
while (cur != end) { // while something is unexamined
|
|
// Moves cur to the next percent sign.
|
|
const char* start = cur;
|
|
while (cur != end && *cur != '%') ++cur;
|
|
|
|
// If the new pending text is all ordinary, copy it out.
|
|
if (cur != start && pending == start) {
|
|
result.append(pending, static_cast<std::size_t>(cur - pending));
|
|
pending = start = cur;
|
|
}
|
|
|
|
// Span the sequential percent signs.
|
|
const char* percent = cur;
|
|
while (cur != end && *cur == '%') ++cur;
|
|
|
|
// If the new pending text is all percents, copy out one
|
|
// percent for every matched pair, then skip those pairs.
|
|
if (cur != start && pending == start) {
|
|
std::size_t escaped = static_cast<std::size_t>(cur - pending) / 2;
|
|
result.append(pending, escaped);
|
|
pending += escaped * 2;
|
|
// Also copy out a single trailing percent.
|
|
if (pending != cur && cur == end) {
|
|
result.push_back(*pending++);
|
|
}
|
|
}
|
|
|
|
// Loop unless we have an unescaped percent.
|
|
if (cur == end || (cur - percent) % 2 == 0) continue;
|
|
|
|
// Simple specifiers that we handle ourselves.
|
|
if (strchr("YmdeHMSzZs%", *cur)) {
|
|
if (cur - 1 != pending) {
|
|
FormatTM(&result, std::string(pending, cur - 1), tm);
|
|
}
|
|
switch (*cur) {
|
|
case 'Y':
|
|
// This avoids the tm.tm_year overflow problem for %Y, however
|
|
// tm.tm_year will still be used by other specifiers like %D.
|
|
bp = Format64(ep, 0, al.cs.year());
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
break;
|
|
case 'm':
|
|
bp = Format02d(ep, al.cs.month());
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
break;
|
|
case 'd':
|
|
case 'e':
|
|
bp = Format02d(ep, al.cs.day());
|
|
if (*cur == 'e' && *bp == '0') *bp = ' '; // for Windows
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
break;
|
|
case 'H':
|
|
bp = Format02d(ep, al.cs.hour());
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
break;
|
|
case 'M':
|
|
bp = Format02d(ep, al.cs.minute());
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
break;
|
|
case 'S':
|
|
bp = Format02d(ep, al.cs.second());
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
break;
|
|
case 'z':
|
|
bp = FormatOffset(ep, al.offset, "");
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
break;
|
|
case 'Z':
|
|
result.append(al.abbr);
|
|
break;
|
|
case 's':
|
|
bp = Format64(ep, 0, ToUnixSeconds(tp));
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
break;
|
|
case '%':
|
|
result.push_back('%');
|
|
break;
|
|
}
|
|
pending = ++cur;
|
|
continue;
|
|
}
|
|
|
|
// More complex specifiers that we handle ourselves.
|
|
if (*cur == ':' && cur + 1 != end) {
|
|
if (*(cur + 1) == 'z') {
|
|
// Formats %:z.
|
|
if (cur - 1 != pending) {
|
|
FormatTM(&result, std::string(pending, cur - 1), tm);
|
|
}
|
|
bp = FormatOffset(ep, al.offset, ":");
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
pending = cur += 2;
|
|
continue;
|
|
}
|
|
if (*(cur + 1) == ':' && cur + 2 != end) {
|
|
if (*(cur + 2) == 'z') {
|
|
// Formats %::z.
|
|
if (cur - 1 != pending) {
|
|
FormatTM(&result, std::string(pending, cur - 1), tm);
|
|
}
|
|
bp = FormatOffset(ep, al.offset, ":*");
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
pending = cur += 3;
|
|
continue;
|
|
}
|
|
if (*(cur + 2) == ':' && cur + 3 != end) {
|
|
if (*(cur + 3) == 'z') {
|
|
// Formats %:::z.
|
|
if (cur - 1 != pending) {
|
|
FormatTM(&result, std::string(pending, cur - 1), tm);
|
|
}
|
|
bp = FormatOffset(ep, al.offset, ":*:");
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
pending = cur += 4;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Loop if there is no E modifier.
|
|
if (*cur != 'E' || ++cur == end) continue;
|
|
|
|
// Format our extensions.
|
|
if (*cur == 'z') {
|
|
// Formats %Ez.
|
|
if (cur - 2 != pending) {
|
|
FormatTM(&result, std::string(pending, cur - 2), tm);
|
|
}
|
|
bp = FormatOffset(ep, al.offset, ":");
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
pending = ++cur;
|
|
} else if (*cur == '*' && cur + 1 != end && *(cur + 1) == 'z') {
|
|
// Formats %E*z.
|
|
if (cur - 2 != pending) {
|
|
FormatTM(&result, std::string(pending, cur - 2), tm);
|
|
}
|
|
bp = FormatOffset(ep, al.offset, ":*");
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
pending = cur += 2;
|
|
} else if (*cur == '*' && cur + 1 != end &&
|
|
(*(cur + 1) == 'S' || *(cur + 1) == 'f')) {
|
|
// Formats %E*S or %E*F.
|
|
if (cur - 2 != pending) {
|
|
FormatTM(&result, std::string(pending, cur - 2), tm);
|
|
}
|
|
char* cp = ep;
|
|
bp = Format64(cp, 15, fs.count());
|
|
while (cp != bp && cp[-1] == '0') --cp;
|
|
switch (*(cur + 1)) {
|
|
case 'S':
|
|
if (cp != bp) *--bp = '.';
|
|
bp = Format02d(bp, al.cs.second());
|
|
break;
|
|
case 'f':
|
|
if (cp == bp) *--bp = '0';
|
|
break;
|
|
}
|
|
result.append(bp, static_cast<std::size_t>(cp - bp));
|
|
pending = cur += 2;
|
|
} else if (*cur == '4' && cur + 1 != end && *(cur + 1) == 'Y') {
|
|
// Formats %E4Y.
|
|
if (cur - 2 != pending) {
|
|
FormatTM(&result, std::string(pending, cur - 2), tm);
|
|
}
|
|
bp = Format64(ep, 4, al.cs.year());
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
pending = cur += 2;
|
|
} else if (std::isdigit(*cur)) {
|
|
// Possibly found %E#S or %E#f.
|
|
int n = 0;
|
|
if (const char* np = ParseInt(cur, 0, 0, 1024, &n)) {
|
|
if (*np == 'S' || *np == 'f') {
|
|
// Formats %E#S or %E#f.
|
|
if (cur - 2 != pending) {
|
|
FormatTM(&result, std::string(pending, cur - 2), tm);
|
|
}
|
|
bp = ep;
|
|
if (n > 0) {
|
|
if (n > kDigits10_64) n = kDigits10_64;
|
|
bp = Format64(bp, n, (n > 15) ? fs.count() * kExp10[n - 15]
|
|
: fs.count() / kExp10[15 - n]);
|
|
if (*np == 'S') *--bp = '.';
|
|
}
|
|
if (*np == 'S') bp = Format02d(bp, al.cs.second());
|
|
result.append(bp, static_cast<std::size_t>(ep - bp));
|
|
pending = cur = ++np;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Formats any remaining data.
|
|
if (end != pending) {
|
|
FormatTM(&result, std::string(pending, end), tm);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
namespace {
|
|
|
|
const char* ParseOffset(const char* dp, const char* mode, int* offset) {
|
|
if (dp != nullptr) {
|
|
const char first = *dp++;
|
|
if (first == '+' || first == '-') {
|
|
char sep = mode[0];
|
|
int hours = 0;
|
|
int minutes = 0;
|
|
int seconds = 0;
|
|
const char* ap = ParseInt(dp, 2, 0, 23, &hours);
|
|
if (ap != nullptr && ap - dp == 2) {
|
|
dp = ap;
|
|
if (sep != '\0' && *ap == sep) ++ap;
|
|
const char* bp = ParseInt(ap, 2, 0, 59, &minutes);
|
|
if (bp != nullptr && bp - ap == 2) {
|
|
dp = bp;
|
|
if (sep != '\0' && *bp == sep) ++bp;
|
|
const char* cp = ParseInt(bp, 2, 0, 59, &seconds);
|
|
if (cp != nullptr && cp - bp == 2) dp = cp;
|
|
}
|
|
*offset = ((hours * 60 + minutes) * 60) + seconds;
|
|
if (first == '-') *offset = -*offset;
|
|
} else {
|
|
dp = nullptr;
|
|
}
|
|
} else if (first == 'Z') { // Zulu
|
|
*offset = 0;
|
|
} else {
|
|
dp = nullptr;
|
|
}
|
|
}
|
|
return dp;
|
|
}
|
|
|
|
const char* ParseZone(const char* dp, std::string* zone) {
|
|
zone->clear();
|
|
if (dp != nullptr) {
|
|
while (*dp != '\0' && !std::isspace(*dp)) zone->push_back(*dp++);
|
|
if (zone->empty()) dp = nullptr;
|
|
}
|
|
return dp;
|
|
}
|
|
|
|
const char* ParseSubSeconds(const char* dp, detail::femtoseconds* subseconds) {
|
|
if (dp != nullptr) {
|
|
std::int_fast64_t v = 0;
|
|
std::int_fast64_t exp = 0;
|
|
const char* const bp = dp;
|
|
while (const char* cp = strchr(kDigits, *dp)) {
|
|
int d = static_cast<int>(cp - kDigits);
|
|
if (d >= 10) break;
|
|
if (exp < 15) {
|
|
exp += 1;
|
|
v *= 10;
|
|
v += d;
|
|
}
|
|
++dp;
|
|
}
|
|
if (dp != bp) {
|
|
v *= kExp10[15 - exp];
|
|
*subseconds = detail::femtoseconds(v);
|
|
} else {
|
|
dp = nullptr;
|
|
}
|
|
}
|
|
return dp;
|
|
}
|
|
|
|
// Parses a string into a std::tm using strptime(3).
|
|
const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) {
|
|
if (dp != nullptr) {
|
|
dp = strptime(dp, fmt, tm);
|
|
}
|
|
return dp;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// Uses strptime(3) to parse the given input. Supports the same extended
|
|
// format specifiers as format(), although %E#S and %E*S are treated
|
|
// identically (and similarly for %E#f and %E*f). %Ez and %E*z also accept
|
|
// the same inputs.
|
|
//
|
|
// The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are
|
|
// handled internally so that we can normally avoid strptime() altogether
|
|
// (which is particularly helpful when the native implementation is broken).
|
|
//
|
|
// The TZ/GNU %s extension is handled internally because strptime() has to
|
|
// use localtime_r() to generate it, and that assumes the local time zone.
|
|
//
|
|
// We also handle the %z specifier to accommodate platforms that do not
|
|
// support the tm_gmtoff extension to std::tm. %Z is parsed but ignored.
|
|
bool parse(const std::string& format, const std::string& input,
|
|
const time_zone& tz, time_point<seconds>* sec,
|
|
detail::femtoseconds* fs, std::string* err) {
|
|
// The unparsed input.
|
|
const char* data = input.c_str(); // NUL terminated
|
|
|
|
// Skips leading whitespace.
|
|
while (std::isspace(*data)) ++data;
|
|
|
|
const year_t kyearmax = std::numeric_limits<year_t>::max();
|
|
const year_t kyearmin = std::numeric_limits<year_t>::min();
|
|
|
|
// Sets default values for unspecified fields.
|
|
bool saw_year = false;
|
|
year_t year = 1970;
|
|
std::tm tm{};
|
|
tm.tm_year = 1970 - 1900;
|
|
tm.tm_mon = 1 - 1; // Jan
|
|
tm.tm_mday = 1;
|
|
tm.tm_hour = 0;
|
|
tm.tm_min = 0;
|
|
tm.tm_sec = 0;
|
|
tm.tm_wday = 4; // Thu
|
|
tm.tm_yday = 0;
|
|
tm.tm_isdst = 0;
|
|
auto subseconds = detail::femtoseconds::zero();
|
|
bool saw_offset = false;
|
|
int offset = 0; // No offset from passed tz.
|
|
std::string zone = "UTC";
|
|
|
|
const char* fmt = format.c_str(); // NUL terminated
|
|
bool twelve_hour = false;
|
|
bool afternoon = false;
|
|
|
|
bool saw_percent_s = false;
|
|
std::int_fast64_t percent_s = 0;
|
|
|
|
// Steps through format, one specifier at a time.
|
|
while (data != nullptr && *fmt != '\0') {
|
|
if (std::isspace(*fmt)) {
|
|
while (std::isspace(*data)) ++data;
|
|
while (std::isspace(*++fmt)) continue;
|
|
continue;
|
|
}
|
|
|
|
if (*fmt != '%') {
|
|
if (*data == *fmt) {
|
|
++data;
|
|
++fmt;
|
|
} else {
|
|
data = nullptr;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const char* percent = fmt;
|
|
if (*++fmt == '\0') {
|
|
data = nullptr;
|
|
continue;
|
|
}
|
|
switch (*fmt++) {
|
|
case 'Y':
|
|
// Symmetrically with FormatTime(), directly handing %Y avoids the
|
|
// tm.tm_year overflow problem. However, tm.tm_year will still be
|
|
// used by other specifiers like %D.
|
|
data = ParseInt(data, 0, kyearmin, kyearmax, &year);
|
|
if (data != nullptr) saw_year = true;
|
|
continue;
|
|
case 'm':
|
|
data = ParseInt(data, 2, 1, 12, &tm.tm_mon);
|
|
if (data != nullptr) tm.tm_mon -= 1;
|
|
continue;
|
|
case 'd':
|
|
case 'e':
|
|
data = ParseInt(data, 2, 1, 31, &tm.tm_mday);
|
|
continue;
|
|
case 'H':
|
|
data = ParseInt(data, 2, 0, 23, &tm.tm_hour);
|
|
twelve_hour = false;
|
|
continue;
|
|
case 'M':
|
|
data = ParseInt(data, 2, 0, 59, &tm.tm_min);
|
|
continue;
|
|
case 'S':
|
|
data = ParseInt(data, 2, 0, 60, &tm.tm_sec);
|
|
continue;
|
|
case 'I':
|
|
case 'l':
|
|
case 'r': // probably uses %I
|
|
twelve_hour = true;
|
|
break;
|
|
case 'R': // uses %H
|
|
case 'T': // uses %H
|
|
case 'c': // probably uses %H
|
|
case 'X': // probably uses %H
|
|
twelve_hour = false;
|
|
break;
|
|
case 'z':
|
|
data = ParseOffset(data, "", &offset);
|
|
if (data != nullptr) saw_offset = true;
|
|
continue;
|
|
case 'Z': // ignored; zone abbreviations are ambiguous
|
|
data = ParseZone(data, &zone);
|
|
continue;
|
|
case 's':
|
|
data = ParseInt(data, 0,
|
|
std::numeric_limits<std::int_fast64_t>::min(),
|
|
std::numeric_limits<std::int_fast64_t>::max(),
|
|
&percent_s);
|
|
if (data != nullptr) saw_percent_s = true;
|
|
continue;
|
|
case ':':
|
|
if (fmt[0] == 'z' ||
|
|
(fmt[0] == ':' &&
|
|
(fmt[1] == 'z' || (fmt[1] == ':' && fmt[2] == 'z')))) {
|
|
data = ParseOffset(data, ":", &offset);
|
|
if (data != nullptr) saw_offset = true;
|
|
fmt += (fmt[0] == 'z') ? 1 : (fmt[1] == 'z') ? 2 : 3;
|
|
continue;
|
|
}
|
|
break;
|
|
case '%':
|
|
data = (*data == '%' ? data + 1 : nullptr);
|
|
continue;
|
|
case 'E':
|
|
if (fmt[0] == 'z' || (fmt[0] == '*' && fmt[1] == 'z')) {
|
|
data = ParseOffset(data, ":", &offset);
|
|
if (data != nullptr) saw_offset = true;
|
|
fmt += (fmt[0] == 'z') ? 1 : 2;
|
|
continue;
|
|
}
|
|
if (fmt[0] == '*' && fmt[1] == 'S') {
|
|
data = ParseInt(data, 2, 0, 60, &tm.tm_sec);
|
|
if (data != nullptr && *data == '.') {
|
|
data = ParseSubSeconds(data + 1, &subseconds);
|
|
}
|
|
fmt += 2;
|
|
continue;
|
|
}
|
|
if (fmt[0] == '*' && fmt[1] == 'f') {
|
|
if (data != nullptr && std::isdigit(*data)) {
|
|
data = ParseSubSeconds(data, &subseconds);
|
|
}
|
|
fmt += 2;
|
|
continue;
|
|
}
|
|
if (fmt[0] == '4' && fmt[1] == 'Y') {
|
|
const char* bp = data;
|
|
data = ParseInt(data, 4, year_t{-999}, year_t{9999}, &year);
|
|
if (data != nullptr) {
|
|
if (data - bp == 4) {
|
|
saw_year = true;
|
|
} else {
|
|
data = nullptr; // stopped too soon
|
|
}
|
|
}
|
|
fmt += 2;
|
|
continue;
|
|
}
|
|
if (std::isdigit(*fmt)) {
|
|
int n = 0; // value ignored
|
|
if (const char* np = ParseInt(fmt, 0, 0, 1024, &n)) {
|
|
if (*np == 'S') {
|
|
data = ParseInt(data, 2, 0, 60, &tm.tm_sec);
|
|
if (data != nullptr && *data == '.') {
|
|
data = ParseSubSeconds(data + 1, &subseconds);
|
|
}
|
|
fmt = ++np;
|
|
continue;
|
|
}
|
|
if (*np == 'f') {
|
|
if (data != nullptr && std::isdigit(*data)) {
|
|
data = ParseSubSeconds(data, &subseconds);
|
|
}
|
|
fmt = ++np;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (*fmt == 'c') twelve_hour = false; // probably uses %H
|
|
if (*fmt == 'X') twelve_hour = false; // probably uses %H
|
|
if (*fmt != '\0') ++fmt;
|
|
break;
|
|
case 'O':
|
|
if (*fmt == 'H') twelve_hour = false;
|
|
if (*fmt == 'I') twelve_hour = true;
|
|
if (*fmt != '\0') ++fmt;
|
|
break;
|
|
}
|
|
|
|
// Parses the current specifier.
|
|
const char* orig_data = data;
|
|
std::string spec(percent, static_cast<std::size_t>(fmt - percent));
|
|
data = ParseTM(data, spec.c_str(), &tm);
|
|
|
|
// If we successfully parsed %p we need to remember whether the result
|
|
// was AM or PM so that we can adjust tm_hour before time_zone::lookup().
|
|
// So reparse the input with a known AM hour, and check if it is shifted
|
|
// to a PM hour.
|
|
if (spec == "%p" && data != nullptr) {
|
|
std::string test_input = "1";
|
|
test_input.append(orig_data, static_cast<std::size_t>(data - orig_data));
|
|
const char* test_data = test_input.c_str();
|
|
std::tm tmp{};
|
|
ParseTM(test_data, "%I%p", &tmp);
|
|
afternoon = (tmp.tm_hour == 13);
|
|
}
|
|
}
|
|
|
|
// Adjust a 12-hour tm_hour value if it should be in the afternoon.
|
|
if (twelve_hour && afternoon && tm.tm_hour < 12) {
|
|
tm.tm_hour += 12;
|
|
}
|
|
|
|
if (data == nullptr) {
|
|
if (err != nullptr) *err = "Failed to parse input";
|
|
return false;
|
|
}
|
|
|
|
// Skip any remaining whitespace.
|
|
while (std::isspace(*data)) ++data;
|
|
|
|
// parse() must consume the entire input std::string.
|
|
if (*data != '\0') {
|
|
if (err != nullptr) *err = "Illegal trailing data in input string";
|
|
return false;
|
|
}
|
|
|
|
// If we saw %s then we ignore anything else and return that time.
|
|
if (saw_percent_s) {
|
|
*sec = FromUnixSeconds(percent_s);
|
|
*fs = detail::femtoseconds::zero();
|
|
return true;
|
|
}
|
|
|
|
// If we saw %z, %Ez, or %E*z then we want to interpret the parsed fields
|
|
// in UTC and then shift by that offset. Otherwise we want to interpret
|
|
// the fields directly in the passed time_zone.
|
|
time_zone ptz = saw_offset ? utc_time_zone() : tz;
|
|
|
|
// Allows a leap second of 60 to normalize forward to the following ":00".
|
|
if (tm.tm_sec == 60) {
|
|
tm.tm_sec -= 1;
|
|
offset -= 1;
|
|
subseconds = detail::femtoseconds::zero();
|
|
}
|
|
|
|
if (!saw_year) {
|
|
year = year_t{tm.tm_year};
|
|
if (year > kyearmax - 1900) {
|
|
// Platform-dependent, maybe unreachable.
|
|
if (err != nullptr) *err = "Out-of-range year";
|
|
return false;
|
|
}
|
|
year += 1900;
|
|
}
|
|
|
|
const int month = tm.tm_mon + 1;
|
|
civil_second cs(year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
|
|
// parse() should not allow normalization. Due to the restricted field
|
|
// ranges above (see ParseInt()), the only possibility is for days to roll
|
|
// into months. That is, parsing "Sep 31" should not produce "Oct 1".
|
|
if (cs.month() != month || cs.day() != tm.tm_mday) {
|
|
if (err != nullptr) *err = "Out-of-range field";
|
|
return false;
|
|
}
|
|
|
|
// Accounts for the offset adjustment before converting to absolute time.
|
|
if ((offset < 0 && cs > civil_second::max() + offset) ||
|
|
(offset > 0 && cs < civil_second::min() + offset)) {
|
|
if (err != nullptr) *err = "Out-of-range field";
|
|
return false;
|
|
}
|
|
cs -= offset;
|
|
|
|
const auto tp = ptz.lookup(cs).pre;
|
|
// Checks for overflow/underflow and returns an error as necessary.
|
|
if (tp == time_point<seconds>::max()) {
|
|
const auto al = ptz.lookup(time_point<seconds>::max());
|
|
if (cs > al.cs) {
|
|
if (err != nullptr) *err = "Out-of-range field";
|
|
return false;
|
|
}
|
|
}
|
|
if (tp == time_point<seconds>::min()) {
|
|
const auto al = ptz.lookup(time_point<seconds>::min());
|
|
if (cs < al.cs) {
|
|
if (err != nullptr) *err = "Out-of-range field";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
*sec = tp;
|
|
*fs = subseconds;
|
|
return true;
|
|
}
|
|
|
|
} // namespace detail
|
|
} // namespace cctz
|
|
} // namespace time_internal
|
|
} // namespace absl
|