forked from sosodev/duration
-
Notifications
You must be signed in to change notification settings - Fork 0
/
duration.go
161 lines (143 loc) · 3.76 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
package duration
import (
"encoding/json"
"errors"
"fmt"
"math"
"strconv"
"time"
"unicode"
)
// Duration holds all the smaller units that make up the duration
type Duration struct {
Years float64
Months float64
Weeks float64
Days float64
Hours float64
Minutes float64
Seconds float64
}
const (
parsingPeriod = iota
parsingTime
)
var (
// ErrUnexpectedInput is returned when an input in the duration string does not match expectations
ErrUnexpectedInput = errors.New("unexpected input")
)
// Parse attempts to parse the given duration string into a *Duration
// if parsing fails an error is returned instead
func Parse(d string) (*Duration, error) {
state := parsingPeriod
duration := &Duration{}
num := ""
var err error
for _, char := range d {
switch char {
case 'P':
state = parsingPeriod
case 'T':
state = parsingTime
case 'Y':
if state != parsingPeriod {
return nil, ErrUnexpectedInput
}
duration.Years, err = strconv.ParseFloat(num, 64)
if err != nil {
return nil, err
}
num = ""
case 'M':
if state == parsingPeriod {
duration.Months, err = strconv.ParseFloat(num, 64)
if err != nil {
return nil, err
}
num = ""
} else if state == parsingTime {
duration.Minutes, err = strconv.ParseFloat(num, 64)
if err != nil {
return nil, err
}
num = ""
}
case 'W':
if state != parsingPeriod {
return nil, ErrUnexpectedInput
}
duration.Weeks, err = strconv.ParseFloat(num, 64)
if err != nil {
return nil, err
}
num = ""
case 'D':
if state != parsingPeriod {
return nil, ErrUnexpectedInput
}
duration.Days, err = strconv.ParseFloat(num, 64)
if err != nil {
return nil, err
}
num = ""
case 'H':
if state != parsingTime {
return nil, ErrUnexpectedInput
}
duration.Hours, err = strconv.ParseFloat(num, 64)
if err != nil {
return nil, err
}
num = ""
case 'S':
if state != parsingTime {
return nil, ErrUnexpectedInput
}
duration.Seconds, err = strconv.ParseFloat(num, 64)
if err != nil {
return nil, err
}
num = ""
default:
if unicode.IsNumber(char) || char == '.' {
num += string(char)
continue
}
return nil, ErrUnexpectedInput
}
}
return duration, nil
}
// ToTimeDuration converts the *Duration to the standard library's time.Duration
// note that for *Duration's with period values of a month or year that the duration becomes a bit fuzzy
// since obviously those things vary month to month and year to year
// I used the values that Google's search provided me with as I couldn't find anything concrete on what they should be
func (duration *Duration) ToTimeDuration() time.Duration {
var timeDuration time.Duration
timeDuration += time.Duration(math.Round(duration.Years * 3.154e+16))
timeDuration += time.Duration(math.Round(duration.Months * 2.628e+15))
timeDuration += time.Duration(math.Round(duration.Weeks * 6.048e+14))
timeDuration += time.Duration(math.Round(duration.Days * 8.64e+13))
timeDuration += time.Duration(math.Round(duration.Hours * 3.6e+12))
timeDuration += time.Duration(math.Round(duration.Minutes * 6e+10))
timeDuration += time.Duration(math.Round(duration.Seconds * 1e+9))
return timeDuration
}
// UnmarshalJSON unamarshals a Json representation of an ISO 8601 duration to a duration object
func (duration *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case string:
var err error
duration, err = Parse(value)
if err != nil {
return err
}
return nil
default:
return errors.New(fmt.Sprintf("Unsupported type trying to unmarshal duration: %v", value))
}
}