Rollback import of CCTZ from GitHub. PiperOrigin-RevId: 958551023 Change-Id: I19160c0858b4f4ade49e6287908cc2a69183bd88
diff --git a/absl/time/internal/cctz/BUILD.bazel b/absl/time/internal/cctz/BUILD.bazel index edec282..3b47877 100644 --- a/absl/time/internal/cctz/BUILD.bazel +++ b/absl/time/internal/cctz/BUILD.bazel
@@ -140,10 +140,7 @@ name = "time_zone_lookup_test", size = "small", timeout = "moderate", - srcs = [ - "src/time_zone_lookup_test.cc", - "src/tzfile.h", - ], + srcs = ["src/time_zone_lookup_test.cc"], copts = ABSL_TEST_COPTS, data = [":zoneinfo"], linkopts = ABSL_DEFAULT_LINKOPTS, @@ -180,24 +177,6 @@ ], ) -cc_test( - name = "time_zone_posix_test", - size = "small", - srcs = [ - "src/time_zone_posix.h", - "src/time_zone_posix_test.cc", - ], - copts = ABSL_TEST_COPTS, - linkopts = ABSL_DEFAULT_LINKOPTS, - deps = [ - ":civil_time", - ":time_zone", - "//absl/base:config", - "@googletest//:gtest", - "@googletest//:gtest_main", - ], -) - ### benchmarks cc_test(
diff --git a/absl/time/internal/cctz/src/cctz_benchmark.cc b/absl/time/internal/cctz/src/cctz_benchmark.cc index 17ff576..ce27818 100644 --- a/absl/time/internal/cctz/src/cctz_benchmark.cc +++ b/absl/time/internal/cctz/src/cctz_benchmark.cc
@@ -28,7 +28,6 @@ namespace { -// SKIP_ABSL_INLINE_NAMESPACE_CHECK namespace cctz = absl::time_internal::cctz; void BM_Difference_Days(benchmark::State& state) {
diff --git a/absl/time/internal/cctz/src/time_zone_fixed.cc b/absl/time/internal/cctz/src/time_zone_fixed.cc index b33410d..ed7f9cb 100644 --- a/absl/time/internal/cctz/src/time_zone_fixed.cc +++ b/absl/time/internal/cctz/src/time_zone_fixed.cc
@@ -40,19 +40,12 @@ return p; } -// Returns the value of the decimal digit ch, or -1 if ch is not a digit. -// Note that std::strchr() also matches kDigits' terminating '\0', which -// would otherwise be taken for a tenth digit. -int ParseDigit(char ch) { - const char* const dp = std::strchr(kDigits, ch); - return (dp == nullptr || *dp == '\0') ? -1 : static_cast<int>(dp - kDigits); -} - int Parse02d(const char* p) { - const int hi = ParseDigit(p[0]); - if (hi >= 0) { - const int lo = ParseDigit(p[1]); - if (lo >= 0) return (hi * 10) + lo; + if (const char* ap = std::strchr(kDigits, *p)) { + int v = static_cast<int>(ap - kDigits); + if (const char* bp = std::strchr(kDigits, *++p)) { + return (v * 10) + static_cast<int>(bp - kDigits); + } } return -1; }
diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc index f97de2f..91b4621 100644 --- a/absl/time/internal/cctz/src/time_zone_format.cc +++ b/absl/time/internal/cctz/src/time_zone_format.cc
@@ -58,16 +58,6 @@ namespace { -// The ctype functions have undefined behavior for negative char values, -// so these helpers ensure the argument is always in the unsigned-char domain. -bool isdigit(char ch) { - return std::isdigit(static_cast<unsigned char>(ch)) != 0; -} - -bool isspace(char ch) { - return std::isspace(static_cast<unsigned char>(ch)) != 0; -} - #if !HAS_STRPTIME // Build a strptime() using C++11's std::get_time(). char* strptime(const char* s, const char* fmt, std::tm* tm) { @@ -563,7 +553,7 @@ bp = Format64(ep, 4, al.cs.year()); result.append(bp, ep); pending = cur += 2; - } else if (isdigit(*cur)) { + } 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)) { @@ -630,7 +620,7 @@ const char* ParseZone(const char* dp, std::string* zone) { zone->clear(); if (dp != nullptr) { - while (*dp != '\0' && !isspace(*dp)) zone->push_back(*dp++); + while (*dp != '\0' && !std::isspace(*dp)) zone->push_back(*dp++); if (zone->empty()) dp = nullptr; } return dp; @@ -716,7 +706,7 @@ const char* const edata = data + input.size(); // Skips leading whitespace. - while (isspace(*data)) ++data; + 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(); @@ -754,9 +744,9 @@ // Steps through format, one specifier at a time. while (data != nullptr && fmt != efmt) { - if (isspace(*fmt)) { - while (isspace(*data)) ++data; - while (isspace(*++fmt)) continue; + if (std::isspace(*fmt)) { + while (std::isspace(*data)) ++data; + while (std::isspace(*++fmt)) continue; continue; } @@ -908,7 +898,7 @@ continue; } if (fmt[0] == '*' && fmt[1] == 'f') { - if (data != nullptr && isdigit(*data)) { + if (data != nullptr && std::isdigit(*data)) { data = ParseSubSeconds(data, &subseconds); } fmt += 2; @@ -927,7 +917,7 @@ fmt += 2; continue; } - if (isdigit(*fmt)) { + if (std::isdigit(*fmt)) { int n = 0; // value ignored if (const char* np = ParseInt(fmt, 0, 0, 1024, &n)) { if (*np == 'S') { @@ -939,7 +929,7 @@ continue; } if (*np == 'f') { - if (data != nullptr && isdigit(*data)) { + if (data != nullptr && std::isdigit(*data)) { data = ParseSubSeconds(data, &subseconds); } fmt = ++np; @@ -987,7 +977,7 @@ } // Skip any remaining whitespace. - while (isspace(*data)) ++data; + while (std::isspace(*data)) ++data; // parse() must consume the entire input string. if (data != edata) {
diff --git a/absl/time/internal/cctz/src/time_zone_format_test.cc b/absl/time/internal/cctz/src/time_zone_format_test.cc index 8793a80..f047d93 100644 --- a/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/absl/time/internal/cctz/src/time_zone_format_test.cc
@@ -1002,27 +1002,6 @@ EXPECT_FALSE(parse("%Ez", "-00:-0", tz, &tp)); } -TEST(Parse, NonAsciiInput) { - const time_zone tz = utc_time_zone(); - auto tp = chrono::system_clock::from_time_t(0); - - // High-bit-set bytes reach the ctype functions during parsing. 0xA0 is not - // ASCII whitespace, so the leading-whitespace skip must not consume it and - // the parse must fail rather than pass a negative char to std::isspace(). - EXPECT_FALSE(parse("%Y-%m-%d", - "\xA0" - "2016-01-02", - tz, &tp)); - EXPECT_FALSE(parse("%Y", - "\xA0" - "2016", - tz, &tp)); - - // A leading ASCII space is still skipped as before. - EXPECT_TRUE(parse("%Y-%m-%d", " 2016-01-02", tz, &tp)); - EXPECT_EQ(2016, convert(tp, utc_time_zone()).year()); -} - TEST(Parse, PosixConversions) { time_zone tz = utc_time_zone(); auto tp = chrono::system_clock::from_time_t(0);
diff --git a/absl/time/internal/cctz/src/time_zone_impl.cc b/absl/time/internal/cctz/src/time_zone_impl.cc index 5cf38e7..5f2f49e 100644 --- a/absl/time/internal/cctz/src/time_zone_impl.cc +++ b/absl/time/internal/cctz/src/time_zone_impl.cc
@@ -76,18 +76,9 @@ // Add the new time zone to the map. std::lock_guard<std::mutex> lock(TimeZoneMutex()); if (time_zone_map == nullptr) time_zone_map = new TimeZoneImplByName; - if (!new_impl->zone_) { - // Load failed, but a successful insertion may have happened concurrently. - // Check it now that we have the lock. Otherwise, avoid caching negative - // entries to avoid unbounded growth and DoS attacks. - auto itr = time_zone_map->find(name); - const Impl* impl = (itr != time_zone_map->end()) ? itr->second : utc_impl; - *tz = time_zone(impl); - return impl != utc_impl; - } const Impl*& impl = (*time_zone_map)[name]; if (impl == nullptr) { // this thread won any load race - impl = new_impl.release(); + impl = new_impl->zone_ ? new_impl.release() : utc_impl; } *tz = time_zone(impl); return impl != utc_impl;
diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc index 1e707c8..f8484c9 100644 --- a/absl/time/internal/cctz/src/time_zone_info.cc +++ b/absl/time/internal/cctz/src/time_zone_info.cc
@@ -32,14 +32,6 @@ #include "absl/time/internal/cctz/src/time_zone_info.h" -#include "absl/base/config.h" - -#if !defined(_MSC_VER) -#include <fcntl.h> -#include <sys/stat.h> -#include <unistd.h> -#endif - #include <algorithm> #include <cassert> #include <chrono> @@ -49,17 +41,16 @@ #include <cstring> #include <fstream> #include <functional> -#include <limits> #include <memory> #include <sstream> #include <string> #include <utility> #include <vector> +#include "absl/base/config.h" #include "absl/time/internal/cctz/include/cctz/civil_time.h" #include "absl/time/internal/cctz/src/time_zone_fixed.h" #include "absl/time/internal/cctz/src/time_zone_posix.h" -#include "absl/time/internal/cctz/src/tzfile.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -347,14 +338,6 @@ return EquivTransitions(transitions_.back().type_index, dst_ti); } - // We require that zoneinfo data with a rule for future transitions - // ends with a non-negative transition. This removes the need to add - // any "second-half" transition to ensure differences between adjacent - // transitions are always representable, while also guaranteeing that - // the arithmetic used to shift between 400-year cycles never overflows. - // All valid zones easily meet this requirement. - if (transitions_.back().unix_time < 0) return false; - // Extend the transitions for an additional 401 years using the future // specification. Years beyond those can be handled by mapping back to // a cycle-equivalent year within that range. Note that we need 401 @@ -399,45 +382,18 @@ using FilePtr = std::unique_ptr<FILE, int (*)(FILE*)>; -// fopen(3) adaptor for reading zoneinfo files (read-only binary mode). -inline FilePtr FOpen(const char* path) { +// fopen(3) adaptor. +inline FilePtr FOpen(const char* path, const char* mode) { #if defined(_MSC_VER) FILE* fp; - if (fopen_s(&fp, path, "rb") != 0) fp = nullptr; + if (fopen_s(&fp, path, mode) != 0) fp = nullptr; return FilePtr(fp, fclose); #else - // Open non-blocking and verify the target is a regular file before handing it - // to stdio. Zone names are potentially attacker-controlled, and a plain - // fopen() on a FIFO or device node (reachable via the "file:" prefix or an - // absolute path) would block indefinitely or read unbounded data. - const int fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); - if (fd >= 0) { - struct stat st; - if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) { - FILE* fp = fdopen(fd, "rb"); - if (fp != nullptr) return FilePtr(fp, fclose); - } - close(fd); - } - return FilePtr(nullptr, fclose); + // TODO: Enable the close-on-exec flag. + return FilePtr(fopen(path, mode), fclose); #endif } -// Returns true if the zone name starting at pos contains an unsafe path. -inline bool UnsafePath(const std::string& name, std::size_t pos) { - // Path traversal: exact match ".." - if (name.compare(pos, std::string::npos, "..") == 0) return true; - // Path traversal: leading component "../" - if (name.compare(pos, 3, "../") == 0) return true; - // Path traversal: interior component "/../" - if (name.find("/../", pos) != std::string::npos) return true; - // Path traversal: trailing component "/.." - if (name.size() - pos >= 3 && name.compare(name.size() - 3, 3, "/..") == 0) { - return true; - } - return false; -} - // A stdio(3)-backed implementation of ZoneInfoSource. class FileZoneInfoSource : public ZoneInfoSource { public: @@ -475,11 +431,6 @@ // Use of the "file:" prefix is intended for testing purposes only. const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0; - // Reject unsafe paths (e.g., "../../etc/passwd"). - if (UnsafePath(name, pos)) { - return nullptr; - } - // Map the time-zone name to a path name. std::string path; if (pos == name.size() || name[pos] != '/') { @@ -500,7 +451,7 @@ path.append(name, pos, std::string::npos); // Open the zoneinfo file. - auto fp = FOpen(path.c_str()); + auto fp = FOpen(path.c_str(), "rb"); if (fp == nullptr) return nullptr; return std::unique_ptr<ZoneInfoSource>(new FileZoneInfoSource(std::move(fp))); } @@ -526,7 +477,7 @@ for (const char* tzdata : {"/apex/com.android.tzdata/etc/tz/tzdata", "/data/misc/zoneinfo/current/tzdata", "/system/usr/share/zoneinfo/tzdata"}) { - auto fp = FOpen(tzdata); + auto fp = FOpen(tzdata, "rb"); if (fp == nullptr) continue; char hbuf[24]; // covers header.zonetab_offset too @@ -546,12 +497,9 @@ if (zonecnt * sizeof(ebuf) != index_size) continue; for (std::size_t i = 0; i != zonecnt; ++i) { if (fread(ebuf, 1, sizeof(ebuf), fp.get()) != sizeof(ebuf)) break; - const std::int_fast64_t start = - std::int_fast64_t{data_offset} + Decode32(ebuf + 40); + const std::int_fast32_t start = data_offset + Decode32(ebuf + 40); const std::int_fast32_t length = Decode32(ebuf + 44); if (start < 0 || length < 0) break; - // fseek() takes a long - if (start > std::numeric_limits<long>::max()) break; ebuf[40] = '\0'; // ensure zone name is NUL terminated if (strcmp(name.c_str() + pos, ebuf) == 0) { if (fseek(fp.get(), static_cast<long>(start), SEEK_SET) != 0) break; @@ -589,11 +537,6 @@ // Use of the "file:" prefix is intended for testing purposes only. const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0; - // Reject unsafe paths (e.g., "../../etc/passwd"). - if (UnsafePath(name, pos)) { - return nullptr; - } - // Prefixes where a Fuchsia component might find zoneinfo files, // in descending order of preference. const auto kTzdataPrefixes = { @@ -618,7 +561,7 @@ if (!prefix.empty()) path += "zoneinfo/tzif2/"; // format path.append(name, pos, std::string::npos); - auto fp = FOpen(path.c_str()); + auto fp = FOpen(path.c_str(), "rb"); if (fp == nullptr) continue; std::string version; @@ -719,12 +662,6 @@ if (hdr.ttisstdcnt != 0 && hdr.ttisstdcnt != hdr.typecnt) return false; if (hdr.ttisutcnt != 0 && hdr.ttisutcnt != hdr.typecnt) return false; - // Bound the header counts before sizing tbuf so that a hostile TZif blob - // cannot force a very large zero-filled allocation from a tiny input. - if (hdr.timecnt > TZ_MAX_TIMES) return false; - if (hdr.typecnt > TZ_MAX_TYPES) return false; - if (hdr.charcnt > TZ_MAX_CHARS) return false; - // Read the data into a local buffer. std::size_t len = hdr.DataLength(time_len); std::vector<char> tbuf(len); @@ -737,14 +674,6 @@ for (std::size_t i = 0; i != hdr.timecnt; ++i) { transitions_[i].unix_time = (time_len == 4) ? Decode32(bp) : Decode64(bp); bp += time_len; - // A valid zoneinfo file keeps transition times far from the int64 limits. - // A hostile one can place them at the extremes, where the - // reverse-conversion arithmetic in MakeTime() (tr.unix_time +/- a sub-day - // civil delta, see MakeSkipped()/MakeRepeated()) overflows. Bound them to - // +/-(1<<59), the times used by the no-op transitions added below. - if (transitions_[i].unix_time < -(1LL << 59) || - transitions_[i].unix_time > (1LL << 59)) - return false; // out of range if (i != 0) { // Check that the transitions are ordered by time (as zic guarantees). if (!Transition::ByUnixTime()(transitions_[i - 1], transitions_[i])) @@ -776,22 +705,16 @@ // Determine the before-first-transition type. default_transition_type_ = 0; if (seen_type_0 && hdr.timecnt != 0) { - std::size_t index = 0; + std::uint_fast8_t index = 0; if (transition_types_[0].is_dst) { index = transitions_[0].type_index; while (index != 0 && transition_types_[index].is_dst) --index; } while (index != hdr.typecnt && transition_types_[index].is_dst) ++index; - if (index != hdr.typecnt) - default_transition_type_ = static_cast<std::uint_fast8_t>(index); + if (index != hdr.typecnt) default_transition_type_ = index; } - // Copy all the abbreviations. The area holds NUL-terminated strings, and - // LocalTime() hands out a pointer into it, so the final abbreviation has - // to be terminated within the area itself. Otherwise an abbreviation runs - // on into whatever ExtendTransitions() later appends. (hdr.charcnt != 0 - // because every abbr_index was validated to be less than it.) - if (bp[hdr.charcnt - 1] != '\0') return false; + // Copy all the abbreviations. abbreviations_.reserve(hdr.charcnt + 10); abbreviations_.assign(bp, hdr.charcnt); bp += hdr.charcnt; @@ -848,7 +771,6 @@ // previous transition is always representable, without overflow. const Transition& last(transitions_.back()); if (last.unix_time < 0) { - assert(!extended_); const std::uint_fast8_t type_index = last.type_index; Transition& tr(*transitions_.emplace(transitions_.end())); tr.unix_time = 2147483647; // 2038-01-19T03:14:07+00:00
diff --git a/absl/time/internal/cctz/src/time_zone_lookup_test.cc b/absl/time/internal/cctz/src/time_zone_lookup_test.cc index 1147e93..cd08a35 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc
@@ -15,27 +15,21 @@ #include <chrono> #include <cstddef> #include <cstdlib> -#include <cstring> #include <future> #include <limits> -#include <memory> #include <string> #include <thread> -#include <utility> #include <vector> #include "absl/base/config.h" #include "absl/time/internal/cctz/include/cctz/time_zone.h" - #if defined(__linux__) #include <features.h> #endif #include "gtest/gtest.h" #include "absl/time/internal/cctz/include/cctz/civil_time.h" -#include "absl/time/internal/cctz/include/cctz/zone_info_source.h" #include "absl/time/internal/cctz/src/test_time_zone_names.h" -#include "absl/time/internal/cctz/src/tzfile.h" namespace chrono = std::chrono; @@ -186,24 +180,6 @@ EXPECT_FALSE(load_time_zone("", &tz)); EXPECT_EQ(chrono::system_clock::from_time_t(0), convert(civil_second(1970, 1, 1, 0, 0, 0), tz)); // UTC - - // Reject path-traversal components. - EXPECT_FALSE(load_time_zone("file:../etc/passwd", &tz)); - EXPECT_FALSE(load_time_zone("file:../../etc/passwd", &tz)); - EXPECT_FALSE(load_time_zone("file:/../etc/passwd", &tz)); - EXPECT_FALSE(load_time_zone("file:America/../America/Los_Angeles", &tz)); - - // Reject a fixed-offset name with a NUL where a digit belongs. - for (const int i : {10, 11, 13, 14, 16, 17}) { - std::string name = "Fixed/UTC+00:00:00"; - name[static_cast<std::size_t>(i)] = '\0'; - EXPECT_FALSE(load_time_zone(name, &tz)) << "NUL at offset " << i; - } - - // Reject non-regular files and directories. - EXPECT_FALSE(load_time_zone("file:/dev/null", &tz)); - EXPECT_FALSE(load_time_zone("file:/dev/stdin", &tz)); - EXPECT_FALSE(load_time_zone("file:/tmp", &tz)); } TEST(TimeZone, Equality) { @@ -936,181 +912,6 @@ ExpectTime(tp, tz, 10000, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); } -// A ZoneInfoSource implementation backed by an in-memory string buffer. -class StringZoneInfoSource : public ZoneInfoSource { - public: - explicit StringZoneInfoSource(std::string data) - : data_(std::move(data)), offset_(0) {} - - std::size_t Read(void* ptr, std::size_t size) override { - std::size_t n = (std::min)(size, data_.size() - offset_); - std::memcpy(ptr, data_.data() + offset_, n); - offset_ += n; - return n; - } - - int Skip(std::size_t offset) override { - if (offset > data_.size() - offset_) return -1; - offset_ += offset; - return 0; - } - - private: - std::string data_; - std::size_t offset_; -}; - -// Constructs a minimal TZif2 string with a single 64-bit transition -// at the given transition time and a future POSIX rule. The abbreviation -// area holds abbr verbatim, so a valid file's abbr must include the -// trailing '\0' (e.g., std::string{"EST", 4}). -std::string MakeExtendedTzif(std::int_fast64_t unix_time, - std::int_fast32_t utc_offset, - const std::string& abbr, - const std::string& future_spec) { - std::string s; - auto append32 = [&s](std::int_fast32_t v) { - const std::int_fast32_t s32max = 0x7fffffff; - const auto s32maxU = static_cast<std::uint_fast32_t>(s32max); - std::uint_fast32_t uv; - if (v >= 0) { - uv = static_cast<std::uint_fast32_t>(v); - } else { - uv = static_cast<std::uint_fast32_t>(v + s32max + 1) + s32maxU + 1; - } - for (int i = 3; i >= 0; --i) { - s.push_back(static_cast<char>((uv >> (i * 8)) & 0xff)); - } - }; - auto append64 = [&s](std::int_fast64_t v) { - const std::int_fast64_t s64max = 0x7fffffffffffffff; - const auto s64maxU = static_cast<std::uint_fast64_t>(s64max); - std::uint_fast64_t uv; - if (v >= 0) { - uv = static_cast<std::uint_fast64_t>(v); - } else { - uv = static_cast<std::uint_fast64_t>(v + s64max + 1) + s64maxU + 1; - } - for (int i = 7; i >= 0; --i) { - s.push_back(static_cast<char>((uv >> (i * 8)) & 0xff)); - } - }; - - const std::size_t charcnt = abbr.size(); - - // 32-bit header - s.append(TZ_MAGIC, 4); - s.push_back('2'); // tzh_version - s.append(15, '\0'); // tzh_reserved - append32(0); // tzh_ttisutcnt - append32(0); // tzh_ttisstdcnt - append32(0); // tzh_leapcnt - append32(0); // tzh_timecnt (0 32-bit transitions) - append32(1); // tzh_typecnt (1 ttinfo record) - append32(static_cast<std::int_fast32_t>(charcnt)); // tzh_charcnt - - // 32-bit data block - append32(utc_offset); // tt_utoff - s.push_back(0); // tt_isdst (standard time) - s.push_back(0); // tt_desigidx - s.append(abbr); // abbreviation table - - // 64-bit header - s.append(TZ_MAGIC, 4); - s.push_back('2'); // tzh_version - s.append(15, '\0'); // tzh_reserved - append32(0); // tzh_ttisutcnt - append32(0); // tzh_ttisstdcnt - append32(0); // tzh_leapcnt - append32(1); // tzh_timecnt (1 64-bit transition) - append32(1); // tzh_typecnt (1 ttinfo record) - append32(static_cast<std::int_fast32_t>(charcnt)); // tzh_charcnt - - // 64-bit data block - append64(unix_time); // transition time - s.push_back(0); // type index for transition - append32(utc_offset); // tt_utoff - s.push_back(0); // tt_isdst (standard time) - s.push_back(0); // tt_desigidx - s.append(abbr); // abbreviation table - - // POSIX footer - s.push_back('\n'); - s.append(future_spec); - s.push_back('\n'); - return s; -} - -std::unique_ptr<ZoneInfoSource> ExtendedTestFactory( - const std::string& name, - const std::function<std::unique_ptr<ZoneInfoSource>(const std::string&)>& - fallback) { - if (name == "test:ExtendedBeforeEpoch") { - // -1 (1969-12-31T23:59:59Z) is the latest final transition before the - // epoch, so the zone is rejected despite the future specification. - return std::unique_ptr<ZoneInfoSource>( - new StringZoneInfoSource(MakeExtendedTzif( - -1, -5 * 3600, std::string{"EST", 4}, "EST5EDT,M3.2.0,M11.1.0"))); - } - if (name == "test:ExtendedFarFuture") { - // 0 (1970-01-01T00:00:00Z) is the earliest final transition an extended - // zone may have, which maximizes the 400-year shift that BreakTime() - // needs for a lookup at the maximum time. - return std::unique_ptr<ZoneInfoSource>( - new StringZoneInfoSource(MakeExtendedTzif( - 0, -5 * 3600, std::string{"EST", 4}, "EST5EDT,M3.2.0,M11.1.0"))); - } - if (name == "test:UnterminatedAbbreviation") { - // The abbreviation area is missing its final NUL, so the abbreviation - // would run into whatever ExtendTransitions() appends behind it. - return std::unique_ptr<ZoneInfoSource>(new StringZoneInfoSource( - MakeExtendedTzif(0, -5 * 3600, "EST", "EST5EDT,M3.2.0,M11.1.0"))); - } - return fallback(name); -} - -// Tests that a TZif file whose abbreviation area is not NUL-terminated -// is rejected. -TEST(TimeZoneEdgeCase, UnterminatedAbbreviation) { - auto prev_factory = cctz_extension::zone_info_source_factory; - cctz_extension::zone_info_source_factory = ExtendedTestFactory; - - time_zone tz; - EXPECT_FALSE(load_time_zone("test:UnterminatedAbbreviation", &tz)); - - cctz_extension::zone_info_source_factory = prev_factory; -} - -// Tests that a TZif file whose explicit transitions end before epoch -// is rejected when it has a POSIX DST footer string. -TEST(TimeZoneEdgeCase, ExtendedBeforeEpoch) { - auto prev_factory = cctz_extension::zone_info_source_factory; - cctz_extension::zone_info_source_factory = ExtendedTestFactory; - - // Extended zones must end with a non-negative explicit transition. - time_zone tz; - EXPECT_FALSE(load_time_zone("test:ExtendedBeforeEpoch", &tz)); - - cctz_extension::zone_info_source_factory = prev_factory; -} - -// Looking up the maximum time in an extended zone must fold back through the -// 400-year cycle without overflowing when BreakTime() computes the shift. -TEST(TimeZoneEdgeCase, ExtendedFarFuture) { - auto prev_factory = cctz_extension::zone_info_source_factory; - cctz_extension::zone_info_source_factory = ExtendedTestFactory; - - time_zone tz; - ASSERT_TRUE(load_time_zone("test:ExtendedFarFuture", &tz)); - - auto tp_max = time_point<absl::time_internal::cctz::seconds>::max(); - ExpectTime(tp_max, tz, 292277026596, 12, 4, 10, 30, 7, -5 * 3600, false, - "EST"); - EXPECT_STREQ("EST", tz.lookup(tp_max).abbr); - - cctz_extension::zone_info_source_factory = prev_factory; -} - } // namespace cctz } // namespace time_internal ABSL_NAMESPACE_END
diff --git a/absl/time/internal/cctz/src/time_zone_posix.cc b/absl/time/internal/cctz/src/time_zone_posix.cc index c60f98b..efea080 100644 --- a/absl/time/internal/cctz/src/time_zone_posix.cc +++ b/absl/time/internal/cctz/src/time_zone_posix.cc
@@ -92,17 +92,14 @@ return p; } -// datetime = , ( Jn | n | Mm.w.d ) [ / offset ] +// datetime = ( Jn | n | Mm.w.d ) [ / offset ] const char* ParseDateTime(const char* p, PosixTransition* res) { - if (p != nullptr) { - if (*p != ',') return nullptr; + if (p != nullptr && *p == ',') { if (*++p == 'M') { int month = 0; - if ((p = ParseInt(p + 1, 1, 12, &month)) != nullptr) { - if (*p != '.') return nullptr; + if ((p = ParseInt(p + 1, 1, 12, &month)) != nullptr && *p == '.') { int week = 0; - if ((p = ParseInt(p + 1, 1, 5, &week)) != nullptr) { - if (*p != '.') return nullptr; + if ((p = ParseInt(p + 1, 1, 5, &week)) != nullptr && *p == '.') { int weekday = 0; if ((p = ParseInt(p + 1, 0, 6, &weekday)) != nullptr) { res->date.fmt = PosixTransition::M; @@ -125,17 +122,17 @@ res->date.n.day = static_cast<std::int_fast16_t>(day); } } - if (p != nullptr) { - res->time.offset = 2 * 60 * 60; // default offset is 02:00:00 - if (*p == '/') p = ParseOffset(p + 1, -167, 167, 1, &res->time.offset); - } + } + if (p != nullptr) { + res->time.offset = 2 * 60 * 60; // default offset is 02:00:00 + if (*p == '/') p = ParseOffset(p + 1, -167, 167, 1, &res->time.offset); } return p; } } // namespace -// spec = std offset [ dst [ offset ] datetime datetime ] +// spec = std offset [ dst [ offset ] , datetime , datetime ] bool ParsePosixSpec(const std::string& spec, PosixTimeZone* res) { const char* p = spec.c_str(); if (*p == ':') return false;
diff --git a/absl/time/internal/cctz/src/time_zone_posix.h b/absl/time/internal/cctz/src/time_zone_posix.h index 23f5699..7fd2b9e 100644 --- a/absl/time/internal/cctz/src/time_zone_posix.h +++ b/absl/time/internal/cctz/src/time_zone_posix.h
@@ -13,7 +13,7 @@ // limitations under the License. // Parsing of a POSIX zone spec as described in the TZ part of section 8.3 in -// https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap08.html. +// http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap08.html. // // The current POSIX spec for America/Los_Angeles is "PST8PDT,M3.2.0,M11.1.0", // which would be broken down as ...
diff --git a/absl/time/internal/cctz/src/time_zone_posix_test.cc b/absl/time/internal/cctz/src/time_zone_posix_test.cc deleted file mode 100644 index 8e429f7..0000000 --- a/absl/time/internal/cctz/src/time_zone_posix_test.cc +++ /dev/null
@@ -1,195 +0,0 @@ -// Copyright 2026 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. - -#include "absl/time/internal/cctz/src/time_zone_posix.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/base/config.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace time_internal { -namespace cctz { - -using ::testing::Eq; -using ::testing::IsEmpty; - -// We only support the second POSIX format (that is, neither the -// "first character is a <colon>" format, nor the "geographical -// or a special timezone" format). We also require DST start/end -// rules whenever a DST abbreviation is given (zic always provides -// them). So, ... -// -// spec = abbr offset [ abbr [ offset ] datetime datetime ] -// abbr = <.*?> | [^-+,\d]{3,} -// offset = [+|-]hh[:mm[:ss]] -// datetime = , ( Jn | n | Mm.w.d ) [ / offset ] - -TEST(ParsePosixSpec, UnsupportedFormats) { - PosixTimeZone zone; - EXPECT_FALSE(ParsePosixSpec(":characters", &zone)); - EXPECT_FALSE(ParsePosixSpec("Area/Location", &zone)); -} - -TEST(ParsePosixSpec, StdOnly) { - PosixTimeZone zone; - - // America/Cancun - EXPECT_TRUE(ParsePosixSpec("EST5", &zone)); - EXPECT_THAT(zone.std_abbr, Eq("EST")); - EXPECT_THAT(zone.std_offset, Eq(-5 * 60 * 60)); - EXPECT_THAT(zone.dst_abbr, IsEmpty()); - - // Australia/Darwin - EXPECT_TRUE(ParsePosixSpec("ACST-9:30", &zone)); - EXPECT_THAT(zone.std_abbr, Eq("ACST")); - EXPECT_THAT(zone.std_offset, Eq((9 * 60 + 30) * 60)); - EXPECT_THAT(zone.dst_abbr, IsEmpty()); - - // Australia/Eucla - EXPECT_TRUE(ParsePosixSpec("<+0845>-8:45", &zone)); - EXPECT_THAT(zone.std_abbr, Eq("+0845")); - EXPECT_THAT(zone.std_offset, Eq((8 * 60 + 45) * 60)); - EXPECT_THAT(zone.dst_abbr, IsEmpty()); -} - -TEST(ParsePosixSpec, WithDst) { - PosixTimeZone zone; - - // America/New_York - EXPECT_TRUE(ParsePosixSpec("EST5EDT,M3.2.0,M11.1.0", &zone)); - EXPECT_THAT(zone.std_abbr, Eq("EST")); - EXPECT_THAT(zone.std_offset, Eq(-5 * 60 * 60)); - EXPECT_THAT(zone.dst_abbr, Eq("EDT")); - EXPECT_THAT(zone.dst_offset, Eq(-4 * 60 * 60)); - EXPECT_THAT(zone.dst_start.date.fmt, Eq(PosixTransition::M)); - EXPECT_THAT(zone.dst_start.date.m.month, Eq(3)); - EXPECT_THAT(zone.dst_start.date.m.week, Eq(2)); - EXPECT_THAT(zone.dst_start.date.m.weekday, Eq(0)); - EXPECT_THAT(zone.dst_start.time.offset, Eq(2 * 60 * 60)); - EXPECT_THAT(zone.dst_end.date.fmt, Eq(PosixTransition::M)); - EXPECT_THAT(zone.dst_end.date.m.month, Eq(11)); - EXPECT_THAT(zone.dst_end.date.m.week, Eq(1)); - EXPECT_THAT(zone.dst_end.date.m.weekday, Eq(0)); - EXPECT_THAT(zone.dst_end.time.offset, Eq(2 * 60 * 60)); - - // Australia/Adelaide - EXPECT_TRUE(ParsePosixSpec("ACST-9:30ACDT,M10.1.0,M4.1.0/3", &zone)); - EXPECT_THAT(zone.std_abbr, Eq("ACST")); - EXPECT_THAT(zone.std_offset, Eq((9 * 60 + 30) * 60)); - EXPECT_THAT(zone.dst_abbr, Eq("ACDT")); - EXPECT_THAT(zone.dst_offset, Eq((10 * 60 + 30) * 60)); - EXPECT_THAT(zone.dst_start.date.fmt, Eq(PosixTransition::M)); - EXPECT_THAT(zone.dst_start.date.m.month, Eq(10)); - EXPECT_THAT(zone.dst_start.date.m.week, Eq(1)); - EXPECT_THAT(zone.dst_start.date.m.weekday, Eq(0)); - EXPECT_THAT(zone.dst_start.time.offset, Eq(2 * 60 * 60)); - EXPECT_THAT(zone.dst_end.date.fmt, Eq(PosixTransition::M)); - EXPECT_THAT(zone.dst_end.date.m.month, Eq(4)); - EXPECT_THAT(zone.dst_end.date.m.week, Eq(1)); - EXPECT_THAT(zone.dst_end.date.m.weekday, Eq(0)); - EXPECT_THAT(zone.dst_end.time.offset, Eq(3 * 60 * 60)); - - // Australia/Lord_Howe - EXPECT_TRUE(ParsePosixSpec("<+1030>-10:30<+11>-11,M10.1.0,M4.1.0", &zone)); - EXPECT_THAT(zone.std_abbr, Eq("+1030")); - EXPECT_THAT(zone.std_offset, Eq((10 * 60 + 30) * 60)); - EXPECT_THAT(zone.dst_abbr, Eq("+11")); - EXPECT_THAT(zone.dst_offset, Eq(11 * 60 * 60)); - EXPECT_THAT(zone.dst_start.date.fmt, Eq(PosixTransition::M)); - EXPECT_THAT(zone.dst_start.date.m.month, Eq(10)); - EXPECT_THAT(zone.dst_start.date.m.week, Eq(1)); - EXPECT_THAT(zone.dst_start.date.m.weekday, Eq(0)); - EXPECT_THAT(zone.dst_start.time.offset, Eq(2 * 60 * 60)); - EXPECT_THAT(zone.dst_end.date.fmt, Eq(PosixTransition::M)); - EXPECT_THAT(zone.dst_end.date.m.month, Eq(4)); - EXPECT_THAT(zone.dst_end.date.m.week, Eq(1)); - EXPECT_THAT(zone.dst_end.date.m.weekday, Eq(0)); - EXPECT_THAT(zone.dst_end.time.offset, Eq(2 * 60 * 60)); - - // Africa/Casablanca (year-round DST) - EXPECT_TRUE(ParsePosixSpec("<+00>0<+01>,0/0,J365/25", &zone)); - EXPECT_THAT(zone.std_abbr, Eq("+00")); - EXPECT_THAT(zone.std_offset, Eq(0)); - EXPECT_THAT(zone.dst_abbr, Eq("+01")); - EXPECT_THAT(zone.dst_offset, Eq(1 * 60 * 60)); - EXPECT_THAT(zone.dst_start.date.fmt, Eq(PosixTransition::N)); - EXPECT_THAT(zone.dst_start.date.n.day, Eq(0)); - EXPECT_THAT(zone.dst_start.time.offset, Eq(0)); - EXPECT_THAT(zone.dst_end.date.fmt, Eq(PosixTransition::J)); - EXPECT_THAT(zone.dst_end.date.n.day, Eq(365)); - EXPECT_THAT(zone.dst_end.time.offset, Eq(25 * 60 * 60)); -} - -TEST(TimeZonePosix, ParseErrors) { - PosixTimeZone zone; - - // STD abbreviation errors. - EXPECT_FALSE(ParsePosixSpec("ET5", &zone)); - EXPECT_FALSE(ParsePosixSpec("ET+", &zone)); - EXPECT_FALSE(ParsePosixSpec("ET-", &zone)); - EXPECT_FALSE(ParsePosixSpec("ET,", &zone)); - EXPECT_FALSE(ParsePosixSpec("<00", &zone)); - - // STD offset errors. - EXPECT_FALSE(ParsePosixSpec("<00>", &zone)); - EXPECT_FALSE(ParsePosixSpec("<00>+", &zone)); - EXPECT_FALSE(ParsePosixSpec("<00>-", &zone)); - EXPECT_FALSE(ParsePosixSpec("<00>?", &zone)); - - // DST abbreviation errors. - EXPECT_FALSE(ParsePosixSpec("EST5DT,M3.2.0,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT+,M3.2.0,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT-,M3.2.0,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("<00>0<-01", &zone)); - - // DST offset errors. - EXPECT_FALSE(ParsePosixSpec("<01>1<00>?,0,0", &zone)); - EXPECT_FALSE(ParsePosixSpec("<01>1<00>+?,0,0", &zone)); - EXPECT_FALSE(ParsePosixSpec("<01>1<00>-?,0,0", &zone)); - - // Malformed DST start date/time. - EXPECT_FALSE(ParsePosixSpec("EST5EDT", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M13.2.0,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.6.0,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.7,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,J0,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,366,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0/,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0/?,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0/1:?,M11.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0/1:2:?,M11.1.0", &zone)); - - // Malformed DST end date/time. - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0,M?.1.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0,M11.?.0", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0,M11.1.?", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0,J?", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0,?", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0,M11.1.0/", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0,M11.1.0/168", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0,M11.1.0/167:60", &zone)); - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0,M11.1.0/167:59:60", &zone)); - - // Trailing data. - EXPECT_FALSE(ParsePosixSpec("EST5EDT,M3.2.0,M11.1.0junk", &zone)); -} - -} // namespace cctz -} // namespace time_internal -ABSL_NAMESPACE_END -} // namespace absl
diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version index 9217a2d..75d34ee 100644 --- a/absl/time/internal/cctz/testdata/version +++ b/absl/time/internal/cctz/testdata/version
@@ -1 +1 @@ -2026c +2026b
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca index fb2f5cc..240ebb2 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun b/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun index 46286b9..909c5f9 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Edmonton b/absl/time/internal/cctz/testdata/zoneinfo/America/Edmonton index 379e365..645ee94 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Edmonton +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Edmonton Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife b/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife index 379e365..645ee94 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Mountain b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Mountain index 379e365..645ee94 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Mountain +++ b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Mountain Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab index 635eabc..a9b47bc 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab
@@ -112,7 +112,7 @@ CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton CST - AB, BC(E), NT(E), SK(W) +CA +5333-11328 America/Edmonton Mountain - AB, BC(E), NT(E), SK(W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +4916-12307 America/Vancouver MST - BC (most areas)
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab index 9c3a8cf..54e4485 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab
@@ -56,19 +56,16 @@ XX -2504-13005 Pacific/Pitcairn Pitcairn # # -08/-07 - PST/PDT (North America DST) -XX +340308-1181434 America/Los_Angeles Pacific (PST/PDT) - US; Mexico near US border +XX +340308-1181434 America/Los_Angeles Pacific (PST/PDT) - US & Canada; Mexico near US border # # -08/-07 - PST/PDT (North America DST) until 2026-11-01 02:00; then MST -XX +4916-12307 America/Vancouver Mountain Standard (MST) - British Columbia (most areas) +XX +4916-12307 America/Vancouver MST - BC (most areas) # # -07 - MST XX +332654-1120424 America/Phoenix Mountain Standard (MST) - Arizona; western Mexico; Yukon # # -07/-06 - MST/MDT (North America DST) -XX +394421-1045903 America/Denver Mountain (MST/MDT) - US; Mexico near US border; northern Canada -# -# -07/-06 - MST/MDT (North America DST) until 2026-11-01 02:00; then CST -XX +5333-11328 America/Edmonton Central Standard (CST) - Alberta and some neighbors +XX +394421-1045903 America/Denver Mountain (MST/MDT) - US & Canada; Mexico near US border # # -06 XX -0054-08936 Pacific/Galapagos Galápagos