// 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 // // https://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) && !defined(__MINGW32__) # define HAS_STRPTIME 1 // assume everyone has strptime() except windows # endif #endif #if defined(HAS_STRPTIME) && HAS_STRPTIME # if !defined(_XOPEN_SOURCE) # define _XOPEN_SOURCE // Definedness suffices for strptime. # endif #endif #include "absl/time/internal/cctz/include/cctz/time_zone.h" // Include time.h directly since, by C++ standards, ctime doesn't have to // declare strptime. #include #include #include #include #include #include #include #include #include #include #if !HAS_STRPTIME #include #include #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(s) + (input.eof() ? strlen(s) : static_cast(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::min() + 1900) { tm.tm_year = std::numeric_limits::min(); } else if (al.cs.year() - 1900 > std::numeric_limits::max()) { tm.tm_year = std::numeric_limits::max(); } else { tm.tm_year = static_cast(al.cs.year() - 1900); } switch (get_weekday(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(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::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 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 const char* ParseInt(const char* dp, int width, T min, T max, T* vp) { if (dp != nullptr) { const T kmin = std::numeric_limits::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(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& 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(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(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(ep - bp)); break; case 'm': bp = Format02d(ep, al.cs.month()); result.append(bp, static_cast(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(ep - bp)); break; case 'H': bp = Format02d(ep, al.cs.hour()); result.append(bp, static_cast(ep - bp)); break; case 'M': bp = Format02d(ep, al.cs.minute()); result.append(bp, static_cast(ep - bp)); break; case 'S': bp = Format02d(ep, al.cs.second()); result.append(bp, static_cast(ep - bp)); break; case 'z': bp = FormatOffset(ep, al.offset, ""); result.append(bp, static_cast(ep - bp)); break; case 'Z': result.append(al.abbr); break; case 's': bp = Format64(ep, 0, ToUnixSeconds(tp)); result.append(bp, static_cast(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(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(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(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(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(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(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(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(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(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* 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::max(); const year_t kyearmin = std::numeric_limits::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::min(), std::numeric_limits::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(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(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::max()) { const auto al = ptz.lookup(time_point::max()); if (cs > al.cs) { if (err != nullptr) *err = "Out-of-range field"; return false; } } if (tp == time_point::min()) { const auto al = ptz.lookup(time_point::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