| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/time/time_delta_from_string.h" |
| |
| #include <limits> |
| #include <utility> |
| |
| #include "base/strings/string_util.h" |
| #include "base/time/time.h" |
| |
| namespace base { |
| |
| namespace { |
| |
| // Strips the |expected| prefix from the start of the given string, returning |
| // |true| if the strip operation succeeded or false otherwise. |
| // |
| // Example: |
| // |
| // StringPiece input("abc"); |
| // EXPECT_TRUE(ConsumePrefix(input, "a")); |
| // EXPECT_EQ(input, "bc"); |
| // |
| // Adapted from absl::ConsumePrefix(): |
| // https://cs.chromium.org/chromium/src/third_party/abseil-cpp/absl/strings/strip.h?l=45&rcl=2c22e9135f107a4319582ae52e2e3e6b201b6b7c |
| bool ConsumePrefix(StringPiece& str, StringPiece expected) { |
| if (!StartsWith(str, expected)) |
| return false; |
| str.remove_prefix(expected.size()); |
| return true; |
| } |
| |
| // Utility struct used by ConsumeDurationNumber() to parse decimal numbers. |
| // A ParsedDecimal represents the number `int_part` + `frac_part`/`frac_scale`, |
| // where: |
| // (i) 0 <= `frac_part` < `frac_scale` (implies `frac_part`/`frac_scale` < 1) |
| // (ii) `frac_scale` is 10^[number of digits after the decimal point] |
| // |
| // Example: |
| // -42 => {.int_part = -42, .frac_part = 0, .frac_scale = 1} |
| // 1.23 => {.int_part = 1, .frac_part = 23, .frac_scale = 100} |
| struct ParsedDecimal { |
| int64_t int_part = 0; |
| int64_t frac_part = 0; |
| int64_t frac_scale = 1; |
| }; |
| |
| // A helper for FromString() that tries to parse a leading number from the given |
| // StringPiece. |number_string| is modified to start from the first unconsumed |
| // char. |
| // |
| // Adapted from absl: |
| // https://cs.chromium.org/chromium/src/third_party/abseil-cpp/absl/time/duration.cc?l=807&rcl=2c22e9135f107a4319582ae52e2e3e6b201b6b7c |
| constexpr absl::optional<ParsedDecimal> ConsumeDurationNumber( |
| StringPiece& number_string) { |
| ParsedDecimal res; |
| StringPiece::const_iterator orig_start = number_string.begin(); |
| // Parse contiguous digits. |
| for (; !number_string.empty(); number_string.remove_prefix(1)) { |
| const int d = number_string.front() - '0'; |
| if (d < 0 || d >= 10) |
| break; |
| |
| if (res.int_part > std::numeric_limits<int64_t>::max() / 10) |
| return absl::nullopt; |
| res.int_part *= 10; |
| if (res.int_part > std::numeric_limits<int64_t>::max() - d) |
| return absl::nullopt; |
| res.int_part += d; |
| } |
| const bool int_part_empty = number_string.begin() == orig_start; |
| if (number_string.empty() || number_string.front() != '.') |
| return int_part_empty ? absl::nullopt : absl::make_optional(res); |
| |
| number_string.remove_prefix(1); // consume '.' |
| // Parse contiguous digits. |
| for (; !number_string.empty(); number_string.remove_prefix(1)) { |
| const int d = number_string.front() - '0'; |
| if (d < 0 || d >= 10) |
| break; |
| DCHECK_LT(res.frac_part, res.frac_scale); |
| if (res.frac_scale <= std::numeric_limits<int64_t>::max() / 10) { |
| // |frac_part| will not overflow because it is always < |frac_scale|. |
| res.frac_part *= 10; |
| res.frac_part += d; |
| res.frac_scale *= 10; |
| } |
| } |
| |
| return int_part_empty && res.frac_scale == 1 ? absl::nullopt |
| : absl::make_optional(res); |
| } |
| |
| // A helper for FromString() that tries to parse a leading unit designator |
| // (e.g., ns, us, ms, s, m, h, d) from the given StringPiece. |unit_string| is |
| // modified to start from the first unconsumed char. |
| // |
| // Adapted from absl: |
| // https://cs.chromium.org/chromium/src/third_party/abseil-cpp/absl/time/duration.cc?l=841&rcl=2c22e9135f107a4319582ae52e2e3e6b201b6b7c |
| absl::optional<TimeDelta> ConsumeDurationUnit(StringPiece& unit_string) { |
| for (const auto& str_delta : { |
| std::make_pair("ns", Nanoseconds(1)), |
| std::make_pair("us", Microseconds(1)), |
| // Note: "ms" MUST be checked before "m" to ensure that milliseconds |
| // are not parsed as minutes. |
| std::make_pair("ms", Milliseconds(1)), |
| std::make_pair("s", Seconds(1)), |
| std::make_pair("m", Minutes(1)), |
| std::make_pair("h", Hours(1)), |
| std::make_pair("d", Days(1)), |
| }) { |
| if (ConsumePrefix(unit_string, str_delta.first)) |
| return str_delta.second; |
| } |
| |
| return absl::nullopt; |
| } |
| |
| } // namespace |
| |
| absl::optional<TimeDelta> TimeDeltaFromString(StringPiece duration_string) { |
| int sign = 1; |
| if (ConsumePrefix(duration_string, "-")) |
| sign = -1; |
| else |
| ConsumePrefix(duration_string, "+"); |
| if (duration_string.empty()) |
| return absl::nullopt; |
| |
| // Handle special-case values that don't require units. |
| if (duration_string == "0") |
| return TimeDelta(); |
| if (duration_string == "inf") |
| return sign == 1 ? TimeDelta::Max() : TimeDelta::Min(); |
| |
| TimeDelta delta; |
| while (!duration_string.empty()) { |
| absl::optional<ParsedDecimal> number_opt = |
| ConsumeDurationNumber(duration_string); |
| if (!number_opt.has_value()) |
| return absl::nullopt; |
| absl::optional<TimeDelta> unit_opt = ConsumeDurationUnit(duration_string); |
| if (!unit_opt.has_value()) |
| return absl::nullopt; |
| |
| ParsedDecimal number = number_opt.value(); |
| TimeDelta unit = unit_opt.value(); |
| if (number.int_part != 0) |
| delta += sign * number.int_part * unit; |
| if (number.frac_part != 0) |
| delta += |
| (static_cast<double>(sign) * number.frac_part / number.frac_scale) * |
| unit; |
| } |
| return delta; |
| } |
| |
| } // namespace base |