blob: 7f67b16e429557ae4e523ad89d62d8ed6480f0b2 [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"
21 "regexp"
22 "strconv"
23 "strings"
24 "time"
25)
26
27const (
28 // MinimumTick is the minimum supported time resolution. This has to be
29 // at least time.Second in order for the code below to work.
30 minimumTick = time.Millisecond
31 // second is the Time duration equivalent to one second.
32 second = int64(time.Second / minimumTick)
33 // The number of nanoseconds per minimum tick.
34 nanosPerTick = int64(minimumTick / time.Nanosecond)
35
36 // Earliest is the earliest Time representable. Handy for
37 // initializing a high watermark.
38 Earliest = Time(math.MinInt64)
39 // Latest is the latest Time representable. Handy for initializing
40 // a low watermark.
41 Latest = Time(math.MaxInt64)
42)
43
44// Time is the number of milliseconds since the epoch
45// (1970-01-01 00:00 UTC) excluding leap seconds.
46type Time int64
47
48// Interval describes an interval between two timestamps.
49type Interval struct {
50 Start, End Time
51}
52
53// Now returns the current time as a Time.
54func Now() Time {
55 return TimeFromUnixNano(time.Now().UnixNano())
56}
57
58// TimeFromUnix returns the Time equivalent to the Unix Time t
59// provided in seconds.
60func TimeFromUnix(t int64) Time {
61 return Time(t * second)
62}
63
64// TimeFromUnixNano returns the Time equivalent to the Unix Time
65// t provided in nanoseconds.
66func TimeFromUnixNano(t int64) Time {
67 return Time(t / nanosPerTick)
68}
69
70// Equal reports whether two Times represent the same instant.
71func (t Time) Equal(o Time) bool {
72 return t == o
73}
74
75// Before reports whether the Time t is before o.
76func (t Time) Before(o Time) bool {
77 return t < o
78}
79
80// After reports whether the Time t is after o.
81func (t Time) After(o Time) bool {
82 return t > o
83}
84
85// Add returns the Time t + d.
86func (t Time) Add(d time.Duration) Time {
87 return t + Time(d/minimumTick)
88}
89
90// Sub returns the Duration t - o.
91func (t Time) Sub(o Time) time.Duration {
92 return time.Duration(t-o) * minimumTick
93}
94
95// Time returns the time.Time representation of t.
96func (t Time) Time() time.Time {
97 return time.Unix(int64(t)/second, (int64(t)%second)*nanosPerTick)
98}
99
100// Unix returns t as a Unix time, the number of seconds elapsed
101// since January 1, 1970 UTC.
102func (t Time) Unix() int64 {
103 return int64(t) / second
104}
105
106// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
107// since January 1, 1970 UTC.
108func (t Time) UnixNano() int64 {
109 return int64(t) * nanosPerTick
110}
111
112// The number of digits after the dot.
113var dotPrecision = int(math.Log10(float64(second)))
114
115// String returns a string representation of the Time.
116func (t Time) String() string {
117 return strconv.FormatFloat(float64(t)/float64(second), 'f', -1, 64)
118}
119
120// MarshalJSON implements the json.Marshaler interface.
121func (t Time) MarshalJSON() ([]byte, error) {
122 return []byte(t.String()), nil
123}
124
125// UnmarshalJSON implements the json.Unmarshaler interface.
126func (t *Time) UnmarshalJSON(b []byte) error {
127 p := strings.Split(string(b), ".")
128 switch len(p) {
129 case 1:
130 v, err := strconv.ParseInt(string(p[0]), 10, 64)
131 if err != nil {
132 return err
133 }
134 *t = Time(v * second)
135
136 case 2:
137 v, err := strconv.ParseInt(string(p[0]), 10, 64)
138 if err != nil {
139 return err
140 }
141 v *= second
142
143 prec := dotPrecision - len(p[1])
144 if prec < 0 {
145 p[1] = p[1][:dotPrecision]
146 } else if prec > 0 {
147 p[1] = p[1] + strings.Repeat("0", prec)
148 }
149
150 va, err := strconv.ParseInt(p[1], 10, 32)
151 if err != nil {
152 return err
153 }
154
155 // If the value was something like -0.1 the negative is lost in the
156 // parsing because of the leading zero, this ensures that we capture it.
157 if len(p[0]) > 0 && p[0][0] == '-' && v+va > 0 {
158 *t = Time(v+va) * -1
159 } else {
160 *t = Time(v + va)
161 }
162
163 default:
164 return fmt.Errorf("invalid time %q", string(b))
165 }
166 return nil
167}
168
169// Duration wraps time.Duration. It is used to parse the custom duration format
170// from YAML.
171// This type should not propagate beyond the scope of input/output processing.
172type Duration time.Duration
173
174// Set implements pflag/flag.Value
175func (d *Duration) Set(s string) error {
176 var err error
177 *d, err = ParseDuration(s)
178 return err
179}
180
181// Type implements pflag.Value
182func (d *Duration) Type() string {
183 return "duration"
184}
185
khenaidood948f772021-08-11 17:49:24 -0400186var durationRE = regexp.MustCompile("^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$")
khenaidooab1f7bd2019-11-14 14:00:27 -0500187
188// ParseDuration parses a string into a time.Duration, assuming that a year
189// always has 365d, a week always has 7d, and a day always has 24h.
190func ParseDuration(durationStr string) (Duration, error) {
khenaidood948f772021-08-11 17:49:24 -0400191 switch durationStr {
192 case "0":
193 // Allow 0 without a unit.
194 return 0, nil
195 case "":
196 return 0, fmt.Errorf("empty duration string")
197 }
khenaidooab1f7bd2019-11-14 14:00:27 -0500198 matches := durationRE.FindStringSubmatch(durationStr)
khenaidood948f772021-08-11 17:49:24 -0400199 if matches == nil {
khenaidooab1f7bd2019-11-14 14:00:27 -0500200 return 0, fmt.Errorf("not a valid duration string: %q", durationStr)
201 }
khenaidood948f772021-08-11 17:49:24 -0400202 var dur time.Duration
203
204 // Parse the match at pos `pos` in the regex and use `mult` to turn that
205 // into ms, then add that value to the total parsed duration.
206 var overflowErr error
207 m := func(pos int, mult time.Duration) {
208 if matches[pos] == "" {
209 return
210 }
211 n, _ := strconv.Atoi(matches[pos])
212
213 // Check if the provided duration overflows time.Duration (> ~ 290years).
214 if n > int((1<<63-1)/mult/time.Millisecond) {
215 overflowErr = errors.New("duration out of range")
216 }
217 d := time.Duration(n) * time.Millisecond
218 dur += d * mult
219
220 if dur < 0 {
221 overflowErr = errors.New("duration out of range")
222 }
khenaidooab1f7bd2019-11-14 14:00:27 -0500223 }
khenaidood948f772021-08-11 17:49:24 -0400224
225 m(2, 1000*60*60*24*365) // y
226 m(4, 1000*60*60*24*7) // w
227 m(6, 1000*60*60*24) // d
228 m(8, 1000*60*60) // h
229 m(10, 1000*60) // m
230 m(12, 1000) // s
231 m(14, 1) // ms
232
233 return Duration(dur), overflowErr
khenaidooab1f7bd2019-11-14 14:00:27 -0500234}
235
236func (d Duration) String() string {
237 var (
khenaidood948f772021-08-11 17:49:24 -0400238 ms = int64(time.Duration(d) / time.Millisecond)
239 r = ""
khenaidooab1f7bd2019-11-14 14:00:27 -0500240 )
241 if ms == 0 {
242 return "0s"
243 }
khenaidood948f772021-08-11 17:49:24 -0400244
245 f := func(unit string, mult int64, exact bool) {
246 if exact && ms%mult != 0 {
247 return
248 }
249 if v := ms / mult; v > 0 {
250 r += fmt.Sprintf("%d%s", v, unit)
251 ms -= v * mult
252 }
khenaidooab1f7bd2019-11-14 14:00:27 -0500253 }
254
khenaidood948f772021-08-11 17:49:24 -0400255 // Only format years and weeks if the remainder is zero, as it is often
256 // easier to read 90d than 12w6d.
257 f("y", 1000*60*60*24*365, true)
258 f("w", 1000*60*60*24*7, true)
259
260 f("d", 1000*60*60*24, false)
261 f("h", 1000*60*60, false)
262 f("m", 1000*60, false)
263 f("s", 1000, false)
264 f("ms", 1, false)
265
266 return r
267}
268
269// MarshalJSON implements the json.Marshaler interface.
270func (d Duration) MarshalJSON() ([]byte, error) {
271 return json.Marshal(d.String())
272}
273
274// UnmarshalJSON implements the json.Unmarshaler interface.
275func (d *Duration) UnmarshalJSON(bytes []byte) error {
276 var s string
277 if err := json.Unmarshal(bytes, &s); err != nil {
278 return err
khenaidooab1f7bd2019-11-14 14:00:27 -0500279 }
khenaidood948f772021-08-11 17:49:24 -0400280 dur, err := ParseDuration(s)
281 if err != nil {
282 return err
283 }
284 *d = dur
285 return nil
286}
287
288// MarshalText implements the encoding.TextMarshaler interface.
289func (d *Duration) MarshalText() ([]byte, error) {
290 return []byte(d.String()), nil
291}
292
293// UnmarshalText implements the encoding.TextUnmarshaler interface.
294func (d *Duration) UnmarshalText(text []byte) error {
295 var err error
296 *d, err = ParseDuration(string(text))
297 return err
khenaidooab1f7bd2019-11-14 14:00:27 -0500298}
299
300// MarshalYAML implements the yaml.Marshaler interface.
301func (d Duration) MarshalYAML() (interface{}, error) {
302 return d.String(), nil
303}
304
305// UnmarshalYAML implements the yaml.Unmarshaler interface.
306func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error {
307 var s string
308 if err := unmarshal(&s); err != nil {
309 return err
310 }
311 dur, err := ParseDuration(s)
312 if err != nil {
313 return err
314 }
315 *d = dur
316 return nil
317}