1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package bigquery
16
17 import (
18 "bytes"
19 "fmt"
20 "strconv"
21 "time"
22 )
23
24
25
26
27
28
29
30
31
32
33
34 type IntervalValue struct {
35
36
37 Years int32
38 Months int32
39
40
41
42 Days int32
43
44
45 Hours int32
46 Minutes int32
47 Seconds int32
48
49 SubSecondNanos int32
50 }
51
52
53
54
55
56 func (iv *IntervalValue) String() string {
57
58
59 src := iv
60 if !iv.IsCanonical() {
61 src = iv.Canonicalize()
62 }
63 out := fmt.Sprintf("%d-%d %d %d:%d:%d", src.Years, int32abs(src.Months), src.Days, src.Hours, int32abs(src.Minutes), int32abs(src.Seconds))
64 if src.SubSecondNanos != 0 {
65 mantStr := fmt.Sprintf("%09d", src.SubSecondNanos)
66 for len(mantStr) > 0 && mantStr[len(mantStr)-1:] == "0" {
67 mantStr = mantStr[0 : len(mantStr)-1]
68 }
69 out = fmt.Sprintf("%s.%s", out, mantStr)
70 }
71 return out
72 }
73
74
75 type intervalPart int
76
77 const (
78 yearsPart = iota
79 monthsPart
80 daysPart
81 hoursPart
82 minutesPart
83 secondsPart
84 subsecsPart
85 )
86
87 func (i intervalPart) String() string {
88 knownParts := []string{"YEARS", "MONTHS", "DAYS", "HOURS", "MINUTES", "SECONDS", "SUBSECONDS"}
89 if i < 0 || int(i) > len(knownParts) {
90 return fmt.Sprintf("UNKNOWN(%d)", i)
91 }
92 return knownParts[i]
93 }
94
95
96 var canonicalParts = []intervalPart{yearsPart, monthsPart, daysPart, hoursPart, minutesPart, secondsPart, subsecsPart}
97
98
99 func ParseInterval(value string) (*IntervalValue, error) {
100 iVal := &IntervalValue{}
101 for _, part := range canonicalParts {
102 remaining, v, err := getPartValue(part, value)
103 if err != nil {
104 return nil, err
105 }
106 switch part {
107 case yearsPart:
108 iVal.Years = v
109 case monthsPart:
110 iVal.Months = v
111 if iVal.Years < 0 {
112 iVal.Months = -v
113 }
114 case daysPart:
115 iVal.Days = v
116 case hoursPart:
117 iVal.Hours = v
118 case minutesPart:
119 iVal.Minutes = v
120 if iVal.Hours < 0 {
121 iVal.Minutes = -v
122 }
123 case secondsPart:
124 iVal.Seconds = v
125 if iVal.Hours < 0 {
126 iVal.Seconds = -v
127 }
128 case subsecsPart:
129 iVal.SubSecondNanos = v
130 if iVal.Hours < 0 {
131 iVal.SubSecondNanos = -v
132 }
133 default:
134 return nil, fmt.Errorf("encountered invalid part %s during parse", part)
135 }
136 value = remaining
137 }
138 return iVal, nil
139 }
140
141 func getPartValue(part intervalPart, s string) (string, int32, error) {
142 s = trimPrefix(part, s)
143 return getNumVal(part, s)
144 }
145
146
147 func trimPrefix(part intervalPart, s string) string {
148 var trimByte byte
149 switch part {
150 case yearsPart, daysPart, hoursPart:
151 trimByte = byte(' ')
152 case monthsPart:
153 trimByte = byte('-')
154 case minutesPart, secondsPart:
155 trimByte = byte(':')
156 case subsecsPart:
157 trimByte = byte('.')
158 }
159 for len(s) > 0 && s[0] == trimByte {
160 s = s[1:]
161 }
162 return s
163 }
164
165 func getNumVal(part intervalPart, s string) (string, int32, error) {
166
167 allowedVals := []byte("0123456789")
168 var allowedSign bool
169 captured := ""
170 switch part {
171 case yearsPart, daysPart, hoursPart:
172 allowedSign = true
173 }
174
175 if len(s) > 0 && allowedSign {
176 switch s[0] {
177 case '-':
178 captured = "-"
179 s = s[1:]
180 case '+':
181 s = s[1:]
182 }
183 }
184 for len(s) > 0 && bytes.IndexByte(allowedVals, s[0]) >= 0 {
185 captured = captured + string(s[0])
186 s = s[1:]
187 }
188
189 if len(captured) == 0 {
190 if part == subsecsPart {
191 return s, 0, nil
192 }
193 return "", 0, fmt.Errorf("no value parsed for part %s", part.String())
194 }
195
196 if part == subsecsPart {
197 parsed, err := strconv.ParseFloat(fmt.Sprintf("0.%s", captured), 64)
198 if err != nil {
199 return "", 0, fmt.Errorf("couldn't parse %s as %s", captured, part.String())
200 }
201 return s, int32(parsed * 1e9), nil
202 }
203 parsed, err := strconv.ParseInt(captured, 10, 32)
204 if err != nil {
205 return "", 0, fmt.Errorf("error parsing value %s for %s: %w", captured, part.String(), err)
206 }
207 return s, int32(parsed), nil
208 }
209
210
211
212
213
214 func IntervalValueFromDuration(in time.Duration) *IntervalValue {
215 nanos := in.Nanoseconds()
216 out := &IntervalValue{}
217 out.Hours = int32(nanos / 3600 / 1e9)
218 nanos = nanos - (int64(out.Hours) * 3600 * 1e9)
219 out.Minutes = int32(nanos / 60 / 1e9)
220 nanos = nanos - (int64(out.Minutes) * 60 * 1e9)
221 out.Seconds = int32(nanos / 1e9)
222 nanos = nanos - (int64(out.Seconds) * 1e9)
223 out.SubSecondNanos = int32(nanos)
224 return out
225 }
226
227
228
229
230
231
232
233 func (iv *IntervalValue) ToDuration() time.Duration {
234 var accum int64
235 accum = 12*int64(iv.Years) + int64(iv.Months)
236
237 accum = accum*30 + int64(iv.Days)
238
239 accum = accum*24 + int64(iv.Hours)
240
241 accum = accum*60 + int64(iv.Minutes)
242
243 accum = accum*60 + int64(iv.Seconds)
244
245 accum = accum*1e9 + int64(iv.SubSecondNanos*1e9)
246 return time.Duration(accum)
247 }
248
249
250
251
252
253
254
255 func (iv *IntervalValue) Canonicalize() *IntervalValue {
256 newIV := &IntervalValue{iv.Years, iv.Months, iv.Days, iv.Hours, iv.Minutes, iv.Seconds, iv.SubSecondNanos}
257
258 totalMonths := iv.Years*12 + iv.Months
259 newIV.Years = totalMonths / 12
260 totalMonths = totalMonths - (newIV.Years * 12)
261 newIV.Months = totalMonths % 12
262
263
264
265
266 totalNanos := int64(iv.Hours)*3600*1e9 +
267 int64(iv.Minutes)*60*1e9 +
268 int64(iv.Seconds)*1e9 +
269 int64(iv.SubSecondNanos)
270
271
272 newIV.Hours = int32(totalNanos / 60 / 60 / 1e9)
273 totalNanos = totalNanos - (int64(newIV.Hours) * 3600 * 1e9)
274 newIV.Minutes = int32(totalNanos / 60 / 1e9)
275 totalNanos = totalNanos - (int64(newIV.Minutes) * 60 * 1e9)
276 newIV.Seconds = int32(totalNanos / 1e9)
277 totalNanos = totalNanos - (int64(newIV.Seconds) * 1e9)
278 newIV.SubSecondNanos = int32(totalNanos)
279 return newIV
280 }
281
282
283
284 func (iv *IntervalValue) IsCanonical() bool {
285 if !sameSign(iv.Years, iv.Months) ||
286 !sameSign(iv.Hours, iv.Minutes) {
287 return false
288 }
289
290 if int32abs(iv.Months) > 12 ||
291 int32abs(iv.Minutes) > 60 ||
292 int32abs(iv.Seconds) > 60 ||
293 int32abs(iv.SubSecondNanos) > 1e9 {
294 return false
295 }
296
297 return true
298 }
299
300 func int32abs(x int32) int32 {
301 if x < 0 {
302 return -x
303 }
304 return x
305 }
306
307 func sameSign(nums ...int32) bool {
308 var pos, neg int
309 for _, n := range nums {
310 if n > 0 {
311 pos = pos + 1
312 }
313 if n < 0 {
314 neg = neg + 1
315 }
316 }
317 if pos > 0 && neg > 0 {
318 return false
319 }
320 return true
321 }
322
View as plain text