• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/time/time_delta_from_string.h"
6 
7 #include <limits>
8 #include <utility>
9 
10 #include "base/strings/string_util.h"
11 #include "base/time/time.h"
12 
13 namespace base {
14 
15 namespace {
16 
17 // Strips the |expected| prefix from the start of the given string, returning
18 // |true| if the strip operation succeeded or false otherwise.
19 //
20 // Example:
21 //
22 //   StringPiece input("abc");
23 //   EXPECT_TRUE(ConsumePrefix(input, "a"));
24 //   EXPECT_EQ(input, "bc");
25 //
26 // Adapted from absl::ConsumePrefix():
27 // https://cs.chromium.org/chromium/src/third_party/abseil-cpp/absl/strings/strip.h?l=45&rcl=2c22e9135f107a4319582ae52e2e3e6b201b6b7c
ConsumePrefix(StringPiece & str,StringPiece expected)28 bool ConsumePrefix(StringPiece& str, StringPiece expected) {
29   if (!StartsWith(str, expected))
30     return false;
31   str.remove_prefix(expected.size());
32   return true;
33 }
34 
35 // Utility struct used by ConsumeDurationNumber() to parse decimal numbers.
36 // A ParsedDecimal represents the number `int_part` + `frac_part`/`frac_scale`,
37 // where:
38 //  (i)  0 <= `frac_part` < `frac_scale` (implies `frac_part`/`frac_scale` < 1)
39 //  (ii) `frac_scale` is 10^[number of digits after the decimal point]
40 //
41 // Example:
42 //  -42 => {.int_part = -42, .frac_part = 0, .frac_scale = 1}
43 //  1.23 => {.int_part = 1, .frac_part = 23, .frac_scale = 100}
44 struct ParsedDecimal {
45   int64_t int_part = 0;
46   int64_t frac_part = 0;
47   int64_t frac_scale = 1;
48 };
49 
50 // A helper for FromString() that tries to parse a leading number from the given
51 // StringPiece. |number_string| is modified to start from the first unconsumed
52 // char.
53 //
54 // Adapted from absl:
55 // https://cs.chromium.org/chromium/src/third_party/abseil-cpp/absl/time/duration.cc?l=807&rcl=2c22e9135f107a4319582ae52e2e3e6b201b6b7c
ConsumeDurationNumber(StringPiece & number_string)56 constexpr absl::optional<ParsedDecimal> ConsumeDurationNumber(
57     StringPiece& number_string) {
58   ParsedDecimal res;
59   StringPiece::const_iterator orig_start = number_string.begin();
60   // Parse contiguous digits.
61   for (; !number_string.empty(); number_string.remove_prefix(1)) {
62     const int d = number_string.front() - '0';
63     if (d < 0 || d >= 10)
64       break;
65 
66     if (res.int_part > std::numeric_limits<int64_t>::max() / 10)
67       return absl::nullopt;
68     res.int_part *= 10;
69     if (res.int_part > std::numeric_limits<int64_t>::max() - d)
70       return absl::nullopt;
71     res.int_part += d;
72   }
73   const bool int_part_empty = number_string.begin() == orig_start;
74   if (number_string.empty() || number_string.front() != '.')
75     return int_part_empty ? absl::nullopt : absl::make_optional(res);
76 
77   number_string.remove_prefix(1);  // consume '.'
78   // Parse contiguous digits.
79   for (; !number_string.empty(); number_string.remove_prefix(1)) {
80     const int d = number_string.front() - '0';
81     if (d < 0 || d >= 10)
82       break;
83     DCHECK_LT(res.frac_part, res.frac_scale);
84     if (res.frac_scale <= std::numeric_limits<int64_t>::max() / 10) {
85       // |frac_part| will not overflow because it is always < |frac_scale|.
86       res.frac_part *= 10;
87       res.frac_part += d;
88       res.frac_scale *= 10;
89     }
90   }
91 
92   return int_part_empty && res.frac_scale == 1 ? absl::nullopt
93                                                : absl::make_optional(res);
94 }
95 
96 // A helper for FromString() that tries to parse a leading unit designator
97 // (e.g., ns, us, ms, s, m, h, d) from the given StringPiece. |unit_string| is
98 // modified to start from the first unconsumed char.
99 //
100 // Adapted from absl:
101 // https://cs.chromium.org/chromium/src/third_party/abseil-cpp/absl/time/duration.cc?l=841&rcl=2c22e9135f107a4319582ae52e2e3e6b201b6b7c
ConsumeDurationUnit(StringPiece & unit_string)102 absl::optional<TimeDelta> ConsumeDurationUnit(StringPiece& unit_string) {
103   for (const auto& str_delta : {
104            std::make_pair("ns", Nanoseconds(1)),
105            std::make_pair("us", Microseconds(1)),
106            // Note: "ms" MUST be checked before "m" to ensure that milliseconds
107            // are not parsed as minutes.
108            std::make_pair("ms", Milliseconds(1)),
109            std::make_pair("s", Seconds(1)),
110            std::make_pair("m", Minutes(1)),
111            std::make_pair("h", Hours(1)),
112            std::make_pair("d", Days(1)),
113        }) {
114     if (ConsumePrefix(unit_string, str_delta.first))
115       return str_delta.second;
116   }
117 
118   return absl::nullopt;
119 }
120 
121 }  // namespace
122 
TimeDeltaFromString(StringPiece duration_string)123 absl::optional<TimeDelta> TimeDeltaFromString(StringPiece duration_string) {
124   int sign = 1;
125   if (ConsumePrefix(duration_string, "-"))
126     sign = -1;
127   else
128     ConsumePrefix(duration_string, "+");
129   if (duration_string.empty())
130     return absl::nullopt;
131 
132   // Handle special-case values that don't require units.
133   if (duration_string == "0")
134     return TimeDelta();
135   if (duration_string == "inf")
136     return sign == 1 ? TimeDelta::Max() : TimeDelta::Min();
137 
138   TimeDelta delta;
139   while (!duration_string.empty()) {
140     absl::optional<ParsedDecimal> number_opt =
141         ConsumeDurationNumber(duration_string);
142     if (!number_opt.has_value())
143       return absl::nullopt;
144     absl::optional<TimeDelta> unit_opt = ConsumeDurationUnit(duration_string);
145     if (!unit_opt.has_value())
146       return absl::nullopt;
147 
148     ParsedDecimal number = number_opt.value();
149     TimeDelta unit = unit_opt.value();
150     if (number.int_part != 0)
151       delta += sign * number.int_part * unit;
152     if (number.frac_part != 0)
153       delta +=
154           (static_cast<double>(sign) * number.frac_part / number.frac_scale) *
155           unit;
156   }
157   return delta;
158 }
159 
160 }  // namespace base
161