From 084aa074c6e3994471fcdb8f542d3a24916792de Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Fri, 8 Oct 2021 15:00:10 -0700 Subject: Export of internal Abseil changes -- 42dc250867db8816381a38596e00a3b27b7dbc37 by Gennadiy Rozental : Import of CCTZ from GitHub. PiperOrigin-RevId: 401863616 GitOrigin-RevId: 42dc250867db8816381a38596e00a3b27b7dbc37 Change-Id: Ia1f100293e0c0845de76d986a170b5ca8d15f1a3 --- absl/time/internal/cctz/include/cctz/time_zone.h | 113 +++++++++++++++++---- .../internal/cctz/src/time_zone_format_test.cc | 81 ++++++++++++++- absl/time/internal/cctz/src/time_zone_if.h | 3 +- 3 files changed, 172 insertions(+), 25 deletions(-) diff --git a/absl/time/internal/cctz/include/cctz/time_zone.h b/absl/time/internal/cctz/include/cctz/time_zone.h index 5562a37b..6e382dc6 100644 --- a/absl/time/internal/cctz/include/cctz/time_zone.h +++ b/absl/time/internal/cctz/include/cctz/time_zone.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -41,20 +42,9 @@ using sys_seconds = seconds; // Deprecated. Use cctz::seconds instead. namespace detail { template -inline std::pair, D> split_seconds( - const time_point& tp) { - auto sec = std::chrono::time_point_cast(tp); - auto sub = tp - sec; - if (sub.count() < 0) { - sec -= seconds(1); - sub += seconds(1); - } - return {sec, std::chrono::duration_cast(sub)}; -} -inline std::pair, seconds> split_seconds( - const time_point& tp) { - return {tp, seconds::zero()}; -} +std::pair, D> split_seconds(const time_point& tp); +std::pair, seconds> split_seconds( + const time_point& tp); } // namespace detail // cctz::time_zone is an opaque, small, value-type class representing a @@ -279,6 +269,20 @@ std::string format(const std::string&, const time_point&, const femtoseconds&, const time_zone&); bool parse(const std::string&, const std::string&, const time_zone&, time_point*, femtoseconds*, std::string* err = nullptr); +template +bool join_seconds( + const time_point& sec, const femtoseconds& fs, + time_point>>* tpp); +template +bool join_seconds( + const time_point& sec, const femtoseconds& fs, + time_point>>* tpp); +template +bool join_seconds( + const time_point& sec, const femtoseconds& fs, + time_point>>* tpp); +bool join_seconds(const time_point& sec, const femtoseconds&, + time_point* tpp); } // namespace detail // Formats the given time_point in the given cctz::time_zone according to @@ -369,15 +373,84 @@ inline bool parse(const std::string& fmt, const std::string& input, const time_zone& tz, time_point* tpp) { time_point sec; detail::femtoseconds fs; - const bool b = detail::parse(fmt, input, tz, &sec, &fs); - if (b) { - // TODO: Return false if unrepresentable as a time_point. - *tpp = std::chrono::time_point_cast(sec); - *tpp += std::chrono::duration_cast(fs); + return detail::parse(fmt, input, tz, &sec, &fs) && + detail::join_seconds(sec, fs, tpp); +} + +namespace detail { + +// Split a time_point into a time_point and a D subseconds. +// Undefined behavior if time_point is not of sufficient range. +// Note that this means it is UB to call cctz::time_zone::lookup(tp) or +// cctz::format(fmt, tp, tz) with a time_point that is outside the range +// of a 64-bit std::time_t. +template +std::pair, D> split_seconds(const time_point& tp) { + auto sec = std::chrono::time_point_cast(tp); + auto sub = tp - sec; + if (sub.count() < 0) { + sec -= seconds(1); + sub += seconds(1); } - return b; + return {sec, std::chrono::duration_cast(sub)}; +} + +inline std::pair, seconds> split_seconds( + const time_point& tp) { + return {tp, seconds::zero()}; } +// Join a time_point and femto subseconds into a time_point. +// Floors to the resolution of time_point. Returns false if time_point +// is not of sufficient range. +template +bool join_seconds( + const time_point& sec, const femtoseconds& fs, + time_point>>* tpp) { + using D = std::chrono::duration>; + // TODO(#199): Return false if result unrepresentable as a time_point. + *tpp = std::chrono::time_point_cast(sec); + *tpp += std::chrono::duration_cast(fs); + return true; +} + +template +bool join_seconds( + const time_point& sec, const femtoseconds&, + time_point>>* tpp) { + using D = std::chrono::duration>; + auto count = sec.time_since_epoch().count(); + if (count >= 0 || count % Num == 0) { + count /= Num; + } else { + count /= Num; + count -= 1; + } + if (count > (std::numeric_limits::max)()) return false; + if (count < (std::numeric_limits::min)()) return false; + *tpp = time_point() + D{static_cast(count)}; + return true; +} + +template +bool join_seconds( + const time_point& sec, const femtoseconds&, + time_point>>* tpp) { + using D = std::chrono::duration>; + auto count = sec.time_since_epoch().count(); + if (count > (std::numeric_limits::max)()) return false; + if (count < (std::numeric_limits::min)()) return false; + *tpp = time_point() + D{static_cast(count)}; + return true; +} + +inline bool join_seconds(const time_point& sec, const femtoseconds&, + time_point* tpp) { + *tpp = sec; + return true; +} + +} // namespace detail } // namespace cctz } // namespace time_internal ABSL_NAMESPACE_END 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 294f2e22..6487fa93 100644 --- a/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/absl/time/internal/cctz/src/time_zone_format_test.cc @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include #include @@ -1504,7 +1505,7 @@ TEST(Parse, MaxRange) { parse(RFC3339_sec, "292277026596-12-04T14:30:07-01:00", utc, &tp)); EXPECT_EQ(tp, time_point::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( @@ -1525,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(); + + using D = chrono::duration; + time_point tp; + + EXPECT_TRUE( + parse(RFC3339_full, "2262-04-11T23:47:16.8547758079+00:00", utc, &tp)); + EXPECT_EQ(tp, time_point::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::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; + time_point stp; + + EXPECT_TRUE(parse(RFC3339_full, "1970-01-01T00:02:07.9+00:00", utc, &stp)); + EXPECT_EQ(stp, time_point::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::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)); - // 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 DM = chrono::duration; + time_point mtp; + + EXPECT_TRUE(parse(RFC3339_full, "1970-01-01T02:07:59+00:00", utc, &mtp)); + EXPECT_EQ(mtp, time_point::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::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; + time_point tp; + + EXPECT_TRUE( + parse(RFC3339_full, "294247-01-10T04:00:54.7758079+00:00", utc, &tp)); + EXPECT_EQ(tp, time_point::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::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/absl/time/internal/cctz/src/time_zone_if.h b/absl/time/internal/cctz/src/time_zone_if.h index 32c0891c..7d3e42d3 100644 --- a/absl/time/internal/cctz/src/time_zone_if.h +++ b/absl/time/internal/cctz/src/time_zone_if.h @@ -56,7 +56,8 @@ class TimeZoneIf { // Convert between time_point 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& tp) { return (tp - std::chrono::time_point_cast( std::chrono::system_clock::from_time_t(0))) -- cgit v1.2.3