Import of CCTZ from GitHub. PiperOrigin-RevId: 1003089943
diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc index c04b680..5cee53f 100644 --- a/absl/time/internal/cctz/src/time_zone_info.cc +++ b/absl/time/internal/cctz/src/time_zone_info.cc
@@ -890,13 +890,18 @@ ttp = &transition_types_[tr.type_index]; tr.civil_sec = LocalTime(tr.unix_time, *ttp).cs; if (i != 0) { - // Check that offset changes don't cross each other. No one - // does this in practice, and we depend on increasing absolute - // and civil times in BreakTime() and MakeTime() respectively. - if (!Transition::ByUnixTime()(transitions_[i - 1], tr) || - !Transition::ByCivilTime()(transitions_[i - 1], tr)) { + // Check that offset changes don't cross each other, including the + // civil times each one skips or repeats. No one does this in + // practice, and we depend on increasing absolute and civil times + // in BreakTime() and MakeTime() respectively. + const Transition& prev_tr(transitions_[i - 1]); + if (prev_tr.unix_time >= tr.unix_time) { return false; // out of order } + if (std::max(prev_tr.prev_civil_sec, prev_tr.civil_sec) >= + std::min(tr.prev_civil_sec, tr.civil_sec)) { + return false; // transitions too close + } } }
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 ab9a432..c242bbc 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc
@@ -21,6 +21,7 @@ #include <memory> #include <string> #include <thread> +#include <type_traits> #include <utility> #include <vector> @@ -37,6 +38,19 @@ #include "absl/time/internal/cctz/src/test_time_zone_names.h" #include "absl/time/internal/cctz/src/tzfile.h" +#if __cplusplus < 201402L && !(defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +namespace { +template <typename T, typename... Args> +typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type +cxx11_make_unique(Args&&... args) { + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); +} +} // namespace +#define make_unique cxx11_make_unique +#else +using std::make_unique; +#endif + namespace chrono = std::chrono; namespace absl { @@ -948,6 +962,8 @@ ExpectTime(tp, tz, 10000, 1, 1, 0, 0, 0, 0 * 3600, false, "UTC"); } +namespace { + // A ZoneInfoSource implementation backed by an in-memory string buffer. class StringZoneInfoSource : public ZoneInfoSource { public: @@ -976,10 +992,8 @@ // 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 MakeTZif(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; @@ -1053,138 +1067,145 @@ return s; } -std::unique_ptr<ZoneInfoSource> ExtendedTestFactory( +std::unique_ptr<ZoneInfoSource> TestZoneInfoSourceFactory( 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"))); + return make_unique<StringZoneInfoSource>(MakeTZif( + -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"))); + return make_unique<StringZoneInfoSource>(MakeTZif( + 0, -5 * 3600, std::string{"EST", 4}, "EST5EDT,M3.2.0,M11.1.0")); } if (name == "test:ExtendedOverlappingRules") { // The daylight time of one year starts more than four days after its // nominal date, while the next year's ends more than five days before // its own, so the two years' generated transitions overlap. - return std::unique_ptr<ZoneInfoSource>( - new StringZoneInfoSource(MakeExtendedTzif( - 0, -5 * 3600, std::string{"STD", 4}, - "STD-12:00:00DST12:00:00,358/100:00:00,1/-139:00:00"))); + return make_unique<StringZoneInfoSource>( + MakeTZif(0, -5 * 3600, std::string{"STD", 4}, + "STD-12:00:00DST12:00:00,358/100:00:00,1/-139:00:00")); + } + if (name == "test:ExtendedCrossingChanges") { + // Daylight time ends at 01:00, falling back to 23:00 standard time, + // and restarts at 00:00 standard time, springing forward to 02:00. + // So the civil times repeated by the first change (23:00 to 00:59) + // run into those skipped by the second (00:00 to 01:59). + return make_unique<StringZoneInfoSource>( + MakeTZif(0, 0, std::string{"STD", 4}, "STD0DST-2,M3.2.0/0,M3.2.0/1")); + } + if (name == "test:ExtendedCrossingChangesReversed") { + // The reverse order: daylight time starts at 02:00, springing forward + // to 04:00, and ends at 05:00, falling back to 03:00. So the civil + // times skipped by the first change (02:00 to 03:59) run into those + // repeated by the second (03:00 to 04:59). + return make_unique<StringZoneInfoSource>( + MakeTZif(0, 0, std::string{"STD", 4}, "STD0DST-2,M3.2.0/2,M3.2.0/5")); } 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 make_unique<StringZoneInfoSource>( + MakeTZif(0, -5 * 3600, "EST", "EST5EDT,M3.2.0,M11.1.0")); } if (name == "test:ExtendedLargeOffset") { // A file-supplied UTC offset of 25h is more than a day from UTC but still // within the (-25h < utoff < 26h) range RFC 9636 allows, so the reader // must accept it. - return std::unique_ptr<ZoneInfoSource>( - new StringZoneInfoSource(MakeExtendedTzif( - 0, 25 * 3600, std::string{"EST", 4}, "EST5EDT,M3.2.0,M11.1.0"))); + return make_unique<StringZoneInfoSource>(MakeTZif( + 0, 25 * 3600, std::string{"EST", 4}, "EST5EDT,M3.2.0,M11.1.0")); } if (name == "test:MinUtcOffset") { // The minimum 32-bit UTC offset is far outside the (-25h < utoff < 26h) // range RFC 9636 allows, and is the one offset whose negation overflows // in FormatOffset(), so the reader must reject it. const std::int_fast32_t min_offset = -2147483647 - 1; - return std::unique_ptr<ZoneInfoSource>(new StringZoneInfoSource( - MakeExtendedTzif(0, min_offset, std::string{"X", 2}, ""))); + return make_unique<StringZoneInfoSource>( + MakeTZif(0, min_offset, std::string{"X", 2}, "")); } + EXPECT_NE(name.compare(0, 5, "test:"), 0); return fallback(name); } +class TZifTest : public ::testing::Test { + protected: + TZifTest() { + saved_factory = cctz_extension::zone_info_source_factory; + cctz_extension::zone_info_source_factory = TestZoneInfoSourceFactory; + } + + ~TZifTest() { cctz_extension::zone_info_source_factory = saved_factory; } + + private: + cctz_extension::ZoneInfoSourceFactory saved_factory; +}; + +} // namespace + // 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; - +TEST_F(TZifTest, UnterminatedAbbreviation) { 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; - +TEST_F(TZifTest, ExtendedBeforeEpoch) { // 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; } // A transition time may include a day offset of up to +/-167 hours, so the // rules for consecutive years can overlap, producing transitions that go // backward in unix time. BreakTime() binary searches the transitions by // unix time, so such a zone must be rejected. -TEST(TimeZoneEdgeCase, ExtendedOverlappingRules) { - auto prev_factory = cctz_extension::zone_info_source_factory; - cctz_extension::zone_info_source_factory = ExtendedTestFactory; - +TEST_F(TZifTest, ExtendedOverlappingRules) { time_zone tz; EXPECT_FALSE(load_time_zone("test:ExtendedOverlappingRules", &tz)); +} - cctz_extension::zone_info_source_factory = prev_factory; +// Two offset changes can be close enough that the civil times skipped or +// repeated by one overlap those of the other, even though the transitions +// are ordered by both absolute and civil time. MakeTime() cannot represent +// that, so such a zone must be rejected. +TEST_F(TZifTest, ExtendedCrossingChanges) { + time_zone tz; + EXPECT_FALSE(load_time_zone("test:ExtendedCrossingChanges", &tz)); + EXPECT_FALSE(load_time_zone("test:ExtendedCrossingChangesReversed", &tz)); } // A file-supplied UTC offset more than a day from UTC is still valid per // RFC 9636 (which allows -25h < utoff < 26h), so loading must succeed. -TEST(TimeZoneEdgeCase, ExtendedLargeOffset) { - auto prev_factory = cctz_extension::zone_info_source_factory; - cctz_extension::zone_info_source_factory = ExtendedTestFactory; - +TEST_F(TZifTest, ExtendedLargeOffset) { time_zone tz; EXPECT_TRUE(load_time_zone("test:ExtendedLargeOffset", &tz)); - - cctz_extension::zone_info_source_factory = prev_factory; } // A file-supplied UTC offset outside the range RFC 9636 allows (which is // -25h < utoff < 26h) would break the fixed-width offset formatting, so // loading must fail. -TEST(TimeZoneEdgeCase, MinUtcOffset) { - auto prev_factory = cctz_extension::zone_info_source_factory; - cctz_extension::zone_info_source_factory = ExtendedTestFactory; - +TEST_F(TZifTest, MinUtcOffset) { time_zone tz; EXPECT_FALSE(load_time_zone("test:MinUtcOffset", &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; - +TEST_F(TZifTest, ExtendedFarFuture) { 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