-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathduration.go
165 lines (144 loc) · 4.03 KB
/
duration.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/*
* Package duration allows for parsing and use of RFC3339 duration values
*/
package duration
import (
"bytes"
"fmt"
"regexp"
"strconv"
"time"
)
// ABNF pattern in https://www.ietf.org/rfc/rfc3339.txt
var (
rfc3339DurationPattern = regexp.MustCompile(`\AP(` +
// a number of weeks
`((?P<weeks>\d+)W)` +
`|(` +
// date duration
`((?P<years>\d+)Y)?((?P<months>\d+)M)?((?P<days>\d+)D)?` +
`(T((?P<hours>\d+)H)?((?P<minutes>\d+)M)?((?P<seconds>\d+?)S)?)?` +
`)` +
`)\z`,
)
ErrInvalidFormat = fmt.Errorf("must be RFC3999 formatted duration")
)
type ParseError struct {
error
}
// Duration represents a time duration
//
// We do not use time.Duration as there would be ambiguilty when units greater than a day are used. For example, if we
// encoded a year as 365 days, adding the duration to a date would not yield the correct result when a leap year is in
// the interval.
type Duration struct {
Years int
Months int
// TODO(jesse):
// Weeks should not be combined with other units according to RFC 3339 we could seperate this into two structs with an
// interface (like WeekDuration)
Weeks int
Days int
Hours int
Minutes int
Seconds int
}
// ParseRFC3339 parses a duration encdoded as described in RFC 3339
func ParseRFC3339(s string) (Duration, error) {
matches := rfc3339DurationPattern.FindStringSubmatch(s)
if matches == nil {
return Duration{}, ParseError{fmt.Errorf("must be RFC3999 formatted duration")}
}
d := Duration{}
for i, name := range rfc3339DurationPattern.SubexpNames() {
value := matches[i]
if i == 0 || name == "" || len(value) == 0 {
continue
}
i64, err := strconv.ParseInt(string(value), 10, 32)
if err != nil {
return Duration{}, ParseError{fmt.Errorf("must be RFC3999 formatted duration, found non-integer: %s", string(value))}
}
i := int(i64)
switch name {
case "years":
d.Years = i
case "months":
d.Months = i
case "weeks":
d.Weeks = i
case "days":
d.Days = i
case "hours":
d.Hours = i
case "minutes":
d.Minutes = i
case "seconds":
d.Seconds = i
}
}
return d, nil
}
// FormatRFC3339 returns the duration formatted as an RFC 3339 string
func (d Duration) FormatRFC3339() string {
buf := &bytes.Buffer{}
fmt.Fprint(buf, "P")
if d.Years != 0 {
fmt.Fprintf(buf, "%dY", d.Years)
}
if d.Months != 0 {
fmt.Fprintf(buf, "%dM", d.Months)
}
if d.Weeks != 0 {
fmt.Fprintf(buf, "%dW", d.Weeks)
}
if d.Days != 0 {
fmt.Fprintf(buf, "%dD", d.Days)
}
if d.Hours != 0 || d.Minutes != 0 || d.Seconds != 0 {
fmt.Fprint(buf, "T")
}
if d.Hours != 0 {
fmt.Fprintf(buf, "%dH", d.Hours)
}
if d.Minutes != 0 {
fmt.Fprintf(buf, "%dM", d.Minutes)
}
if d.Seconds != 0 {
fmt.Fprintf(buf, "%dS", d.Seconds)
}
return buf.String()
}
// String retrurns the duration as an RFC 3339 duration
func (d Duration) String() string {
return d.FormatRFC3339()
}
// MarshalText implements the encoding.TextMarshaler interface. The duration is formatted in RFC 3339 format.
func (d Duration) MarshalText() (text []byte, err error) {
return []byte(d.FormatRFC3339()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface. The duration is expected to be in RFC 3339 format.
func (d *Duration) UnmarshalText(text []byte) error {
duration, err := ParseRFC3339(string(text))
if err != nil {
return err
}
*d = duration
return nil
}
// AddToTime adds the duration to the provided time.Time, returning a new time.Time.
func (d Duration) AddToTime(t time.Time) time.Time {
t = t.AddDate(d.Years, d.Months, d.Days+d.Weeks*7)
t = t.Add(time.Duration(d.Hours) * time.Hour)
t = t.Add(time.Duration(d.Minutes) * time.Minute)
t = t.Add(time.Duration(d.Seconds) * time.Second)
return t
}
// SubtractFromTime subtracts the duration from the provided time.Time, returning a new time.Time.
func (d Duration) SubtractFromTime(t time.Time) time.Time {
t = t.AddDate(-d.Years, -d.Months, -d.Days+-d.Weeks*7)
t = t.Add(time.Duration(-d.Hours) * time.Hour)
t = t.Add(time.Duration(-d.Minutes) * time.Minute)
t = t.Add(time.Duration(-d.Seconds) * time.Second)
return t
}