diff options
Diffstat (limited to 'third_party/abseil-cpp/absl/time/internal/cctz/src')
15 files changed, 680 insertions, 246 deletions
diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/cctz_benchmark.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/cctz_benchmark.cc index d30a644e09..6770ad6b97 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/cctz_benchmark.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/cctz_benchmark.cc @@ -97,8 +97,8 @@ void BM_PrevWeekday(benchmark::State& state) { } BENCHMARK(BM_PrevWeekday); -const char RFC3339_full[] = "%Y-%m-%dT%H:%M:%E*S%Ez"; -const char RFC3339_sec[] = "%Y-%m-%dT%H:%M:%S%Ez"; +const char RFC3339_full[] = "%Y-%m-%d%ET%H:%M:%E*S%Ez"; +const char RFC3339_sec[] = "%Y-%m-%d%ET%H:%M:%S%Ez"; const char RFC1123_full[] = "%a, %d %b %Y %H:%M:%S %z"; const char RFC1123_no_wday[] = "%d %b %Y %H:%M:%S %z"; @@ -280,6 +280,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/North_Dakota/Beulah", "America/North_Dakota/Center", "America/North_Dakota/New_Salem", + "America/Nuuk", "America/Ojinaga", "America/Panama", "America/Pangnirtung", @@ -647,6 +648,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Pacific/Guam", "Pacific/Honolulu", "Pacific/Johnston", + "Pacific/Kanton", "Pacific/Kiritimati", "Pacific/Kosrae", "Pacific/Kwajalein", @@ -990,12 +992,12 @@ void BM_Time_FromCivilDay0_Libc(benchmark::State& state) { BENCHMARK(BM_Time_FromCivilDay0_Libc); const char* const kFormats[] = { - RFC1123_full, // 0 - RFC1123_no_wday, // 1 - RFC3339_full, // 2 - RFC3339_sec, // 3 - "%Y-%m-%dT%H:%M:%S", // 4 - "%Y-%m-%d", // 5 + RFC1123_full, // 0 + RFC1123_no_wday, // 1 + RFC3339_full, // 2 + RFC3339_sec, // 3 + "%Y-%m-%d%ET%H:%M:%S", // 4 + "%Y-%m-%d", // 5 }; const int kNumFormats = sizeof(kFormats) / sizeof(kFormats[0]); diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/civil_time_test.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/civil_time_test.cc index be894d7072..a5a7123041 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/civil_time_test.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/civil_time_test.cc @@ -235,6 +235,16 @@ TEST(CivilTime, Difference) { } // NOTE: Run this with --copt=-ftrapv to detect overflow problems. +TEST(CivilTime, ConstructionWithHugeYear) { + constexpr civil_hour h(-9223372036854775807, 1, 1, -1); + static_assert(h.year() == -9223372036854775807 - 1, + "ConstructionWithHugeYear"); + static_assert(h.month() == 12, "ConstructionWithHugeYear"); + static_assert(h.day() == 31, "ConstructionWithHugeYear"); + static_assert(h.hour() == 23, "ConstructionWithHugeYear"); +} + +// NOTE: Run this with --copt=-ftrapv to detect overflow problems. TEST(CivilTime, DifferenceWithHugeYear) { { constexpr civil_day d1(9223372036854775807, 1, 1); diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_fixed.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_fixed.cc index 303c0244a8..f2b3294ef7 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_fixed.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_fixed.cc @@ -53,7 +53,7 @@ int Parse02d(const char* p) { } // namespace bool FixedOffsetFromName(const std::string& name, seconds* offset) { - if (name.compare(0, std::string::npos, "UTC", 3) == 0) { + if (name == "UTC" || name == "UTC0") { *offset = seconds::zero(); return true; } diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format.cc index 950b23a1d2..d8cb047425 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format.cc @@ -67,6 +67,48 @@ char* strptime(const char* s, const char* fmt, std::tm* tm) { } #endif +// Convert a cctz::weekday to a tm_wday value (0-6, Sunday = 0). +int ToTmWday(weekday wd) { + switch (wd) { + case weekday::sunday: + return 0; + case weekday::monday: + return 1; + case weekday::tuesday: + return 2; + case weekday::wednesday: + return 3; + case weekday::thursday: + return 4; + case weekday::friday: + return 5; + case weekday::saturday: + return 6; + } + return 0; /*NOTREACHED*/ +} + +// Convert a tm_wday value (0-6, Sunday = 0) to a cctz::weekday. +weekday FromTmWday(int tm_wday) { + switch (tm_wday) { + case 0: + return weekday::sunday; + case 1: + return weekday::monday; + case 2: + return weekday::tuesday; + case 3: + return weekday::wednesday; + case 4: + return weekday::thursday; + case 5: + return weekday::friday; + case 6: + return weekday::saturday; + } + return weekday::sunday; /*NOTREACHED*/ +} + std::tm ToTM(const time_zone::absolute_lookup& al) { std::tm tm{}; tm.tm_sec = al.cs.second(); @@ -84,34 +126,19 @@ std::tm ToTM(const time_zone::absolute_lookup& al) { tm.tm_year = static_cast<int>(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_wday = ToTmWday(get_weekday(al.cs)); tm.tm_yday = get_yearday(al.cs) - 1; tm.tm_isdst = al.is_dst ? 1 : 0; return tm; } +// Returns the week of the year [0:53] given a civil day and the day on +// which weeks are defined to start. +int ToWeek(const civil_day& cd, weekday week_start) { + const civil_day d(cd.year() % 400, cd.month(), cd.day()); + return static_cast<int>((d - prev_weekday(civil_year(d), week_start)) / 7); +} + const char kDigits[] = "0123456789"; // Formats a 64-bit integer in the given field width. Note that it is up @@ -189,7 +216,7 @@ 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 + // the following code grows the buffer size from 2x the format string // length up to 32x. for (std::size_t i = 2; i != 32; i *= 2) { std::size_t buf_size = fmt.size() * i; @@ -290,6 +317,7 @@ const std::int_fast64_t kExp10[kDigits10_64 + 1] = { // - %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) +// - %ET - The RFC3339 "date-time" separator "T" // // The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are // handled internally for performance reasons. strftime(3) is slow due to @@ -354,7 +382,7 @@ std::string format(const std::string& format, const time_point<seconds>& tp, if (cur == end || (cur - percent) % 2 == 0) continue; // Simple specifiers that we handle ourselves. - if (strchr("YmdeHMSzZs%", *cur)) { + if (strchr("YmdeUuWwHMSzZs%", *cur)) { if (cur - 1 != pending) { FormatTM(&result, std::string(pending, cur - 1), tm); } @@ -375,6 +403,22 @@ std::string format(const std::string& format, const time_point<seconds>& tp, if (*cur == 'e' && *bp == '0') *bp = ' '; // for Windows result.append(bp, static_cast<std::size_t>(ep - bp)); break; + case 'U': + bp = Format02d(ep, ToWeek(civil_day(al.cs), weekday::sunday)); + result.append(bp, static_cast<std::size_t>(ep - bp)); + break; + case 'u': + bp = Format64(ep, 0, tm.tm_wday ? tm.tm_wday : 7); + result.append(bp, static_cast<std::size_t>(ep - bp)); + break; + case 'W': + bp = Format02d(ep, ToWeek(civil_day(al.cs), weekday::monday)); + result.append(bp, static_cast<std::size_t>(ep - bp)); + break; + case 'w': + bp = Format64(ep, 0, tm.tm_wday); + 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)); @@ -448,7 +492,14 @@ std::string format(const std::string& format, const time_point<seconds>& tp, if (*cur != 'E' || ++cur == end) continue; // Format our extensions. - if (*cur == 'z') { + if (*cur == 'T') { + // Formats %ET. + if (cur - 2 != pending) { + FormatTM(&result, std::string(pending, cur - 2), tm); + } + result.append("T"); + pending = ++cur; + } else if (*cur == 'z') { // Formats %Ez. if (cur - 2 != pending) { FormatTM(&result, std::string(pending, cur - 2), tm); @@ -551,7 +602,7 @@ const char* ParseOffset(const char* dp, const char* mode, int* offset) { } else { dp = nullptr; } - } else if (first == 'Z') { // Zulu + } else if (first == 'Z' || first == 'z') { // Zulu *offset = 0; } else { dp = nullptr; @@ -602,12 +653,32 @@ const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) { return dp; } +// Sets year, tm_mon and tm_mday given the year, week_num, and tm_wday, +// and the day on which weeks are defined to start. Returns false if year +// would need to move outside its bounds. +bool FromWeek(int week_num, weekday week_start, year_t* year, std::tm* tm) { + const civil_year y(*year % 400); + civil_day cd = prev_weekday(y, week_start); // week 0 + cd = next_weekday(cd - 1, FromTmWday(tm->tm_wday)) + (week_num * 7); + if (const year_t shift = cd.year() - y.year()) { + if (shift > 0) { + if (*year > std::numeric_limits<year_t>::max() - shift) return false; + } else { + if (*year < std::numeric_limits<year_t>::min() - shift) return false; + } + *year += shift; + } + tm->tm_mon = cd.month() - 1; + tm->tm_mday = cd.day(); + return true; +} + } // 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 same inputs. %ET accepts either 'T' or 't'. // // The standard specifiers from RFC3339_* (%Y, %m, %d, %H, %M, and %S) are // handled internally so that we can normally avoid strptime() altogether @@ -651,6 +722,8 @@ bool parse(const std::string& format, const std::string& input, const char* fmt = format.c_str(); // NUL terminated bool twelve_hour = false; bool afternoon = false; + int week_num = -1; + weekday week_start = weekday::sunday; bool saw_percent_s = false; std::int_fast64_t percent_s = 0; @@ -689,10 +762,27 @@ bool parse(const std::string& format, const std::string& input, case 'm': data = ParseInt(data, 2, 1, 12, &tm.tm_mon); if (data != nullptr) tm.tm_mon -= 1; + week_num = -1; continue; case 'd': case 'e': data = ParseInt(data, 2, 1, 31, &tm.tm_mday); + week_num = -1; + continue; + case 'U': + data = ParseInt(data, 0, 0, 53, &week_num); + week_start = weekday::sunday; + continue; + case 'W': + data = ParseInt(data, 0, 0, 53, &week_num); + week_start = weekday::monday; + continue; + case 'u': + data = ParseInt(data, 0, 1, 7, &tm.tm_wday); + if (data != nullptr) tm.tm_wday %= 7; + continue; + case 'w': + data = ParseInt(data, 0, 0, 6, &tm.tm_wday); continue; case 'H': data = ParseInt(data, 2, 0, 23, &tm.tm_hour); @@ -742,6 +832,15 @@ bool parse(const std::string& format, const std::string& input, data = (*data == '%' ? data + 1 : nullptr); continue; case 'E': + if (fmt[0] == 'T') { + if (*data == 'T' || *data == 't') { + ++data; + ++fmt; + } else { + data = nullptr; + } + continue; + } if (fmt[0] == 'z' || (fmt[0] == '*' && fmt[1] == 'z')) { data = ParseOffset(data, ":", &offset); if (data != nullptr) saw_offset = true; @@ -839,7 +938,7 @@ bool parse(const std::string& format, const std::string& input, // Skip any remaining whitespace. while (std::isspace(*data)) ++data; - // parse() must consume the entire input std::string. + // parse() must consume the entire input string. if (*data != '\0') { if (err != nullptr) *err = "Illegal trailing data in input string"; return false; @@ -874,6 +973,14 @@ bool parse(const std::string& format, const std::string& input, year += 1900; } + // Compute year, tm.tm_mon and tm.tm_mday if we parsed a week number. + if (week_num != -1) { + if (!FromWeek(week_num, week_start, &year, &tm)) { + if (err != nullptr) *err = "Out-of-range field"; + return false; + } + } + const int month = tm.tm_mon + 1; civil_second cs(year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format_test.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format_test.cc index caebcc4d95..6487fa9373 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_format_test.cc @@ -13,6 +13,7 @@ // limitations under the License. #include <chrono> +#include <cstdint> #include <iomanip> #include <sstream> #include <string> @@ -48,8 +49,8 @@ namespace { EXPECT_STREQ(zone, al.abbr); \ } while (0) -const char RFC3339_full[] = "%Y-%m-%dT%H:%M:%E*S%Ez"; -const char RFC3339_sec[] = "%Y-%m-%dT%H:%M:%S%Ez"; +const char RFC3339_full[] = "%Y-%m-%d%ET%H:%M:%E*S%Ez"; +const char RFC3339_sec[] = "%Y-%m-%d%ET%H:%M:%S%Ez"; const char RFC1123_full[] = "%a, %d %b %Y %H:%M:%S %z"; const char RFC1123_no_wday[] = "%d %b %Y %H:%M:%S %z"; @@ -679,6 +680,34 @@ TEST(Format, RFC1123Format) { // locale specific EXPECT_EQ("28 Jun 1977 09:08:07 -0700", format(RFC1123_no_wday, tp, tz)); } +TEST(Format, Week) { + const time_zone utc = utc_time_zone(); + + auto tp = convert(civil_second(2017, 1, 1, 0, 0, 0), utc); + EXPECT_EQ("2017-01-7", format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2017-00-0", format("%Y-%W-%w", tp, utc)); + + tp = convert(civil_second(2017, 12, 31, 0, 0, 0), utc); + EXPECT_EQ("2017-53-7", format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2017-52-0", format("%Y-%W-%w", tp, utc)); + + tp = convert(civil_second(2018, 1, 1, 0, 0, 0), utc); + EXPECT_EQ("2018-00-1", format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2018-01-1", format("%Y-%W-%w", tp, utc)); + + tp = convert(civil_second(2018, 12, 31, 0, 0, 0), utc); + EXPECT_EQ("2018-52-1", format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2018-53-1", format("%Y-%W-%w", tp, utc)); + + tp = convert(civil_second(2019, 1, 1, 0, 0, 0), utc); + EXPECT_EQ("2019-00-2", format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2019-00-2", format("%Y-%W-%w", tp, utc)); + + tp = convert(civil_second(2019, 12, 31, 0, 0, 0), utc); + EXPECT_EQ("2019-52-2", format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2019-52-2", format("%Y-%W-%w", tp, utc)); +} + // // Testing parse() // @@ -767,7 +796,7 @@ TEST(Parse, WithTimeZone) { EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); time_point<chrono::nanoseconds> tp; - // We can parse a std::string without a UTC offset if we supply a timezone. + // We can parse a string without a UTC offset if we supply a timezone. EXPECT_TRUE(parse("%Y-%m-%d %H:%M:%S", "2013-06-28 19:08:09", tz, &tp)); ExpectTime(tp, tz, 2013, 6, 28, 19, 8, 9, -7 * 60 * 60, true, "PDT"); @@ -1107,7 +1136,7 @@ TEST(Parse, ExtendedSeconds) { // All %E<prec>S cases are treated the same as %E*S on input. auto precisions = {"*", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"}; - for (const std::string& prec : precisions) { + for (const std::string prec : precisions) { const std::string fmt = "%E" + prec + "S"; SCOPED_TRACE(fmt); time_point<chrono::nanoseconds> tp = unix_epoch; @@ -1189,7 +1218,7 @@ TEST(Parse, ExtendedSubeconds) { // All %E<prec>f cases are treated the same as %E*f on input. auto precisions = {"*", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"}; - for (const std::string& prec : precisions) { + for (const std::string prec : precisions) { const std::string fmt = "%E" + prec + "f"; SCOPED_TRACE(fmt); time_point<chrono::nanoseconds> tp = unix_epoch - chrono::seconds(1); @@ -1379,10 +1408,85 @@ TEST(Parse, RFC3339Format) { EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00+00:00", tz, &tp)); ExpectTime(tp, tz, 2014, 2, 12, 20, 21, 0, 0, false, "UTC"); - // Check that %Ez also accepts "Z" as a synonym for "+00:00". + // Check that %ET also accepts "t". time_point<chrono::nanoseconds> tp2; - EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp2)); + EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12t20:21:00+00:00", tz, &tp2)); EXPECT_EQ(tp, tp2); + + // Check that %Ez also accepts "Z" as a synonym for "+00:00". + time_point<chrono::nanoseconds> tp3; + EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00Z", tz, &tp3)); + EXPECT_EQ(tp, tp3); + + // Check that %Ez also accepts "z" as a synonym for "+00:00". + time_point<chrono::nanoseconds> tp4; + EXPECT_TRUE(parse(RFC3339_sec, "2014-02-12T20:21:00z", tz, &tp4)); + EXPECT_EQ(tp, tp4); +} + +TEST(Parse, Week) { + const time_zone utc = utc_time_zone(); + time_point<absl::time_internal::cctz::seconds> tp; + + auto exp = convert(civil_second(2017, 1, 1, 0, 0, 0), utc); + EXPECT_TRUE(parse("%Y-%U-%u", "2017-01-7", utc, &tp)); + EXPECT_EQ(exp, tp); + EXPECT_TRUE(parse("%Y-%W-%w", "2017-00-0", utc, &tp)); + EXPECT_EQ(exp, tp); + + exp = convert(civil_second(2017, 12, 31, 0, 0, 0), utc); + EXPECT_TRUE(parse("%Y-%U-%u", "2017-53-7", utc, &tp)); + EXPECT_EQ(exp, tp); + EXPECT_TRUE(parse("%Y-%W-%w", "2017-52-0", utc, &tp)); + EXPECT_EQ(exp, tp); + + exp = convert(civil_second(2018, 1, 1, 0, 0, 0), utc); + EXPECT_TRUE(parse("%Y-%U-%u", "2018-00-1", utc, &tp)); + EXPECT_EQ(exp, tp); + EXPECT_TRUE(parse("%Y-%W-%w", "2018-01-1", utc, &tp)); + EXPECT_EQ(exp, tp); + + exp = convert(civil_second(2018, 12, 31, 0, 0, 0), utc); + EXPECT_TRUE(parse("%Y-%U-%u", "2018-52-1", utc, &tp)); + EXPECT_EQ(exp, tp); + EXPECT_TRUE(parse("%Y-%W-%w", "2018-53-1", utc, &tp)); + EXPECT_EQ(exp, tp); + + exp = convert(civil_second(2019, 1, 1, 0, 0, 0), utc); + EXPECT_TRUE(parse("%Y-%U-%u", "2019-00-2", utc, &tp)); + EXPECT_EQ(exp, tp); + EXPECT_TRUE(parse("%Y-%W-%w", "2019-00-2", utc, &tp)); + EXPECT_EQ(exp, tp); + + exp = convert(civil_second(2019, 12, 31, 0, 0, 0), utc); + EXPECT_TRUE(parse("%Y-%U-%u", "2019-52-2", utc, &tp)); + EXPECT_EQ(exp, tp); + EXPECT_TRUE(parse("%Y-%W-%w", "2019-52-2", utc, &tp)); + EXPECT_EQ(exp, tp); +} + +TEST(Parse, WeekYearShift) { + // %U/%W conversions with week values in {0, 52, 53} can slip + // into the previous/following calendar years. + const time_zone utc = utc_time_zone(); + time_point<absl::time_internal::cctz::seconds> tp; + + auto exp = convert(civil_second(2019, 12, 31, 0, 0, 0), utc); + EXPECT_TRUE(parse("%Y-%U-%u", "2020-00-2", utc, &tp)); + EXPECT_EQ(exp, tp); + EXPECT_TRUE(parse("%Y-%W-%w", "2020-00-2", utc, &tp)); + EXPECT_EQ(exp, tp); + + exp = convert(civil_second(2021, 1, 1, 0, 0, 0), utc); + EXPECT_TRUE(parse("%Y-%U-%u", "2020-52-5", utc, &tp)); + EXPECT_EQ(exp, tp); + EXPECT_TRUE(parse("%Y-%W-%w", "2020-52-5", utc, &tp)); + EXPECT_EQ(exp, tp); + + // Slipping into the previous/following calendar years should fail when + // we're already at the extremes. + EXPECT_FALSE(parse("%Y-%U-%u", "-9223372036854775808-0-7", utc, &tp)); + EXPECT_FALSE(parse("%Y-%U-%u", "9223372036854775807-53-7", utc, &tp)); } TEST(Parse, MaxRange) { @@ -1401,7 +1505,7 @@ TEST(Parse, MaxRange) { parse(RFC3339_sec, "292277026596-12-04T14:30:07-01:00", utc, &tp)); EXPECT_EQ(tp, time_point<absl::time_internal::cctz::seconds>::max()); EXPECT_FALSE( - parse(RFC3339_sec, "292277026596-12-04T15:30:07-01:00", utc, &tp)); + parse(RFC3339_sec, "292277026596-12-04T14:30:08-01:00", utc, &tp)); // tests the lower limit using +00:00 offset EXPECT_TRUE( @@ -1422,10 +1526,82 @@ TEST(Parse, MaxRange) { parse(RFC3339_sec, "9223372036854775807-12-31T23:59:59-00:01", utc, &tp)); EXPECT_FALSE(parse(RFC3339_sec, "-9223372036854775808-01-01T00:00:00+00:01", utc, &tp)); +} + +TEST(Parse, TimePointOverflow) { + const time_zone utc = utc_time_zone(); - // TODO: Add tests that parsing times with fractional seconds overflow - // appropriately. This can't be done until cctz::parse() properly detects - // overflow when combining the chrono seconds and femto. + using D = chrono::duration<std::int64_t, std::nano>; + time_point<D> tp; + + EXPECT_TRUE( + parse(RFC3339_full, "2262-04-11T23:47:16.8547758079+00:00", utc, &tp)); + EXPECT_EQ(tp, time_point<D>::max()); + EXPECT_EQ("2262-04-11T23:47:16.854775807+00:00", + format(RFC3339_full, tp, utc)); +#if 0 + // TODO(#199): Will fail until cctz::parse() properly detects overflow. + EXPECT_FALSE( + parse(RFC3339_full, "2262-04-11T23:47:16.8547758080+00:00", utc, &tp)); + EXPECT_TRUE( + parse(RFC3339_full, "1677-09-21T00:12:43.1452241920+00:00", utc, &tp)); + EXPECT_EQ(tp, time_point<D>::min()); + EXPECT_EQ("1677-09-21T00:12:43.145224192+00:00", + format(RFC3339_full, tp, utc)); + EXPECT_FALSE( + parse(RFC3339_full, "1677-09-21T00:12:43.1452241919+00:00", utc, &tp)); +#endif + + using DS = chrono::duration<std::int8_t, chrono::seconds::period>; + time_point<DS> stp; + + EXPECT_TRUE(parse(RFC3339_full, "1970-01-01T00:02:07.9+00:00", utc, &stp)); + EXPECT_EQ(stp, time_point<DS>::max()); + EXPECT_EQ("1970-01-01T00:02:07+00:00", format(RFC3339_full, stp, utc)); + EXPECT_FALSE(parse(RFC3339_full, "1970-01-01T00:02:08+00:00", utc, &stp)); + + EXPECT_TRUE(parse(RFC3339_full, "1969-12-31T23:57:52+00:00", utc, &stp)); + EXPECT_EQ(stp, time_point<DS>::min()); + EXPECT_EQ("1969-12-31T23:57:52+00:00", format(RFC3339_full, stp, utc)); + EXPECT_FALSE(parse(RFC3339_full, "1969-12-31T23:57:51.9+00:00", utc, &stp)); + + using DM = chrono::duration<std::int8_t, chrono::minutes::period>; + time_point<DM> mtp; + + EXPECT_TRUE(parse(RFC3339_full, "1970-01-01T02:07:59+00:00", utc, &mtp)); + EXPECT_EQ(mtp, time_point<DM>::max()); + EXPECT_EQ("1970-01-01T02:07:00+00:00", format(RFC3339_full, mtp, utc)); + EXPECT_FALSE(parse(RFC3339_full, "1970-01-01T02:08:00+00:00", utc, &mtp)); + + EXPECT_TRUE(parse(RFC3339_full, "1969-12-31T21:52:00+00:00", utc, &mtp)); + EXPECT_EQ(mtp, time_point<DM>::min()); + EXPECT_EQ("1969-12-31T21:52:00+00:00", format(RFC3339_full, mtp, utc)); + EXPECT_FALSE(parse(RFC3339_full, "1969-12-31T21:51:59+00:00", utc, &mtp)); +} + +TEST(Parse, TimePointOverflowFloor) { + const time_zone utc = utc_time_zone(); + + using D = chrono::duration<std::int64_t, std::micro>; + time_point<D> tp; + + EXPECT_TRUE( + parse(RFC3339_full, "294247-01-10T04:00:54.7758079+00:00", utc, &tp)); + EXPECT_EQ(tp, time_point<D>::max()); + EXPECT_EQ("294247-01-10T04:00:54.775807+00:00", + format(RFC3339_full, tp, utc)); +#if 0 + // TODO(#199): Will fail until cctz::parse() properly detects overflow. + EXPECT_FALSE( + parse(RFC3339_full, "294247-01-10T04:00:54.7758080+00:00", utc, &tp)); + EXPECT_TRUE( + parse(RFC3339_full, "-290308-12-21T19:59:05.2241920+00:00", utc, &tp)); + EXPECT_EQ(tp, time_point<D>::min()); + EXPECT_EQ("-290308-12-21T19:59:05.224192+00:00", + format(RFC3339_full, tp, utc)); + EXPECT_FALSE( + parse(RFC3339_full, "-290308-12-21T19:59:05.2241919+00:00", utc, &tp)); +#endif } // diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_if.h b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_if.h index 32c0891c1e..7d3e42d3cd 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_if.h +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_if.h @@ -56,7 +56,8 @@ class TimeZoneIf { // Convert between time_point<seconds> and a count of seconds since the // Unix epoch. We assume that the std::chrono::system_clock and the -// Unix clock are second aligned, but not that they share an epoch. +// Unix clock are second aligned, and that the results are representable. +// (That is, that they share an epoch, which is required since C++20.) inline std::int_fast64_t ToUnixSeconds(const time_point<seconds>& tp) { return (tp - std::chrono::time_point_cast<seconds>( std::chrono::system_clock::from_time_t(0))) diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.cc index 030ae0e19e..f34e3aec84 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.cc @@ -15,6 +15,7 @@ #include "time_zone_impl.h" #include <deque> +#include <memory> #include <mutex> #include <string> #include <unordered_map> @@ -48,17 +49,16 @@ std::mutex& TimeZoneMutex() { time_zone time_zone::Impl::UTC() { return time_zone(UTCImpl()); } bool time_zone::Impl::LoadTimeZone(const std::string& name, time_zone* tz) { - const time_zone::Impl* const utc_impl = UTCImpl(); + const Impl* const utc_impl = UTCImpl(); - // First check for UTC (which is never a key in time_zone_map). + // Check for UTC (which is never a key in time_zone_map). auto offset = seconds::zero(); if (FixedOffsetFromName(name, &offset) && offset == seconds::zero()) { *tz = time_zone(utc_impl); return true; } - // Then check, under a shared lock, whether the time zone has already - // been loaded. This is the common path. TODO: Move to shared_mutex. + // Check whether the time zone has already been loaded. { std::lock_guard<std::mutex> lock(TimeZoneMutex()); if (time_zone_map != nullptr) { @@ -70,20 +70,15 @@ bool time_zone::Impl::LoadTimeZone(const std::string& name, time_zone* tz) { } } - // Now check again, under an exclusive lock. + // Load the new time zone (outside the lock). + std::unique_ptr<const Impl> new_impl(new Impl(name)); + + // 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; const Impl*& impl = (*time_zone_map)[name]; - if (impl == nullptr) { - // The first thread in loads the new time zone. - Impl* new_impl = new Impl(name); - new_impl->zone_ = TimeZoneIf::Load(new_impl->name_); - if (new_impl->zone_ == nullptr) { - delete new_impl; // free the nascent Impl - impl = utc_impl; // and fallback to UTC - } else { - impl = new_impl; // install new time zone - } + if (impl == nullptr) { // this thread won any load race + impl = new_impl->zone_ ? new_impl.release() : utc_impl; } *tz = time_zone(impl); return impl != utc_impl; @@ -104,14 +99,11 @@ void time_zone::Impl::ClearTimeZoneMapTestOnly() { } } -time_zone::Impl::Impl(const std::string& name) : name_(name) {} +time_zone::Impl::Impl(const std::string& name) + : name_(name), zone_(TimeZoneIf::Load(name_)) {} const time_zone::Impl* time_zone::Impl::UTCImpl() { - static Impl* utc_impl = [] { - Impl* impl = new Impl("UTC"); - impl->zone_ = TimeZoneIf::Load(impl->name_); // never fails - return impl; - }(); + static const Impl* utc_impl = new Impl("UTC"); // never fails return utc_impl; } diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.h b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.h index 69806c10d9..7d747ba966 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.h +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_impl.h @@ -71,7 +71,7 @@ class time_zone::Impl { return zone_->PrevTransition(tp, trans); } - // Returns an implementation-defined version std::string for this time zone. + // Returns an implementation-defined version string for this time zone. std::string Version() const { return zone_->Version(); } // Returns an implementation-defined description of this time zone. diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.cc index f1697cdf04..4f175d95fc 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.cc @@ -39,11 +39,12 @@ #include <cstdio> #include <cstdlib> #include <cstring> +#include <fstream> #include <functional> -#include <iostream> #include <memory> #include <sstream> #include <string> +#include <utility> #include "absl/base/config.h" #include "absl/time/internal/cctz/include/cctz/civil_time.h" @@ -83,6 +84,27 @@ const std::int_least32_t kSecsPerYear[2] = { 366 * kSecsPerDay, }; +// Convert a cctz::weekday to a POSIX TZ weekday number (0==Sun, ..., 6=Sat). +inline int ToPosixWeekday(weekday wd) { + switch (wd) { + case weekday::sunday: + return 0; + case weekday::monday: + return 1; + case weekday::tuesday: + return 2; + case weekday::wednesday: + return 3; + case weekday::thursday: + return 4; + case weekday::friday: + return 5; + case weekday::saturday: + return 6; + } + return 0; /*NOTREACHED*/ +} + // Single-byte, unsigned numeric values are encoded directly. inline std::uint_fast8_t Decode8(const char* cp) { return static_cast<std::uint_fast8_t>(*cp) & 0xff; @@ -188,15 +210,13 @@ bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) { tt.is_dst = false; tt.abbr_index = 0; - // We temporarily add some redundant, contemporary (2013 through 2023) + // We temporarily add some redundant, contemporary (2015 through 2025) // transitions for performance reasons. See TimeZoneInfo::LocalTime(). // TODO: Fix the performance issue and remove the extra transitions. transitions_.clear(); transitions_.reserve(12); for (const std::int_fast64_t unix_time : { - -(1LL << 59), // BIG_BANG - 1356998400LL, // 2013-01-01T00:00:00+00:00 - 1388534400LL, // 2014-01-01T00:00:00+00:00 + -(1LL << 59), // a "first half" transition 1420070400LL, // 2015-01-01T00:00:00+00:00 1451606400LL, // 2016-01-01T00:00:00+00:00 1483228800LL, // 2017-01-01T00:00:00+00:00 @@ -206,7 +226,8 @@ bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) { 1609459200LL, // 2021-01-01T00:00:00+00:00 1640995200LL, // 2022-01-01T00:00:00+00:00 1672531200LL, // 2023-01-01T00:00:00+00:00 - 2147483647LL, // 2^31 - 1 + 1704067200LL, // 2024-01-01T00:00:00+00:00 + 1735689600LL, // 2025-01-01T00:00:00+00:00 }) { Transition& tr(*transitions_.emplace(transitions_.end())); tr.unix_time = unix_time; @@ -217,8 +238,8 @@ bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) { default_transition_type_ = 0; abbreviations_ = FixedOffsetToAbbr(offset); - abbreviations_.append(1, '\0'); // add NUL - future_spec_.clear(); // never needed for a fixed-offset zone + abbreviations_.append(1, '\0'); + future_spec_.clear(); // never needed for a fixed-offset zone extended_ = false; tt.civil_max = LocalTime(seconds::max().count(), tt).cs; @@ -259,21 +280,6 @@ std::size_t TimeZoneInfo::Header::DataLength(std::size_t time_len) const { return len; } -// Check that the TransitionType has the expected offset/is_dst/abbreviation. -void TimeZoneInfo::CheckTransition(const std::string& name, - const TransitionType& tt, - std::int_fast32_t offset, bool is_dst, - const std::string& abbr) const { - if (tt.utc_offset != offset || tt.is_dst != is_dst || - &abbreviations_[tt.abbr_index] != abbr) { - std::clog << name << ": Transition" - << " offset=" << tt.utc_offset << "/" - << (tt.is_dst ? "DST" : "STD") - << "/abbr=" << &abbreviations_[tt.abbr_index] - << " does not match POSIX spec '" << future_spec_ << "'\n"; - } -} - // zic(8) can generate no-op transitions when a zone changes rules at an // instant when there is actually no discontinuity. So we check whether // two transitions have equivalent types (same offset/is_dst/abbr). @@ -282,117 +288,108 @@ bool TimeZoneInfo::EquivTransitions(std::uint_fast8_t tt1_index, if (tt1_index == tt2_index) return true; const TransitionType& tt1(transition_types_[tt1_index]); const TransitionType& tt2(transition_types_[tt2_index]); - if (tt1.is_dst != tt2.is_dst) return false; if (tt1.utc_offset != tt2.utc_offset) return false; + if (tt1.is_dst != tt2.is_dst) return false; if (tt1.abbr_index != tt2.abbr_index) return false; return true; } +// Find/make a transition type with these attributes. +bool TimeZoneInfo::GetTransitionType(std::int_fast32_t utc_offset, bool is_dst, + const std::string& abbr, + std::uint_least8_t* index) { + std::size_t type_index = 0; + std::size_t abbr_index = abbreviations_.size(); + for (; type_index != transition_types_.size(); ++type_index) { + const TransitionType& tt(transition_types_[type_index]); + const char* tt_abbr = &abbreviations_[tt.abbr_index]; + if (tt_abbr == abbr) abbr_index = tt.abbr_index; + if (tt.utc_offset == utc_offset && tt.is_dst == is_dst) { + if (abbr_index == tt.abbr_index) break; // reuse + } + } + if (type_index > 255 || abbr_index > 255) { + // No index space (8 bits) available for a new type or abbreviation. + return false; + } + if (type_index == transition_types_.size()) { + TransitionType& tt(*transition_types_.emplace(transition_types_.end())); + tt.utc_offset = static_cast<std::int_least32_t>(utc_offset); + tt.is_dst = is_dst; + if (abbr_index == abbreviations_.size()) { + abbreviations_.append(abbr); + abbreviations_.append(1, '\0'); + } + tt.abbr_index = static_cast<std::uint_least8_t>(abbr_index); + } + *index = static_cast<std::uint_least8_t>(type_index); + return true; +} + // Use the POSIX-TZ-environment-variable-style string to handle times // in years after the last transition stored in the zoneinfo data. -void TimeZoneInfo::ExtendTransitions(const std::string& name, - const Header& hdr) { +bool TimeZoneInfo::ExtendTransitions() { extended_ = false; - bool extending = !future_spec_.empty(); + if (future_spec_.empty()) return true; // last transition prevails PosixTimeZone posix; - if (extending && !ParsePosixSpec(future_spec_, &posix)) { - std::clog << name << ": Failed to parse '" << future_spec_ << "'\n"; - extending = false; - } - - if (extending && posix.dst_abbr.empty()) { // std only - // The future specification should match the last/default transition, - // and that means that handling the future will fall out naturally. - std::uint_fast8_t index = default_transition_type_; - if (hdr.timecnt != 0) index = transitions_[hdr.timecnt - 1].type_index; - const TransitionType& tt(transition_types_[index]); - CheckTransition(name, tt, posix.std_offset, false, posix.std_abbr); - extending = false; - } - - if (extending && hdr.timecnt < 2) { - std::clog << name << ": Too few transitions for POSIX spec\n"; - extending = false; - } - - if (!extending) { - // Ensure that there is always a transition in the second half of the - // time line (the BIG_BANG transition is in the first half) so that the - // signed difference between a civil_second and the civil_second of its - // previous transition is always representable, without overflow. - const Transition& last(transitions_.back()); - if (last.unix_time < 0) { - 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 - tr.type_index = type_index; - } - return; // last transition wins + if (!ParsePosixSpec(future_spec_, &posix)) return false; + + // Find transition type for the future std specification. + std::uint_least8_t std_ti; + if (!GetTransitionType(posix.std_offset, false, posix.std_abbr, &std_ti)) + return false; + + if (posix.dst_abbr.empty()) { // std only + // The future specification should match the last transition, and + // that means that handling the future will fall out naturally. + return EquivTransitions(transitions_.back().type_index, std_ti); } + // Find transition type for the future dst specification. + std::uint_least8_t dst_ti; + if (!GetTransitionType(posix.dst_offset, true, posix.dst_abbr, &dst_ti)) + return false; + // Extend the transitions for an additional 400 years using the // future specification. Years beyond those can be handled by // mapping back to a cycle-equivalent year within that range. - // zic(8) should probably do this so that we don't have to. - // TODO: Reduce the extension by the number of compatible - // transitions already in place. - transitions_.reserve(hdr.timecnt + 400 * 2 + 1); - transitions_.resize(hdr.timecnt + 400 * 2); + // We may need two additional transitions for the current year. + transitions_.reserve(transitions_.size() + 400 * 2 + 2); extended_ = true; - // The future specification should match the last two transitions, - // and those transitions should have different is_dst flags. Note - // that nothing says the UTC offset used by the is_dst transition - // must be greater than that used by the !is_dst transition. (See - // Europe/Dublin, for example.) - const Transition* tr0 = &transitions_[hdr.timecnt - 1]; - const Transition* tr1 = &transitions_[hdr.timecnt - 2]; - const TransitionType* tt0 = &transition_types_[tr0->type_index]; - const TransitionType* tt1 = &transition_types_[tr1->type_index]; - const TransitionType& dst(tt0->is_dst ? *tt0 : *tt1); - const TransitionType& std(tt0->is_dst ? *tt1 : *tt0); - CheckTransition(name, dst, posix.dst_offset, true, posix.dst_abbr); - CheckTransition(name, std, posix.std_offset, false, posix.std_abbr); - - // Add the transitions to tr1 and back to tr0 for each extra year. - last_year_ = LocalTime(tr0->unix_time, *tt0).cs.year(); + const Transition& last(transitions_.back()); + const std::int_fast64_t last_time = last.unix_time; + const TransitionType& last_tt(transition_types_[last.type_index]); + last_year_ = LocalTime(last_time, last_tt).cs.year(); bool leap_year = IsLeap(last_year_); - const civil_day jan1(last_year_, 1, 1); - std::int_fast64_t jan1_time = civil_second(jan1) - civil_second(); - int jan1_weekday = (static_cast<int>(get_weekday(jan1)) + 1) % 7; - Transition* tr = &transitions_[hdr.timecnt]; // next trans to fill - if (LocalTime(tr1->unix_time, *tt1).cs.year() != last_year_) { - // Add a single extra transition to align to a calendar year. - transitions_.resize(transitions_.size() + 1); - assert(tr == &transitions_[hdr.timecnt]); // no reallocation - const PosixTransition& pt1(tt0->is_dst ? posix.dst_end : posix.dst_start); - std::int_fast64_t tr1_offset = TransOffset(leap_year, jan1_weekday, pt1); - tr->unix_time = jan1_time + tr1_offset - tt0->utc_offset; - tr++->type_index = tr1->type_index; - tr0 = &transitions_[hdr.timecnt]; - tr1 = &transitions_[hdr.timecnt - 1]; - tt0 = &transition_types_[tr0->type_index]; - tt1 = &transition_types_[tr1->type_index]; - } - const PosixTransition& pt1(tt0->is_dst ? posix.dst_end : posix.dst_start); - const PosixTransition& pt0(tt0->is_dst ? posix.dst_start : posix.dst_end); - for (const year_t limit = last_year_ + 400; last_year_ < limit;) { - last_year_ += 1; // an additional year of generated transitions + const civil_second jan1(last_year_); + std::int_fast64_t jan1_time = jan1 - civil_second(); + int jan1_weekday = ToPosixWeekday(get_weekday(jan1)); + + Transition dst = {0, dst_ti, civil_second(), civil_second()}; + Transition std = {0, std_ti, civil_second(), civil_second()}; + for (const year_t limit = last_year_ + 400;; ++last_year_) { + auto dst_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_start); + auto std_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_end); + dst.unix_time = jan1_time + dst_trans_off - posix.std_offset; + std.unix_time = jan1_time + std_trans_off - posix.dst_offset; + const auto* ta = dst.unix_time < std.unix_time ? &dst : &std; + const auto* tb = dst.unix_time < std.unix_time ? &std : &dst; + if (last_time < tb->unix_time) { + if (last_time < ta->unix_time) transitions_.push_back(*ta); + transitions_.push_back(*tb); + } + if (last_year_ == limit) break; jan1_time += kSecsPerYear[leap_year]; jan1_weekday = (jan1_weekday + kDaysPerYear[leap_year]) % 7; - leap_year = !leap_year && IsLeap(last_year_); - std::int_fast64_t tr1_offset = TransOffset(leap_year, jan1_weekday, pt1); - tr->unix_time = jan1_time + tr1_offset - tt0->utc_offset; - tr++->type_index = tr1->type_index; - std::int_fast64_t tr0_offset = TransOffset(leap_year, jan1_weekday, pt0); - tr->unix_time = jan1_time + tr0_offset - tt1->utc_offset; - tr++->type_index = tr0->type_index; - } - assert(tr == &transitions_[0] + transitions_.size()); + leap_year = !leap_year && IsLeap(last_year_ + 1); + } + + return true; } -bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) { +bool TimeZoneInfo::Load(ZoneInfoSource* zip) { // Read and validate the header. tzhead tzh; if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh)) return false; @@ -430,7 +427,7 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) { const char* bp = tbuf.data(); // Decode and validate the transitions. - transitions_.reserve(hdr.timecnt + 2); // We might add a couple. + transitions_.reserve(hdr.timecnt + 2); transitions_.resize(hdr.timecnt); for (std::size_t i = 0; i != hdr.timecnt; ++i) { transitions_[i].unix_time = (time_len == 4) ? Decode32(bp) : Decode64(bp); @@ -449,6 +446,7 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) { } // Decode and validate the transition types. + transition_types_.reserve(hdr.typecnt + 2); transition_types_.resize(hdr.typecnt); for (std::size_t i = 0; i != hdr.typecnt; ++i) { transition_types_[i].utc_offset = @@ -475,6 +473,7 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) { } // Copy all the abbreviations. + abbreviations_.reserve(hdr.charcnt + 10); abbreviations_.assign(bp, hdr.charcnt); bp += hdr.charcnt; @@ -506,7 +505,7 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) { // If we did not find version information during the standard loading // process (as of tzh_version '3' that is unsupported), then ask the - // ZoneInfoSource for any out-of-bound version std::string it may be privy to. + // ZoneInfoSource for any out-of-bound version string it may be privy to. if (version_.empty()) { version_ = zip->Version(); } @@ -525,19 +524,29 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) { transitions_.resize(hdr.timecnt); // Ensure that there is always a transition in the first half of the - // time line (the second half is handled in ExtendTransitions()) so that - // the signed difference between a civil_second and the civil_second of - // its previous transition is always representable, without overflow. - // A contemporary zic will usually have already done this for us. + // time line (the second half is handled below) so that the signed + // difference between a civil_second and the civil_second of its + // previous transition is always representable, without overflow. if (transitions_.empty() || transitions_.front().unix_time >= 0) { Transition& tr(*transitions_.emplace(transitions_.begin())); - tr.unix_time = -(1LL << 59); // see tz/zic.c "BIG_BANG" + tr.unix_time = -(1LL << 59); // -18267312070-10-26T17:01:52+00:00 tr.type_index = default_transition_type_; - hdr.timecnt += 1; } // Extend the transitions using the future specification. - ExtendTransitions(name, hdr); + if (!ExtendTransitions()) return false; + + // Ensure that there is always a transition in the second half of the + // time line (the first half is handled above) so that the signed + // difference between a civil_second and the civil_second of its + // previous transition is always representable, without overflow. + const Transition& last(transitions_.back()); + if (last.unix_time < 0) { + 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 + tr.type_index = type_index; + } // Compute the local civil time for each transition and the preceding // second. These will be used for reverse conversions in MakeTime(). @@ -569,14 +578,17 @@ bool TimeZoneInfo::Load(const std::string& name, ZoneInfoSource* zip) { namespace { +using FilePtr = std::unique_ptr<FILE, int (*)(FILE*)>; + // fopen(3) adaptor. -inline FILE* FOpen(const char* path, const char* mode) { +inline FilePtr FOpen(const char* path, const char* mode) { #if defined(_MSC_VER) FILE* fp; if (fopen_s(&fp, path, mode) != 0) fp = nullptr; - return fp; + return FilePtr(fp, fclose); #else - return fopen(path, mode); // TODO: Enable the close-on-exec flag. + // TODO: Enable the close-on-exec flag. + return FilePtr(fopen(path, mode), fclose); #endif } @@ -604,11 +616,11 @@ class FileZoneInfoSource : public ZoneInfoSource { protected: explicit FileZoneInfoSource( - FILE* fp, std::size_t len = std::numeric_limits<std::size_t>::max()) - : fp_(fp, fclose), len_(len) {} + FilePtr fp, std::size_t len = std::numeric_limits<std::size_t>::max()) + : fp_(std::move(fp)), len_(len) {} private: - std::unique_ptr<FILE, int (*)(FILE*)> fp_; + FilePtr fp_; std::size_t len_; }; @@ -637,17 +649,9 @@ std::unique_ptr<ZoneInfoSource> FileZoneInfoSource::Open( path.append(name, pos, std::string::npos); // Open the zoneinfo file. - FILE* fp = FOpen(path.c_str(), "rb"); + auto fp = FOpen(path.c_str(), "rb"); if (fp == nullptr) return nullptr; - std::size_t length = 0; - if (fseek(fp, 0, SEEK_END) == 0) { - long offset = ftell(fp); - if (offset >= 0) { - length = static_cast<std::size_t>(offset); - } - rewind(fp); - } - return std::unique_ptr<ZoneInfoSource>(new FileZoneInfoSource(fp, length)); + return std::unique_ptr<ZoneInfoSource>(new FileZoneInfoSource(std::move(fp))); } class AndroidZoneInfoSource : public FileZoneInfoSource { @@ -656,8 +660,9 @@ class AndroidZoneInfoSource : public FileZoneInfoSource { std::string Version() const override { return version_; } private: - explicit AndroidZoneInfoSource(FILE* fp, std::size_t len, const char* vers) - : FileZoneInfoSource(fp, len), version_(vers) {} + explicit AndroidZoneInfoSource(FilePtr fp, std::size_t len, + std::string version) + : FileZoneInfoSource(std::move(fp), len), version_(std::move(version)) {} std::string version_; }; @@ -669,8 +674,8 @@ std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open( // See Android's libc/tzcode/bionic.cpp for additional information. for (const char* tzdata : {"/data/misc/zoneinfo/current/tzdata", "/system/usr/share/zoneinfo/tzdata"}) { - std::unique_ptr<FILE, int (*)(FILE*)> fp(FOpen(tzdata, "rb"), fclose); - if (fp.get() == nullptr) continue; + auto fp = FOpen(tzdata, "rb"); + if (fp == nullptr) continue; char hbuf[24]; // covers header.zonetab_offset too if (fread(hbuf, 1, sizeof(hbuf), fp.get()) != sizeof(hbuf)) continue; @@ -696,7 +701,7 @@ std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open( if (strcmp(name.c_str() + pos, ebuf) == 0) { if (fseek(fp.get(), static_cast<long>(start), SEEK_SET) != 0) break; return std::unique_ptr<ZoneInfoSource>(new AndroidZoneInfoSource( - fp.release(), static_cast<std::size_t>(length), vers)); + std::move(fp), static_cast<std::size_t>(length), vers)); } } } @@ -704,6 +709,69 @@ std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open( return nullptr; } +// A zoneinfo source for use inside Fuchsia components. This attempts to +// read zoneinfo files from one of several known paths in a component's +// incoming namespace. [Config data][1] is preferred, but package-specific +// resources are also supported. +// +// Fuchsia's implementation supports `FileZoneInfoSource::Version()`. +// +// [1]: +// https://fuchsia.dev/fuchsia-src/development/components/data#using_config_data_in_your_component +class FuchsiaZoneInfoSource : public FileZoneInfoSource { + public: + static std::unique_ptr<ZoneInfoSource> Open(const std::string& name); + std::string Version() const override { return version_; } + + private: + explicit FuchsiaZoneInfoSource(FilePtr fp, std::string version) + : FileZoneInfoSource(std::move(fp)), version_(std::move(version)) {} + std::string version_; +}; + +std::unique_ptr<ZoneInfoSource> FuchsiaZoneInfoSource::Open( + const std::string& name) { + // Use of the "file:" prefix is intended for testing purposes only. + const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0; + + // Prefixes where a Fuchsia component might find zoneinfo files, + // in descending order of preference. + const auto kTzdataPrefixes = { + "/config/data/tzdata/", + "/pkg/data/tzdata/", + "/data/tzdata/", + }; + const auto kEmptyPrefix = {""}; + const bool name_absolute = (pos != name.size() && name[pos] == '/'); + const auto prefixes = name_absolute ? kEmptyPrefix : kTzdataPrefixes; + + // Fuchsia builds place zoneinfo files at "<prefix><format><name>". + for (const std::string prefix : prefixes) { + std::string path = prefix; + if (!prefix.empty()) path += "zoneinfo/tzif2/"; // format + path.append(name, pos, std::string::npos); + + auto fp = FOpen(path.c_str(), "rb"); + if (fp == nullptr) continue; + + std::string version; + if (!prefix.empty()) { + // Fuchsia builds place the version in "<prefix>revision.txt". + std::ifstream version_stream(prefix + "revision.txt"); + if (version_stream.is_open()) { + // revision.txt should contain no newlines, but to be + // defensive we read just the first line. + std::getline(version_stream, version); + } + } + + return std::unique_ptr<ZoneInfoSource>( + new FuchsiaZoneInfoSource(std::move(fp), std::move(version))); + } + + return nullptr; +} + } // namespace bool TimeZoneInfo::Load(const std::string& name) { @@ -718,12 +786,13 @@ bool TimeZoneInfo::Load(const std::string& name) { // Find and use a ZoneInfoSource to load the named zone. auto zip = cctz_extension::zone_info_source_factory( - name, [](const std::string& name) -> std::unique_ptr<ZoneInfoSource> { - if (auto zip = FileZoneInfoSource::Open(name)) return zip; - if (auto zip = AndroidZoneInfoSource::Open(name)) return zip; + name, [](const std::string& n) -> std::unique_ptr<ZoneInfoSource> { + if (auto z = FileZoneInfoSource::Open(n)) return z; + if (auto z = AndroidZoneInfoSource::Open(n)) return z; + if (auto z = FuchsiaZoneInfoSource::Open(n)) return z; return nullptr; }); - return zip != nullptr && Load(name, zip.get()); + return zip != nullptr && Load(zip.get()); } // BreakTime() translation for a particular transition type. @@ -897,8 +966,8 @@ bool TimeZoneInfo::NextTransition(const time_point<seconds>& tp, const Transition* begin = &transitions_[0]; const Transition* end = begin + transitions_.size(); if (begin->unix_time <= -(1LL << 59)) { - // Do not report the BIG_BANG found in recent zoneinfo data as it is - // really a sentinel, not a transition. See tz/zic.c. + // Do not report the BIG_BANG found in some zoneinfo data as it is + // really a sentinel, not a transition. See pre-2018f tz/zic.c. ++begin; } std::int_fast64_t unix_time = ToUnixSeconds(tp); @@ -923,8 +992,8 @@ bool TimeZoneInfo::PrevTransition(const time_point<seconds>& tp, const Transition* begin = &transitions_[0]; const Transition* end = begin + transitions_.size(); if (begin->unix_time <= -(1LL << 59)) { - // Do not report the BIG_BANG found in recent zoneinfo data as it is - // really a sentinel, not a transition. See tz/zic.c. + // Do not report the BIG_BANG found in some zoneinfo data as it is + // really a sentinel, not a transition. See pre-2018f tz/zic.c. ++begin; } std::int_fast64_t unix_time = ToUnixSeconds(tp); diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.h b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.h index 2a10c06c77..2467ff559d 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.h +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_info.h @@ -95,15 +95,14 @@ class TimeZoneInfo : public TimeZoneIf { std::size_t DataLength(std::size_t time_len) const; }; - void CheckTransition(const std::string& name, const TransitionType& tt, - std::int_fast32_t offset, bool is_dst, - const std::string& abbr) const; + bool GetTransitionType(std::int_fast32_t utc_offset, bool is_dst, + const std::string& abbr, std::uint_least8_t* index); bool EquivTransitions(std::uint_fast8_t tt1_index, std::uint_fast8_t tt2_index) const; - void ExtendTransitions(const std::string& name, const Header& hdr); + bool ExtendTransitions(); bool ResetToBuiltinUTC(const seconds& offset); - bool Load(const std::string& name, ZoneInfoSource* zip); + bool Load(ZoneInfoSource* zip); // Helpers for BreakTime() and MakeTime(). time_zone::absolute_lookup LocalTime(std::int_fast64_t unix_time, diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_libc.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_libc.cc index 47cf84c663..887dd097c6 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_libc.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_libc.cc @@ -27,6 +27,12 @@ #include "absl/time/internal/cctz/include/cctz/civil_time.h" #include "absl/time/internal/cctz/include/cctz/time_zone.h" +#if defined(_AIX) +extern "C" { +extern long altzone; +} +#endif + namespace absl { ABSL_NAMESPACE_BEGIN namespace time_internal { @@ -44,7 +50,7 @@ auto tm_zone(const std::tm& tm) -> decltype(_tzname[0]) { const bool is_dst = tm.tm_isdst > 0; return _tzname[is_dst]; } -#elif defined(__sun) +#elif defined(__sun) || defined(_AIX) // Uses the globals: 'timezone', 'altzone' and 'tzname'. auto tm_gmtoff(const std::tm& tm) -> decltype(timezone) { const bool is_dst = tm.tm_isdst > 0; @@ -153,7 +159,8 @@ std::time_t find_trans(std::time_t lo, std::time_t hi, int offset) { std::tm tm; while (lo + 1 != hi) { const std::time_t mid = lo + (hi - lo) / 2; - if (std::tm* tmp = local_time(&mid, &tm)) { + std::tm* tmp = local_time(&mid, &tm); + if (tmp != nullptr) { if (tm_gmtoff(*tmp) == offset) { hi = mid; } else { @@ -163,7 +170,8 @@ std::time_t find_trans(std::time_t lo, std::time_t hi, int offset) { // If std::tm cannot hold some result we resort to a linear search, // ignoring all failed conversions. Slow, but never really happens. while (++lo != hi) { - if (std::tm* tmp = local_time(&lo, &tm)) { + tmp = local_time(&lo, &tm); + if (tmp != nullptr) { if (tm_gmtoff(*tmp) == offset) break; } } @@ -223,11 +231,10 @@ time_zone::civil_lookup TimeZoneLibC::MakeTime(const civil_second& cs) const { civil_second() + ToUnixSeconds(time_point<seconds>::min()); static const civil_second max_tp_cs = civil_second() + ToUnixSeconds(time_point<seconds>::max()); - const time_point<seconds> tp = - (cs < min_tp_cs) - ? time_point<seconds>::min() - : (cs > max_tp_cs) ? time_point<seconds>::max() - : FromUnixSeconds(cs - civil_second()); + const time_point<seconds> tp = (cs < min_tp_cs) ? time_point<seconds>::min() + : (cs > max_tp_cs) + ? time_point<seconds>::max() + : FromUnixSeconds(cs - civil_second()); return {time_zone::civil_lookup::UNIQUE, tp, tp, tp}; } diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup.cc index efdea64b4e..898d04c125 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup.cc @@ -28,6 +28,13 @@ #include <vector> #endif +#if defined(__Fuchsia__) +#include <fuchsia/intl/cpp/fidl.h> +#include <lib/async-loop/cpp/loop.h> +#include <lib/sys/cpp/component_context.h> +#include <zircon/types.h> +#endif + #include <cstdlib> #include <cstring> #include <string> @@ -140,6 +147,48 @@ time_zone local_time_zone() { } CFRelease(tz_default); #endif +#if defined(__Fuchsia__) + std::string primary_tz; + [&]() { + // Note: We can't use the synchronous FIDL API here because it doesn't + // allow timeouts; if the FIDL call failed, local_time_zone() would never + // return. + + const zx::duration kTimeout = zx::msec(500); + + // Don't attach to the thread because otherwise the thread's dispatcher + // would be set to null when the loop is destroyed, causing any other FIDL + // code running on the same thread to crash. + async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); + std::unique_ptr<sys::ComponentContext> context = + sys::ComponentContext::Create(); + + fuchsia::intl::PropertyProviderHandle handle; + zx_status_t status = context->svc()->Connect(handle.NewRequest()); + if (status != ZX_OK) { + return; + } + + fuchsia::intl::PropertyProviderPtr intl_provider; + status = intl_provider.Bind(std::move(handle), loop.dispatcher()); + if (status != ZX_OK) { + return; + } + + intl_provider->GetProfile( + [&loop, &primary_tz](fuchsia::intl::Profile profile) { + if (!profile.time_zones().empty()) { + primary_tz = profile.time_zones()[0].id; + } + loop.Quit(); + }); + loop.Run(zx::deadline_after(kTimeout)); + }(); + + if (!primary_tz.empty()) { + zone = primary_tz.c_str(); + } +#endif // Allow ${TZ} to override to default zone. char* tz_env = nullptr; diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup_test.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup_test.cc index 99137a082a..0226ab71ab 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_lookup_test.cc @@ -211,6 +211,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/North_Dakota/Beulah", "America/North_Dakota/Center", "America/North_Dakota/New_Salem", + "America/Nuuk", "America/Ojinaga", "America/Panama", "America/Pangnirtung", @@ -578,6 +579,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Pacific/Guam", "Pacific/Honolulu", "Pacific/Johnston", + "Pacific/Kanton", "Pacific/Kiritimati", "Pacific/Kosrae", "Pacific/Kwajalein", @@ -716,6 +718,18 @@ TEST(TimeZones, LoadZonesConcurrently) { } #endif +TEST(TimeZone, UTC) { + const time_zone utc = utc_time_zone(); + + time_zone loaded_utc; + EXPECT_TRUE(load_time_zone("UTC", &loaded_utc)); + EXPECT_EQ(loaded_utc, utc); + + time_zone loaded_utc0; + EXPECT_TRUE(load_time_zone("UTC0", &loaded_utc0)); + EXPECT_EQ(loaded_utc0, utc); +} + TEST(TimeZone, NamedTimeZones) { const time_zone utc = utc_time_zone(); EXPECT_EQ("UTC", utc.name()); @@ -749,7 +763,7 @@ TEST(TimeZone, Failures) { EXPECT_EQ(chrono::system_clock::from_time_t(0), convert(civil_second(1970, 1, 1, 0, 0, 0), tz)); // UTC - // Loading an empty std::string timezone should fail. + // Loading an empty string timezone should fail. tz = LoadZone("America/Los_Angeles"); EXPECT_FALSE(load_time_zone("", &tz)); EXPECT_EQ(chrono::system_clock::from_time_t(0), @@ -932,7 +946,7 @@ TEST(MakeTime, Normalization) { // NOTE: Run this with -ftrapv to detect overflow problems. TEST(MakeTime, SysSecondsLimits) { - const char RFC3339[] = "%Y-%m-%dT%H:%M:%S%Ez"; + const char RFC3339[] = "%Y-%m-%d%ET%H:%M:%S%Ez"; const time_zone utc = utc_time_zone(); const time_zone east = fixed_time_zone(chrono::hours(14)); const time_zone west = fixed_time_zone(-chrono::hours(14)); @@ -1003,13 +1017,21 @@ TEST(MakeTime, SysSecondsLimits) { #if defined(_WIN32) || defined(_WIN64) // localtime_s() and gmtime_s() don't believe in years outside [1970:3000]. #else - const time_zone utc = LoadZone("libc:UTC"); + const time_zone cut = LoadZone("libc:UTC"); const year_t max_tm_year = year_t{std::numeric_limits<int>::max()} + 1900; - tp = convert(civil_second(max_tm_year, 12, 31, 23, 59, 59), utc); - EXPECT_EQ("2147485547-12-31T23:59:59+00:00", format(RFC3339, tp, utc)); + tp = convert(civil_second(max_tm_year, 12, 31, 23, 59, 59), cut); +#if defined(__FreeBSD__) || defined(__OpenBSD__) + // The BSD gmtime_r() fails on extreme positive tm_year values. +#else + EXPECT_EQ("2147485547-12-31T23:59:59+00:00", format(RFC3339, tp, cut)); +#endif const year_t min_tm_year = year_t{std::numeric_limits<int>::min()} + 1900; - tp = convert(civil_second(min_tm_year, 1, 1, 0, 0, 0), utc); - EXPECT_EQ("-2147481748-01-01T00:00:00+00:00", format(RFC3339, tp, utc)); + tp = convert(civil_second(min_tm_year, 1, 1, 0, 0, 0), cut); +#if defined(__Fuchsia__) + // Fuchsia's gmtime_r() fails on extreme negative values (fxbug.dev/78527). +#else + EXPECT_EQ("-2147481748-01-01T00:00:00+00:00", format(RFC3339, tp, cut)); +#endif #endif } } diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/tzfile.h b/third_party/abseil-cpp/absl/time/internal/cctz/src/tzfile.h index 1ed55e0f95..31e8598257 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/tzfile.h +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/tzfile.h @@ -43,7 +43,7 @@ struct tzhead { char tzh_magic[4]; /* TZ_MAGIC */ - char tzh_version[1]; /* '\0' or '2' or '3' as of 2013 */ + char tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */ char tzh_reserved[15]; /* reserved; must be zero */ char tzh_ttisutcnt[4]; /* coded number of trans. time flags */ char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ @@ -83,13 +83,13 @@ struct tzhead { ** If tzh_version is '2' or greater, the above is followed by a second instance ** of tzhead and a second instance of the data in which each coded transition ** time uses 8 rather than 4 chars, -** then a POSIX-TZ-environment-variable-style std::string for use in handling +** then a POSIX-TZ-environment-variable-style string for use in handling ** instants after the last transition time stored in the file ** (with nothing between the newlines if there is no POSIX representation for ** such instants). ** ** If tz_version is '3' or greater, the above is extended as follows. -** First, the POSIX TZ std::string's hour offset may range from -167 +** First, the POSIX TZ string's hour offset may range from -167 ** through 167 as compared to the POSIX-required 0 through 24. ** Second, its DST start time may be January 1 at 00:00 and its stop ** time December 31 at 24:00 plus the difference between DST and diff --git a/third_party/abseil-cpp/absl/time/internal/cctz/src/zone_info_source.cc b/third_party/abseil-cpp/absl/time/internal/cctz/src/zone_info_source.cc index 98ea161267..5ab5a59ecf 100644 --- a/third_party/abseil-cpp/absl/time/internal/cctz/src/zone_info_source.cc +++ b/third_party/abseil-cpp/absl/time/internal/cctz/src/zone_info_source.cc @@ -65,7 +65,7 @@ ZoneInfoSourceFactory zone_info_source_factory __attribute__((weak)) = extern ZoneInfoSourceFactory zone_info_source_factory; extern ZoneInfoSourceFactory default_factory; ZoneInfoSourceFactory default_factory = DefaultFactory; -#if defined(_M_IX86) +#if defined(_M_IX86) || defined(_M_ARM) #pragma comment( \ linker, \ "/alternatename:?zone_info_source_factory@cctz_extension@time_internal@" ABSL_INTERNAL_MANGLED_NS \ |