From 2eba343b51e0923cd3fb919a6abd6120590fc059 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 9 Jun 2020 08:17:12 -0700 Subject: Export of internal Abseil changes -- baf626d27ff1547776745f3b601cc42f703d4bdf by Greg Falcon : Import of CCTZ from GitHub. PiperOrigin-RevId: 315486679 -- 39d4e7f9214c5a74e4ee4b8a7f4af26479925029 by Gennadiy Rozental : Avoid order dependant test cases. PiperOrigin-RevId: 315327948 -- 8feb187703a28bb0c5cfc5dd6091e3faa90a8e61 by Gennadiy Rozental : Avoid order dependant test cases. PiperOrigin-RevId: 315317018 -- cffd8fddad15908ec9de7f21f65b20528972e3fa by Gennadiy Rozental : Avoid order dependant test cases. PiperOrigin-RevId: 315311587 -- 48997a0ceb231b75c14eb4b1eb3b66af69e49f42 by Tom Manshreck : Nit: remove extra "the" PiperOrigin-RevId: 314959763 -- 9516f44a4b5d3055427c95e80c8c04a6321e4221 by Gennadiy Rozental : Import of CCTZ from GitHub. PiperOrigin-RevId: 314925147 -- 4f8691d74a4eb42ed03ef29c9588a01b78829941 by Abseil Team : Uninclude util/bits/bits.h PiperOrigin-RevId: 314858384 -- 02b42a8ec5e5561b29b4e5f93cc1482c3c72ef2d by Abseil Team : Google-internal changes only. PiperOrigin-RevId: 314855667 -- 146013d69dabafbe2030e23ced7b741d9e8d417c by Gennadiy Rozental : Uninclude util/bits/bits.h PiperOrigin-RevId: 314838927 GitOrigin-RevId: baf626d27ff1547776745f3b601cc42f703d4bdf Change-Id: I2602286fb13cf35a88bdd80fe0b44974d4388d46 --- absl/time/internal/cctz/src/time_zone_format.cc | 124 +++++++++++++++++---- .../internal/cctz/src/time_zone_format_test.cc | 88 +++++++++++++++ absl/time/internal/cctz/src/time_zone_impl.cc | 34 +++--- 3 files changed, 201 insertions(+), 45 deletions(-) (limited to 'absl/time') diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc index a4428632..2e02233c 100644 --- a/absl/time/internal/cctz/src/time_zone_format.cc +++ b/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(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((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 @@ -355,7 +382,7 @@ std::string format(const std::string& format, const time_point& 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); } @@ -376,6 +403,22 @@ std::string format(const std::string& format, const time_point& tp, if (*cur == 'e' && *bp == '0') *bp = ' '; // for Windows result.append(bp, static_cast(ep - bp)); break; + case 'U': + bp = Format02d(ep, ToWeek(civil_day(al.cs), weekday::sunday)); + result.append(bp, static_cast(ep - bp)); + break; + case 'u': + bp = Format64(ep, 0, tm.tm_wday ? tm.tm_wday : 7); + result.append(bp, static_cast(ep - bp)); + break; + case 'W': + bp = Format02d(ep, ToWeek(civil_day(al.cs), weekday::monday)); + result.append(bp, static_cast(ep - bp)); + break; + case 'w': + bp = Format64(ep, 0, tm.tm_wday); + result.append(bp, static_cast(ep - bp)); + break; case 'H': bp = Format02d(ep, al.cs.hour()); result.append(bp, static_cast(ep - bp)); @@ -610,6 +653,17 @@ 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. +void 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); + *year += cd.year() - y.year(); + tm->tm_mon = cd.month() - 1; + tm->tm_mday = cd.day(); +} + } // namespace // Uses strptime(3) to parse the given input. Supports the same extended @@ -659,6 +713,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; @@ -697,10 +753,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); @@ -891,6 +964,9 @@ 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) FromWeek(week_num, week_start, &year, &tm); + 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/absl/time/internal/cctz/src/time_zone_format_test.cc b/absl/time/internal/cctz/src/time_zone_format_test.cc index 13a4227e..e625a839 100644 --- a/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/absl/time/internal/cctz/src/time_zone_format_test.cc @@ -679,6 +679,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() // @@ -1395,6 +1423,66 @@ TEST(Parse, RFC3339Format) { EXPECT_EQ(tp, tp4); } +TEST(Parse, Week) { + const time_zone utc = utc_time_zone(); + time_point 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 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); +} + TEST(Parse, MaxRange) { const time_zone utc = utc_time_zone(); time_point tp; diff --git a/absl/time/internal/cctz/src/time_zone_impl.cc b/absl/time/internal/cctz/src/time_zone_impl.cc index 030ae0e1..f34e3aec 100644 --- a/absl/time/internal/cctz/src/time_zone_impl.cc +++ b/absl/time/internal/cctz/src/time_zone_impl.cc @@ -15,6 +15,7 @@ #include "time_zone_impl.h" #include +#include #include #include #include @@ -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 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 new_impl(new Impl(name)); + + // Add the new time zone to the map. std::lock_guard 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; } -- cgit v1.2.3