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