diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40351ddcf..f4cb4a29e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,53 @@ You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. +## Contribution Guidelines + +Potential contributors sometimes ask us if the Abseil project is the appropriate +home for their utility library code or for specific functions implementing +missing portions of the standard. Often, the answer to this question is "no". +We’d like to articulate our thinking on this issue so that our choices can be +understood by everyone and so that contributors can have a better intuition +about whether Abseil might be interested in adopting a new library. + +### Priorities + +Although our mission is to augment the C++ standard library, our goal is not to +provide a full forward-compatible implementation of the latest standard. For us +to consider a library for inclusion in Abseil, it is not enough that a library +is useful. We generally choose to release a library when it meets at least one +of the following criteria: + +* **Widespread usage** - Using our internal codebase to help gauge usage, most + of the libraries we've released have tens of thousands of users. +* **Anticipated widespread usage** - Pre-adoption of some standard-compliant + APIs may not have broad adoption initially but can be expected to pick up + usage when it replaces legacy APIs. `absl::from_chars`, for example, + replaces existing code that converts strings to numbers and will therefore + likely see usage growth. +* **High impact** - APIs that provide a key solution to a specific problem, + such as `absl::FixedArray`, have higher impact than usage numbers may signal + and are released because of their importance. +* **Direct support for a library that falls under one of the above** - When we + want access to a smaller library as an implementation detail for a + higher-priority library we plan to release, we may release it, as we did + with portions of `absl/meta/type_traits.h`. One consequence of this is that + the presence of a library in Abseil does not necessarily mean that other + similar libraries would be a high priority. + +### API Freeze Consequences + +Via the +[Abseil Compatibility Guidelines](https://abseil.io/about/compatibility), we +have promised a large degree of API stability. In particular, we will not make +backward-incompatible changes to released APIs without also shipping a tool or +process that can upgrade our users' code. We are not yet at the point of easily +releasing such tools. Therefore, at this time, shipping a library establishes an +API contract which is borderline unchangeable. (We can add new functionality, +but we cannot easily change existing behavior.) This constraint forces us to +very carefully review all APIs that we ship. + + ## Coding Style To keep the source consistent, readable, diffable and easy to merge, we use a diff --git a/absl/hash/hash_testing.h b/absl/hash/hash_testing.h index 1e3cda644..52bcb55a2 100644 --- a/absl/hash/hash_testing.h +++ b/absl/hash/hash_testing.h @@ -90,7 +90,7 @@ namespace absl { // template // friend H AbslHashValue(H state, Bad2 x) { // // Uses a and b. -// return H::combine(x.a, x.b); +// return H::combine(std::move(state), x.a, x.b); // } // friend bool operator==(Bad2 x, Bad2 y) { // // Only uses a. @@ -107,7 +107,7 @@ namespace absl { // template // friend H AbslHashValue(H state, Bad3 x) { // // Only uses a. -// return H::combine(x.a); +// return H::combine(std::move(state), x.a); // } // friend bool operator==(Bad3 x, Bad3 y) { // // Uses a and b. @@ -123,19 +123,21 @@ namespace absl { // int *p, size; // template // friend H AbslHashValue(H state, Bad4 x) { -// return H::combine_range(x.p, x.p + x.size); +// return H::combine_contiguous(std::move(state), x.p, x.p + x.size); // } // friend bool operator==(Bad4 x, Bad4 y) { -// return std::equal(x.p, x.p + x.size, y.p, y.p + y.size); +// // Compare two ranges for equality. C++14 code can instead use std::equal. +// return absl::equal(x.p, x.p + x.size, y.p, y.p + y.size); // } // }; // // An easy solution to this is to combine the size after combining the range, // like so: -// template -// friend H AbslHashValue(H state, Bad4 x) { -// return H::combine(H::combine_range(x.p, x.p + x.size), x.size); -// } +// template +// friend H AbslHashValue(H state, Bad4 x) { +// return H::combine( +// H::combine_contiguous(std::move(state), x.p, x.p + x.size), x.size); +// } // template ABSL_MUST_USE_RESULT testing::AssertionResult @@ -227,7 +229,8 @@ VerifyTypeImplementsAbslHashCorrectly(const Container& values, Eq equals) { // Now we verify that AbslHashValue is also correctly implemented. for (const auto& c : classes) { - // All elements of the equivalence class must have the same hash expansion. + // All elements of the equivalence class must have the same hash + // expansion. const SpyHashState expected = c[0].expand(); for (const Info& v : c) { if (v.expand() != v.expand()) { @@ -285,7 +288,7 @@ struct TypeSet { }; template -struct MakeTypeSet : TypeSet<>{}; +struct MakeTypeSet : TypeSet<> {}; template struct MakeTypeSet : MakeTypeSet::template Insert::type {}; @@ -346,8 +349,7 @@ template ABSL_MUST_USE_RESULT testing::AssertionResult VerifyTypeImplementsAbslHashCorrectly(const Container& values, Eq equals) { return hash_internal::VerifyTypeImplementsAbslHashCorrectly( - hash_internal::ContainerAsVector::Do(values), - equals); + hash_internal::ContainerAsVector::Do(values), equals); } template 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 280c96b4f..f28e7f853 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc @@ -991,15 +991,17 @@ TEST(MakeTime, SysSecondsLimits) { tp = convert(civil_second::min(), west); EXPECT_EQ(time_point::min(), tp); - // Checks that "tm_year + 1900", as used by the "libc" implementation, - // can produce year values beyond the range on an int without overflow. + if (sizeof(std::time_t) >= 8) { + // Checks that "tm_year + 1900", as used by the "libc" implementation, + // can produce year values beyond the range on an int without overflow. #if defined(_WIN32) || defined(_WIN64) - // localtime_s() and gmtime_s() don't believe in years past 3000. + // localtime_s() and gmtime_s() don't believe in years past 3000. #else - const time_zone libc_utc = LoadZone("libc:UTC"); - tp = convert(civil_year(year_t{2147483648}), libc_utc); - EXPECT_EQ("2147483648-01-01T00:00:00+00:00", format(RFC3339, tp, libc_utc)); + const time_zone libc_utc = LoadZone("libc:UTC"); + tp = convert(civil_year(year_t{2147483648}), libc_utc); + EXPECT_EQ("2147483648-01-01T00:00:00+00:00", format(RFC3339, tp, libc_utc)); #endif + } } TEST(NextTransition, UTC) { diff --git a/absl/time/time.cc b/absl/time/time.cc index 0703856fa..ac2c8a835 100644 --- a/absl/time/time.cc +++ b/absl/time/time.cc @@ -176,6 +176,20 @@ inline int MapWeekday(const cctz::weekday& wd) { return 1; } +bool FindTransition(const cctz::time_zone& tz, + bool (cctz::time_zone::*find_transition)( + const cctz::time_point& tp, + cctz::time_zone::civil_transition* trans) const, + Time t, TimeZone::CivilTransition* trans) { + // Transitions are second-aligned, so we can discard any fractional part. + const auto tp = unix_epoch() + cctz::seconds(ToUnixSeconds(t)); + cctz::time_zone::civil_transition tr; + if (!(tz.*find_transition)(tp, &tr)) return false; + trans->from = CivilSecond(tr.from); + trans->to = CivilSecond(tr.to); + return true; +} + } // namespace // @@ -366,6 +380,14 @@ absl::TimeZone::TimeInfo TimeZone::At(CivilSecond ct) const { return ti; } +bool TimeZone::NextTransition(Time t, CivilTransition* trans) const { + return FindTransition(cz_, &cctz::time_zone::next_transition, t, trans); +} + +bool TimeZone::PrevTransition(Time t, CivilTransition* trans) const { + return FindTransition(cz_, &cctz::time_zone::prev_transition, t, trans); +} + // // Conversions involving time zones. // diff --git a/absl/time/time.h b/absl/time/time.h index 2858da295..7d5c3fde7 100644 --- a/absl/time/time.h +++ b/absl/time/time.h @@ -886,7 +886,7 @@ class TimeZone { struct TimeInfo { enum CivilKind { UNIQUE, // the civil time was singular (pre == trans == post) - SKIPPED, // the civil time did not exist (pre => trans > post) + SKIPPED, // the civil time did not exist (pre >= trans > post) REPEATED, // the civil time was ambiguous (pre < trans <= post) } kind; Time pre; // time calculated using the pre-transition offset @@ -925,6 +925,44 @@ class TimeZone { // // nov06.post is 2011-11-06 01:15:00 -0800 TimeInfo At(CivilSecond ct) const; + // TimeZone::NextTransition() + // TimeZone::PrevTransition() + // + // Finds the time of the next/previous offset change in this time zone. + // + // By definition, `NextTransition(t, &trans)` returns false when `t` is + // `InfiniteFuture()`, and `PrevTransition(t, &trans)` returns false + // when `t` is `InfinitePast()`. If the zone has no transitions, the + // result will also be false no matter what the argument. + // + // Otherwise, when `t` is `InfinitePast()`, `NextTransition(t, &trans)` + // returns true and sets `trans` to the first recorded transition. Chains + // of calls to `NextTransition()/PrevTransition()` will eventually return + // false, but it is unspecified exactly when `NextTransition(t, &trans)` + // jumps to false, or what time is set by `PrevTransition(t, &trans)` for + // a very distant `t`. + // + // Note: Enumeration of time-zone transitions is for informational purposes + // only. Modern time-related code should not care about when offset changes + // occur. + // + // Example: + // absl::TimeZone nyc; + // if (!absl::LoadTimeZone("America/New_York", &nyc)) { ... } + // const auto now = absl::Now(); + // auto t = absl::InfinitePast(); + // absl::TimeZone::CivilTransition trans; + // while (t <= now && nyc.NextTransition(t, &trans)) { + // // transition: trans.from -> trans.to + // t = nyc.At(trans.to).trans; + // } + struct CivilTransition { + CivilSecond from; // the civil time we jump from + CivilSecond to; // the civil time we jump to + }; + bool NextTransition(Time t, CivilTransition* trans) const; + bool PrevTransition(Time t, CivilTransition* trans) const; + template friend H AbslHashValue(H h, TimeZone tz) { return H::combine(std::move(h), tz.cz_); diff --git a/absl/time/time_test.cc b/absl/time/time_test.cc index feca45873..4d7107092 100644 --- a/absl/time/time_test.cc +++ b/absl/time/time_test.cc @@ -1135,4 +1135,67 @@ TEST(Time, LegacyDateTime) { EXPECT_EQ("2014-10-29 22:58:59", absl::FormatTime(ymdhms, t, utc)); } +TEST(Time, NextTransitionUTC) { + const auto tz = absl::UTCTimeZone(); + absl::TimeZone::CivilTransition trans; + + auto t = absl::InfinitePast(); + EXPECT_FALSE(tz.NextTransition(t, &trans)); + + t = absl::InfiniteFuture(); + EXPECT_FALSE(tz.NextTransition(t, &trans)); +} + +TEST(Time, PrevTransitionUTC) { + const auto tz = absl::UTCTimeZone(); + absl::TimeZone::CivilTransition trans; + + auto t = absl::InfiniteFuture(); + EXPECT_FALSE(tz.PrevTransition(t, &trans)); + + t = absl::InfinitePast(); + EXPECT_FALSE(tz.PrevTransition(t, &trans)); +} + +TEST(Time, NextTransitionNYC) { + const auto tz = absl::time_internal::LoadTimeZone("America/New_York"); + absl::TimeZone::CivilTransition trans; + + auto t = absl::FromCivil(absl::CivilSecond(2018, 6, 30, 0, 0, 0), tz); + EXPECT_TRUE(tz.NextTransition(t, &trans)); + EXPECT_EQ(absl::CivilSecond(2018, 11, 4, 2, 0, 0), trans.from); + EXPECT_EQ(absl::CivilSecond(2018, 11, 4, 1, 0, 0), trans.to); + + t = absl::InfiniteFuture(); + EXPECT_FALSE(tz.NextTransition(t, &trans)); + + t = absl::InfinitePast(); + EXPECT_TRUE(tz.NextTransition(t, &trans)); + if (trans.from == absl::CivilSecond(1918, 03, 31, 2, 0, 0)) { + // It looks like the tzdata is only 32 bit (probably macOS), + // which bottoms out at 1901-12-13T20:45:52+00:00. + EXPECT_EQ(absl::CivilSecond(1918, 3, 31, 3, 0, 0), trans.to); + } else { + EXPECT_EQ(absl::CivilSecond(1883, 11, 18, 12, 3, 58), trans.from); + EXPECT_EQ(absl::CivilSecond(1883, 11, 18, 12, 0, 0), trans.to); + } +} + +TEST(Time, PrevTransitionNYC) { + const auto tz = absl::time_internal::LoadTimeZone("America/New_York"); + absl::TimeZone::CivilTransition trans; + + auto t = absl::FromCivil(absl::CivilSecond(2018, 6, 30, 0, 0, 0), tz); + EXPECT_TRUE(tz.PrevTransition(t, &trans)); + EXPECT_EQ(absl::CivilSecond(2018, 3, 11, 2, 0, 0), trans.from); + EXPECT_EQ(absl::CivilSecond(2018, 3, 11, 3, 0, 0), trans.to); + + t = absl::InfinitePast(); + EXPECT_FALSE(tz.PrevTransition(t, &trans)); + + t = absl::InfiniteFuture(); + EXPECT_TRUE(tz.PrevTransition(t, &trans)); + // We have a transition but we don't know which one. +} + } // namespace