diff options
-rw-r--r-- | absl/time/civil_time.cc | 90 | ||||
-rw-r--r-- | absl/time/civil_time.h | 51 | ||||
-rw-r--r-- | absl/time/civil_time_benchmark.cc | 20 | ||||
-rw-r--r-- | absl/time/civil_time_test.cc | 158 |
4 files changed, 319 insertions, 0 deletions
diff --git a/absl/time/civil_time.cc b/absl/time/civil_time.cc index 7527fc11..7b86fa8b 100644 --- a/absl/time/civil_time.cc +++ b/absl/time/civil_time.cc @@ -42,6 +42,58 @@ std::string FormatYearAnd(string_view fmt, CivilSecond cs) { FormatTime(std::string(fmt), FromCivil(ncs, utc), utc)); } +template <typename CivilT> +bool ParseYearAnd(string_view fmt, string_view s, CivilT* c) { + // Civil times support a larger year range than absl::Time, so we need to + // parse the year separately, normalize it, then use absl::ParseTime on the + // normalized std::string. + const std::string ss = std::string(s); // TODO(absl-team): Avoid conversion. + const char* const np = ss.c_str(); + char* endp; + errno = 0; + const civil_year_t y = + std::strtoll(np, &endp, 10); // NOLINT(runtime/deprecated_fn) + if (endp == np || errno == ERANGE) return false; + const std::string norm = StrCat(NormalizeYear(y), endp); + + const TimeZone utc = UTCTimeZone(); + Time t; + if (ParseTime(StrCat("%Y", fmt), norm, utc, &t, nullptr)) { + const auto cs = ToCivilSecond(t, utc); + *c = CivilT(y, cs.month(), cs.day(), cs.hour(), cs.minute(), cs.second()); + return true; + } + + return false; +} + +// Tries to parse the type as a CivilT1, but then assigns the result to the +// argument of type CivilT2. +template <typename CivilT1, typename CivilT2> +bool ParseAs(string_view s, CivilT2* c) { + CivilT1 t1; + if (ParseCivilTime(s, &t1)) { + *c = CivilT2(t1); + return true; + } + return false; +} + +template <typename CivilT> +bool ParseLenient(string_view s, CivilT* c) { + // A fastpath for when the given std::string data parses exactly into the given + // type T (e.g., s="YYYY-MM-DD" and CivilT=CivilDay). + if (ParseCivilTime(s, c)) return true; + // Try parsing as each of the 6 types, trying the most common types first + // (based on csearch results). + if (ParseAs<CivilDay>(s, c)) return true; + if (ParseAs<CivilSecond>(s, c)) return true; + if (ParseAs<CivilHour>(s, c)) return true; + if (ParseAs<CivilMonth>(s, c)) return true; + if (ParseAs<CivilMinute>(s, c)) return true; + if (ParseAs<CivilYear>(s, c)) return true; + return false; +} } // namespace std::string FormatCivilTime(CivilSecond c) { @@ -57,6 +109,44 @@ std::string FormatCivilTime(CivilDay c) { return FormatYearAnd("-%m-%d", c); } std::string FormatCivilTime(CivilMonth c) { return FormatYearAnd("-%m", c); } std::string FormatCivilTime(CivilYear c) { return FormatYearAnd("", c); } +bool ParseCivilTime(string_view s, CivilSecond* c) { + return ParseYearAnd("-%m-%dT%H:%M:%S", s, c); +} +bool ParseCivilTime(string_view s, CivilMinute* c) { + return ParseYearAnd("-%m-%dT%H:%M", s, c); +} +bool ParseCivilTime(string_view s, CivilHour* c) { + return ParseYearAnd("-%m-%dT%H", s, c); +} +bool ParseCivilTime(string_view s, CivilDay* c) { + return ParseYearAnd("-%m-%d", s, c); +} +bool ParseCivilTime(string_view s, CivilMonth* c) { + return ParseYearAnd("-%m", s, c); +} +bool ParseCivilTime(string_view s, CivilYear* c) { + return ParseYearAnd("", s, c); +} + +bool ParseLenientCivilTime(string_view s, CivilSecond* c) { + return ParseLenient(s, c); +} +bool ParseLenientCivilTime(string_view s, CivilMinute* c) { + return ParseLenient(s, c); +} +bool ParseLenientCivilTime(string_view s, CivilHour* c) { + return ParseLenient(s, c); +} +bool ParseLenientCivilTime(string_view s, CivilDay* c) { + return ParseLenient(s, c); +} +bool ParseLenientCivilTime(string_view s, CivilMonth* c) { + return ParseLenient(s, c); +} +bool ParseLenientCivilTime(string_view s, CivilYear* c) { + return ParseLenient(s, c); +} + namespace time_internal { std::ostream& operator<<(std::ostream& os, CivilYear y) { diff --git a/absl/time/civil_time.h b/absl/time/civil_time.h index beaf7d89..7c52586a 100644 --- a/absl/time/civil_time.h +++ b/absl/time/civil_time.h @@ -459,6 +459,57 @@ std::string FormatCivilTime(CivilDay c); std::string FormatCivilTime(CivilMonth c); std::string FormatCivilTime(CivilYear c); +// absl::ParseCivilTime() +// +// Parses a civil-time value from the specified `absl::string_view` into the +// passed output parameter. Returns `true` upon successful parsing. +// +// The expected form of the input string is as follows: +// +// Type | Format +// --------------------------------- +// CivilSecond | YYYY-MM-DDTHH:MM:SS +// CivilMinute | YYYY-MM-DDTHH:MM +// CivilHour | YYYY-MM-DDTHH +// CivilDay | YYYY-MM-DD +// CivilMonth | YYYY-MM +// CivilYear | YYYY +// +// Example: +// +// absl::CivilDay d; +// bool ok = absl::ParseCivilTime("2018-01-02", &d); // OK +// +// Note that parsing will fail if the string's format does not match the +// expected type exactly. `ParseLenientCivilTime()` below is more lenient. +// +bool ParseCivilTime(absl::string_view s, CivilSecond* c); +bool ParseCivilTime(absl::string_view s, CivilMinute* c); +bool ParseCivilTime(absl::string_view s, CivilHour* c); +bool ParseCivilTime(absl::string_view s, CivilDay* c); +bool ParseCivilTime(absl::string_view s, CivilMonth* c); +bool ParseCivilTime(absl::string_view s, CivilYear* c); + +// ParseLenientCivilTime() +// +// Parses any of the formats accepted by `absl::ParseCivilTime()`, but is more +// lenient if the format of the string does not exactly match the associated +// type. +// +// Example: +// +// absl::CivilDay d; +// bool ok = absl::ParseLenientCivilTime("1969-07-20", &d); // OK +// ok = absl::ParseLenientCivilTime("1969-07-20T10", &d); // OK: T10 floored +// ok = absl::ParseLenientCivilTime("1969-07", &d); // OK: day defaults to 1 +// +bool ParseLenientCivilTime(absl::string_view s, CivilSecond* c); +bool ParseLenientCivilTime(absl::string_view s, CivilMinute* c); +bool ParseLenientCivilTime(absl::string_view s, CivilHour* c); +bool ParseLenientCivilTime(absl::string_view s, CivilDay* c); +bool ParseLenientCivilTime(absl::string_view s, CivilMonth* c); +bool ParseLenientCivilTime(absl::string_view s, CivilYear* c); + namespace time_internal { // For functions found via ADL on civil-time tags. // Streaming Operators diff --git a/absl/time/civil_time_benchmark.cc b/absl/time/civil_time_benchmark.cc index 40869835..f04dbe20 100644 --- a/absl/time/civil_time_benchmark.cc +++ b/absl/time/civil_time_benchmark.cc @@ -66,6 +66,26 @@ void BM_Format(benchmark::State& state) { } BENCHMARK(BM_Format); +void BM_Parse(benchmark::State& state) { + const std::string f = "2014-01-02T03:04:05"; + absl::CivilSecond c; + while (state.KeepRunning()) { + const bool b = absl::ParseCivilTime(f, &c); + benchmark::DoNotOptimize(b); + } +} +BENCHMARK(BM_Parse); + +void BM_RoundTripFormatParse(benchmark::State& state) { + const absl::CivilSecond c(2014, 1, 2, 3, 4, 5); + absl::CivilSecond out; + while (state.KeepRunning()) { + const bool b = absl::ParseCivilTime(absl::FormatCivilTime(c), &out); + benchmark::DoNotOptimize(b); + } +} +BENCHMARK(BM_RoundTripFormatParse); + template <typename T> void BM_CivilTimeAbslHash(benchmark::State& state) { const int kSize = 100000; diff --git a/absl/time/civil_time_test.cc b/absl/time/civil_time_test.cc index 03cd1f12..0ebd97ad 100644 --- a/absl/time/civil_time_test.cc +++ b/absl/time/civil_time_test.cc @@ -690,6 +690,69 @@ TEST(CivilTime, Format) { EXPECT_EQ("1970", absl::FormatCivilTime(y)); } +TEST(CivilTime, Parse) { + absl::CivilSecond ss; + absl::CivilMinute mm; + absl::CivilHour hh; + absl::CivilDay d; + absl::CivilMonth m; + absl::CivilYear y; + + // CivilSecond OK; others fail + EXPECT_TRUE(absl::ParseCivilTime("2015-01-02T03:04:05", &ss)); + EXPECT_EQ("2015-01-02T03:04:05", absl::FormatCivilTime(ss)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03:04:05", &mm)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03:04:05", &hh)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03:04:05", &d)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03:04:05", &m)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03:04:05", &y)); + + // CivilMinute OK; others fail + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03:04", &ss)); + EXPECT_TRUE(absl::ParseCivilTime("2015-01-02T03:04", &mm)); + EXPECT_EQ("2015-01-02T03:04", absl::FormatCivilTime(mm)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03:04", &hh)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03:04", &d)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03:04", &m)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03:04", &y)); + + // CivilHour OK; others fail + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03", &ss)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03", &mm)); + EXPECT_TRUE(absl::ParseCivilTime("2015-01-02T03", &hh)); + EXPECT_EQ("2015-01-02T03", absl::FormatCivilTime(hh)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03", &d)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03", &m)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02T03", &y)); + + // CivilDay OK; others fail + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02", &ss)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02", &mm)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02", &hh)); + EXPECT_TRUE(absl::ParseCivilTime("2015-01-02", &d)); + EXPECT_EQ("2015-01-02", absl::FormatCivilTime(d)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02", &m)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01-02", &y)); + + // CivilMonth OK; others fail + EXPECT_FALSE(absl::ParseCivilTime("2015-01", &ss)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01", &mm)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01", &hh)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01", &d)); + EXPECT_TRUE(absl::ParseCivilTime("2015-01", &m)); + EXPECT_EQ("2015-01", absl::FormatCivilTime(m)); + EXPECT_FALSE(absl::ParseCivilTime("2015-01", &y)); + + // CivilYear OK; others fail + EXPECT_FALSE(absl::ParseCivilTime("2015", &ss)); + EXPECT_FALSE(absl::ParseCivilTime("2015", &mm)); + EXPECT_FALSE(absl::ParseCivilTime("2015", &hh)); + EXPECT_FALSE(absl::ParseCivilTime("2015", &d)); + EXPECT_FALSE(absl::ParseCivilTime("2015", &m)); + EXPECT_TRUE(absl::ParseCivilTime("2015", &y)); + EXPECT_EQ("2015", absl::FormatCivilTime(y)); +} + TEST(CivilTime, FormatAndParseLenient) { absl::CivilSecond ss; EXPECT_EQ("1970-01-01T00:00:00", absl::FormatCivilTime(ss)); @@ -708,6 +771,101 @@ TEST(CivilTime, FormatAndParseLenient) { absl::CivilYear y; EXPECT_EQ("1970", absl::FormatCivilTime(y)); + + EXPECT_TRUE(absl::ParseLenientCivilTime("2015-01-02T03:04:05", &ss)); + EXPECT_EQ("2015-01-02T03:04:05", absl::FormatCivilTime(ss)); + + EXPECT_TRUE(absl::ParseLenientCivilTime("2015-01-02T03:04:05", &mm)); + EXPECT_EQ("2015-01-02T03:04", absl::FormatCivilTime(mm)); + + EXPECT_TRUE(absl::ParseLenientCivilTime("2015-01-02T03:04:05", &hh)); + EXPECT_EQ("2015-01-02T03", absl::FormatCivilTime(hh)); + + EXPECT_TRUE(absl::ParseLenientCivilTime("2015-01-02T03:04:05", &d)); + EXPECT_EQ("2015-01-02", absl::FormatCivilTime(d)); + + EXPECT_TRUE(absl::ParseLenientCivilTime("2015-01-02T03:04:05", &m)); + EXPECT_EQ("2015-01", absl::FormatCivilTime(m)); + + EXPECT_TRUE(absl::ParseLenientCivilTime("2015-01-02T03:04:05", &y)); + EXPECT_EQ("2015", absl::FormatCivilTime(y)); +} + +TEST(CivilTime, ParseEdgeCases) { + absl::CivilSecond ss; + EXPECT_TRUE( + absl::ParseLenientCivilTime("9223372036854775807-12-31T23:59:59", &ss)); + EXPECT_EQ("9223372036854775807-12-31T23:59:59", absl::FormatCivilTime(ss)); + EXPECT_TRUE( + absl::ParseLenientCivilTime("-9223372036854775808-01-01T00:00:00", &ss)); + EXPECT_EQ("-9223372036854775808-01-01T00:00:00", absl::FormatCivilTime(ss)); + + absl::CivilMinute mm; + EXPECT_TRUE( + absl::ParseLenientCivilTime("9223372036854775807-12-31T23:59", &mm)); + EXPECT_EQ("9223372036854775807-12-31T23:59", absl::FormatCivilTime(mm)); + EXPECT_TRUE( + absl::ParseLenientCivilTime("-9223372036854775808-01-01T00:00", &mm)); + EXPECT_EQ("-9223372036854775808-01-01T00:00", absl::FormatCivilTime(mm)); + + absl::CivilHour hh; + EXPECT_TRUE( + absl::ParseLenientCivilTime("9223372036854775807-12-31T23", &hh)); + EXPECT_EQ("9223372036854775807-12-31T23", absl::FormatCivilTime(hh)); + EXPECT_TRUE( + absl::ParseLenientCivilTime("-9223372036854775808-01-01T00", &hh)); + EXPECT_EQ("-9223372036854775808-01-01T00", absl::FormatCivilTime(hh)); + + absl::CivilDay d; + EXPECT_TRUE(absl::ParseLenientCivilTime("9223372036854775807-12-31", &d)); + EXPECT_EQ("9223372036854775807-12-31", absl::FormatCivilTime(d)); + EXPECT_TRUE(absl::ParseLenientCivilTime("-9223372036854775808-01-01", &d)); + EXPECT_EQ("-9223372036854775808-01-01", absl::FormatCivilTime(d)); + + absl::CivilMonth m; + EXPECT_TRUE(absl::ParseLenientCivilTime("9223372036854775807-12", &m)); + EXPECT_EQ("9223372036854775807-12", absl::FormatCivilTime(m)); + EXPECT_TRUE(absl::ParseLenientCivilTime("-9223372036854775808-01", &m)); + EXPECT_EQ("-9223372036854775808-01", absl::FormatCivilTime(m)); + + absl::CivilYear y; + EXPECT_TRUE(absl::ParseLenientCivilTime("9223372036854775807", &y)); + EXPECT_EQ("9223372036854775807", absl::FormatCivilTime(y)); + EXPECT_TRUE(absl::ParseLenientCivilTime("-9223372036854775808", &y)); + EXPECT_EQ("-9223372036854775808", absl::FormatCivilTime(y)); + + // Tests some valid, but interesting, cases + EXPECT_TRUE(absl::ParseLenientCivilTime("0", &ss)) << ss; + EXPECT_EQ(absl::CivilYear(0), ss); + EXPECT_TRUE(absl::ParseLenientCivilTime("0-1", &ss)) << ss; + EXPECT_EQ(absl::CivilMonth(0, 1), ss); + EXPECT_TRUE(absl::ParseLenientCivilTime(" 2015 ", &ss)) << ss; + EXPECT_EQ(absl::CivilYear(2015), ss); + EXPECT_TRUE(absl::ParseLenientCivilTime(" 2015-6 ", &ss)) << ss; + EXPECT_EQ(absl::CivilMonth(2015, 6), ss); + EXPECT_TRUE(absl::ParseLenientCivilTime("2015-6-7", &ss)) << ss; + EXPECT_EQ(absl::CivilDay(2015, 6, 7), ss); + EXPECT_TRUE(absl::ParseLenientCivilTime(" 2015-6-7 ", &ss)) << ss; + EXPECT_EQ(absl::CivilDay(2015, 6, 7), ss); + EXPECT_TRUE(absl::ParseLenientCivilTime("2015-06-07T10:11:12 ", &ss)) << ss; + EXPECT_EQ(absl::CivilSecond(2015, 6, 7, 10, 11, 12), ss); + EXPECT_TRUE(absl::ParseLenientCivilTime(" 2015-06-07T10:11:12 ", &ss)) << ss; + EXPECT_EQ(absl::CivilSecond(2015, 6, 7, 10, 11, 12), ss); + EXPECT_TRUE(absl::ParseLenientCivilTime("-01-01", &ss)) << ss; + EXPECT_EQ(absl::CivilMonth(-1, 1), ss); + + // Tests some invalid cases + EXPECT_FALSE(absl::ParseLenientCivilTime("01-01-2015", &ss)) << ss; + EXPECT_FALSE(absl::ParseLenientCivilTime("2015-", &ss)) << ss; + EXPECT_FALSE(absl::ParseLenientCivilTime("0xff-01", &ss)) << ss; + EXPECT_FALSE(absl::ParseLenientCivilTime("2015-02-30T04:05:06", &ss)) << ss; + EXPECT_FALSE(absl::ParseLenientCivilTime("2015-02-03T04:05:96", &ss)) << ss; + EXPECT_FALSE(absl::ParseLenientCivilTime("X2015-02-03T04:05:06", &ss)) << ss; + EXPECT_FALSE(absl::ParseLenientCivilTime("2015-02-03T04:05:003", &ss)) << ss; + EXPECT_FALSE(absl::ParseLenientCivilTime("2015 -02-03T04:05:06", &ss)) << ss; + EXPECT_FALSE(absl::ParseLenientCivilTime("2015-02-03-04:05:06", &ss)) << ss; + EXPECT_FALSE(absl::ParseLenientCivilTime("2015:02:03T04-05-06", &ss)) << ss; + EXPECT_FALSE(absl::ParseLenientCivilTime("9223372036854775808", &y)) << y; } TEST(CivilTime, OutputStream) { |