blob: 1730b0fdc1210b52f62c6c0c7e756bf654c9a691 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001// Copyright 2013 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package model
15
16import (
khenaidood948f772021-08-11 17:49:24 -040017 "encoding/json"
18 "errors"
khenaidooab1f7bd2019-11-14 14:00:27 -050019 "fmt"
20 "math"
khenaidooab1f7bd2019-11-14 14:00:27 -050021 "strconv"
22 "strings"
23 "time"
24)
25
26const (
27 // MinimumTick is the minimum supported time resolution. This has to be
28 // at least time.Second in order for the code below to work.
29 minimumTick = time.Millisecond
30 // second is the Time duration equivalent to one second.
31 second = int64(time.Second / minimumTick)
32 // The number of nanoseconds per minimum tick.
33 nanosPerTick = int64(minimumTick / time.Nanosecond)
34
35 // Earliest is the earliest Time representable. Handy for
36 // initializing a high watermark.
37 Earliest = Time(math.MinInt64)
38 // Latest is the latest Time representable. Handy for initializing
39 // a low watermark.
40 Latest = Time(math.MaxInt64)
41)
42
43// Time is the number of milliseconds since the epoch
44// (1970-01-01 00:00 UTC) excluding leap seconds.
45type Time int64
46
47// Interval describes an interval between two timestamps.
48type Interval struct {
49 Start, End Time
50}
51
52// Now returns the current time as a Time.
53func Now() Time {
54 return TimeFromUnixNano(time.Now().UnixNano())
55}
56
57// TimeFromUnix returns the Time equivalent to the Unix Time t
58// provided in seconds.
59func TimeFromUnix(t int64) Time {
60 return Time(t * second)
61}
62
63// TimeFromUnixNano returns the Time equivalent to the Unix Time
64// t provided in nanoseconds.
65func TimeFromUnixNano(t int64) Time {
66 return Time(t / nanosPerTick)
67}
68
69// Equal reports whether two Times represent the same instant.
70func (t Time) Equal(o Time) bool {
71 return t == o
72}
73
74// Before reports whether the Time t is before o.
75func (t Time) Before(o Time) bool {
76 return t < o
77}
78
79// After reports whether the Time t is after o.
80func (t Time) After(o Time) bool {
81 return t > o
82}
83
84// Add returns the Time t + d.
85func (t Time) Add(d time.Duration) Time {
86 return t + Time(d/minimumTick)
87}
88
89// Sub returns the Duration t - o.
90func (t Time) Sub(o Time) time.Duration {
91 return time.Duration(t-o) * minimumTick
92}
93
94// Time returns the time.Time representation of t.
95func (t Time) Time() time.Time {
96 return time.Unix(int64(t)/second, (int64(t)%second)*nanosPerTick)
97}
98
99// Unix returns t as a Unix time, the number of seconds elapsed
100// since January 1, 1970 UTC.
101func (t Time) Unix() int64 {
102 return int64(t) / second
103}
104
105// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
106// since January 1, 1970 UTC.
107func (t Time) UnixNano() int64 {
108 return int64(t) * nanosPerTick
109}
110
111// The number of digits after the dot.
112var dotPrecision = int(math.Log10(float64(second)))
113
114// String returns a string representation of the Time.
115func (t Time) String() string {
116 return strconv.FormatFloat(float64(t)/float64(second), 'f', -1, 64)
117}
118
119// MarshalJSON implements the json.Marshaler interface.
120func (t Time) MarshalJSON() ([]byte, error) {
121 return []byte(t.String()), nil
122}
123
124// UnmarshalJSON implements the json.Unmarshaler interface.
125func (t *Time) UnmarshalJSON(b []byte) error {
126 p := strings.Split(string(b), ".")
127 switch len(p) {
128 case 1:
Abhay Kumara2ae5992025-11-10 14:02:24 +0000129 v, err := strconv.ParseInt(p[0], 10, 64)
khenaidooab1f7bd2019-11-14 14:00:27 -0500130 if err != nil {
131 return err
132 }
133 *t = Time(v * second)
134
135 case 2:
Abhay Kumara2ae5992025-11-10 14:02:24 +0000136 v, err := strconv.ParseInt(p[0], 10, 64)
khenaidooab1f7bd2019-11-14 14:00:27 -0500137 if err != nil {
138 return err
139 }
140 v *= second
141
142 prec := dotPrecision - len(p[1])
143 if prec < 0 {
144 p[1] = p[1][:dotPrecision]
145 } else if prec > 0 {
Abhay Kumara2ae5992025-11-10 14:02:24 +0000146 p[1] += strings.Repeat("0", prec)
khenaidooab1f7bd2019-11-14 14:00:27 -0500147 }
148
149 va, err := strconv.ParseInt(p[1], 10, 32)
150 if err != nil {
151 return err
152 }
153
154 // If the value was something like -0.1 the negative is lost in the
155 // parsing because of the leading zero, this ensures that we capture it.
156 if len(p[0]) > 0 && p[0][0] == '-' && v+va > 0 {
157 *t = Time(v+va) * -1
158 } else {
159 *t = Time(v + va)
160 }
161
162 default:
163 return fmt.Errorf("invalid time %q", string(b))
164 }
165 return nil
166}
167
168// Duration wraps time.Duration. It is used to parse the custom duration format
169// from YAML.
170// This type should not propagate beyond the scope of input/output processing.
171type Duration time.Duration
172
Abhay Kumara2ae5992025-11-10 14:02:24 +0000173// Set implements pflag/flag.Value.
khenaidooab1f7bd2019-11-14 14:00:27 -0500174func (d *Duration) Set(s string) error {
175 var err error
176 *d, err = ParseDuration(s)
177 return err
178}
179
Abhay Kumara2ae5992025-11-10 14:02:24 +0000180// Type implements pflag.Value.
181func (*Duration) Type() string {
khenaidooab1f7bd2019-11-14 14:00:27 -0500182 return "duration"
183}
184
Abhay Kumara2ae5992025-11-10 14:02:24 +0000185func isdigit(c byte) bool { return c >= '0' && c <= '9' }
186
187// Units are required to go in order from biggest to smallest.
188// This guards against confusion from "1m1d" being 1 minute + 1 day, not 1 month + 1 day.
189var unitMap = map[string]struct {
190 pos int
191 mult uint64
192}{
193 "ms": {7, uint64(time.Millisecond)},
194 "s": {6, uint64(time.Second)},
195 "m": {5, uint64(time.Minute)},
196 "h": {4, uint64(time.Hour)},
197 "d": {3, uint64(24 * time.Hour)},
198 "w": {2, uint64(7 * 24 * time.Hour)},
199 "y": {1, uint64(365 * 24 * time.Hour)},
200}
khenaidooab1f7bd2019-11-14 14:00:27 -0500201
202// ParseDuration parses a string into a time.Duration, assuming that a year
203// always has 365d, a week always has 7d, and a day always has 24h.
Abhay Kumara2ae5992025-11-10 14:02:24 +0000204// Negative durations are not supported.
205func ParseDuration(s string) (Duration, error) {
206 switch s {
khenaidood948f772021-08-11 17:49:24 -0400207 case "0":
208 // Allow 0 without a unit.
209 return 0, nil
210 case "":
Abhay Kumara2ae5992025-11-10 14:02:24 +0000211 return 0, errors.New("empty duration string")
khenaidood948f772021-08-11 17:49:24 -0400212 }
khenaidood948f772021-08-11 17:49:24 -0400213
Abhay Kumara2ae5992025-11-10 14:02:24 +0000214 orig := s
215 var dur uint64
216 lastUnitPos := 0
217
218 for s != "" {
219 if !isdigit(s[0]) {
220 return 0, fmt.Errorf("not a valid duration string: %q", orig)
khenaidood948f772021-08-11 17:49:24 -0400221 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000222 // Consume [0-9]*
223 i := 0
224 for ; i < len(s) && isdigit(s[i]); i++ {
225 }
226 v, err := strconv.ParseUint(s[:i], 10, 0)
227 if err != nil {
228 return 0, fmt.Errorf("not a valid duration string: %q", orig)
229 }
230 s = s[i:]
khenaidood948f772021-08-11 17:49:24 -0400231
Abhay Kumara2ae5992025-11-10 14:02:24 +0000232 // Consume unit.
233 for i = 0; i < len(s) && !isdigit(s[i]); i++ {
234 }
235 if i == 0 {
236 return 0, fmt.Errorf("not a valid duration string: %q", orig)
237 }
238 u := s[:i]
239 s = s[i:]
240 unit, ok := unitMap[u]
241 if !ok {
242 return 0, fmt.Errorf("unknown unit %q in duration %q", u, orig)
243 }
244 if unit.pos <= lastUnitPos { // Units must go in order from biggest to smallest.
245 return 0, fmt.Errorf("not a valid duration string: %q", orig)
246 }
247 lastUnitPos = unit.pos
khenaidood948f772021-08-11 17:49:24 -0400248 // Check if the provided duration overflows time.Duration (> ~ 290years).
Abhay Kumara2ae5992025-11-10 14:02:24 +0000249 if v > 1<<63/unit.mult {
250 return 0, errors.New("duration out of range")
khenaidood948f772021-08-11 17:49:24 -0400251 }
Abhay Kumara2ae5992025-11-10 14:02:24 +0000252 dur += v * unit.mult
253 if dur > 1<<63-1 {
254 return 0, errors.New("duration out of range")
khenaidood948f772021-08-11 17:49:24 -0400255 }
khenaidooab1f7bd2019-11-14 14:00:27 -0500256 }
khenaidood948f772021-08-11 17:49:24 -0400257
Abhay Kumara2ae5992025-11-10 14:02:24 +0000258 return Duration(dur), nil
259}
khenaidood948f772021-08-11 17:49:24 -0400260
Abhay Kumara2ae5992025-11-10 14:02:24 +0000261// ParseDurationAllowNegative is like ParseDuration but also accepts negative durations.
262func ParseDurationAllowNegative(s string) (Duration, error) {
263 if s == "" || s[0] != '-' {
264 return ParseDuration(s)
265 }
266
267 d, err := ParseDuration(s[1:])
268
269 return -d, err
khenaidooab1f7bd2019-11-14 14:00:27 -0500270}
271
272func (d Duration) String() string {
273 var (
Abhay Kumara2ae5992025-11-10 14:02:24 +0000274 ms = int64(time.Duration(d) / time.Millisecond)
275 r = ""
276 sign = ""
khenaidooab1f7bd2019-11-14 14:00:27 -0500277 )
Abhay Kumara2ae5992025-11-10 14:02:24 +0000278
khenaidooab1f7bd2019-11-14 14:00:27 -0500279 if ms == 0 {
280 return "0s"
281 }
khenaidood948f772021-08-11 17:49:24 -0400282
Abhay Kumara2ae5992025-11-10 14:02:24 +0000283 if ms < 0 {
284 sign, ms = "-", -ms
285 }
286
khenaidood948f772021-08-11 17:49:24 -0400287 f := func(unit string, mult int64, exact bool) {
288 if exact && ms%mult != 0 {
289 return
290 }
291 if v := ms / mult; v > 0 {
292 r += fmt.Sprintf("%d%s", v, unit)
293 ms -= v * mult
294 }
khenaidooab1f7bd2019-11-14 14:00:27 -0500295 }
296
khenaidood948f772021-08-11 17:49:24 -0400297 // Only format years and weeks if the remainder is zero, as it is often
298 // easier to read 90d than 12w6d.
299 f("y", 1000*60*60*24*365, true)
300 f("w", 1000*60*60*24*7, true)
301
302 f("d", 1000*60*60*24, false)
303 f("h", 1000*60*60, false)
304 f("m", 1000*60, false)
305 f("s", 1000, false)
306 f("ms", 1, false)
307
Abhay Kumara2ae5992025-11-10 14:02:24 +0000308 return sign + r
khenaidood948f772021-08-11 17:49:24 -0400309}
310
311// MarshalJSON implements the json.Marshaler interface.
312func (d Duration) MarshalJSON() ([]byte, error) {
313 return json.Marshal(d.String())
314}
315
316// UnmarshalJSON implements the json.Unmarshaler interface.
317func (d *Duration) UnmarshalJSON(bytes []byte) error {
318 var s string
319 if err := json.Unmarshal(bytes, &s); err != nil {
320 return err
khenaidooab1f7bd2019-11-14 14:00:27 -0500321 }
khenaidood948f772021-08-11 17:49:24 -0400322 dur, err := ParseDuration(s)
323 if err != nil {
324 return err
325 }
326 *d = dur
327 return nil
328}
329
330// MarshalText implements the encoding.TextMarshaler interface.
331func (d *Duration) MarshalText() ([]byte, error) {
332 return []byte(d.String()), nil
333}
334
335// UnmarshalText implements the encoding.TextUnmarshaler interface.
336func (d *Duration) UnmarshalText(text []byte) error {
337 var err error
338 *d, err = ParseDuration(string(text))
339 return err
khenaidooab1f7bd2019-11-14 14:00:27 -0500340}
341
342// MarshalYAML implements the yaml.Marshaler interface.
343func (d Duration) MarshalYAML() (interface{}, error) {
344 return d.String(), nil
345}
346
347// UnmarshalYAML implements the yaml.Unmarshaler interface.
348func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
349 var s string
350 if err := unmarshal(&s); err != nil {
351 return err
352 }
353 dur, err := ParseDuration(s)
354 if err != nil {
355 return err
356 }
357 *d = dur
358 return nil
359}