-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathperiod.go
390 lines (311 loc) · 9.71 KB
/
period.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
package period
import (
"fmt"
"time"
)
const (
UTS = "1136239445" // unixtimestamp
ICSFORMAT = "20060102T150405Z" //ics date time format
YMDHIS = "2006-01-02 15:04:05" // Y-m-d H:i:S time format
ICSFORMATWHOLEDAY = "20060102" // ics date format ( describes a whole day)
)
var StartWeek time.Weekday = time.Monday
var Timezone *time.Location = time.UTC
type Period struct {
Start time.Time `json:"start"`
End time.Time `json:"end"`
}
// Compare DateTimeInterface objects including microseconds
func compareDate(date1, date2 time.Time) int {
if date1.After(date2) {
return 1
}
if date1.Before(date2) {
return -1
}
return 0
}
// Today create a new period for today
func Today() (p Period, err error) {
var now = time.Now()
p, err = CreateFromDay(now.Year(), int(now.Month()), now.Day())
if err != nil {
return p, err
}
return p, nil
}
// Tomorrow create a new period for Tomorrow
func Tomorrow() (p Period, err error) {
p, err = Today()
if err != nil {
return p, err
}
p.Next()
return p, nil
}
// Yesterday create a new period for Yesterday
func Yesterday() (p Period, err error) {
p, err = Today()
if err != nil {
return p, err
}
p.Previous()
return p, nil
}
// CreateFromDay create a new period for a specific day
func CreateFromDay(year int, month int, day int) (p Period, err error) {
if month, err = validateRange(month, 1, 12); err != nil {
return p, err
}
if day, err = validateRange(day, 1, 31); err != nil {
return p, err
}
p.Start = time.Date(year, time.Month(month), day, 0, 0, 0, 0, Timezone)
p.End = p.Start.AddDate(0, 0, 1)
return p, nil
}
// CreateFromWeek create a Period object from a Year and a Week.
func CreateFromWeek(year, week int) (p Period, err error) {
if week, err = validateRange(week, 1, 53); err != nil {
return p, err
}
p.Start = time.Date(year, 0, 0, 0, 0, 0, 0, Timezone)
isoYear, isoWeek := p.Start.ISOWeek()
for p.Start.Weekday() != time.Monday { // iterate back to Monday (ISO_8601 first day of week = Monday)
p.Start = p.Start.AddDate(0, 0, -1)
isoYear, isoWeek = p.Start.ISOWeek()
}
for isoYear < year { // iterate forward to the first day of the first week
p.Start = p.Start.AddDate(0, 0, 1)
isoYear, isoWeek = p.Start.ISOWeek()
// fmt.Printf("isoYear: %s, year: %s\n", isoYear, year)
}
for isoWeek < week { // iterate forward to the first day of the given week
p.Start = p.Start.AddDate(0, 0, 1)
isoYear, isoWeek = p.Start.ISOWeek()
}
for p.Start.Weekday() != StartWeek { // iterate back to StartWeek
var diff int
diff = int(StartWeek) - int(time.Monday)
p.Start = p.Start.AddDate(0, 0, diff)
isoYear, isoWeek = p.Start.ISOWeek()
}
// add 1 week
p.End = p.Start.AddDate(0, 0, 7)
return p, nil
}
func validateRange(value, min, max int) (int, error) {
if value < min || value > max {
return value, OutOfRangeError
}
return value, nil
}
// CreateFromMonth create a Period object from a Year and a Month.
func CreateFromMonth(year, month int) (p Period, err error) {
if month, err = validateRange(month, 1, 12); err != nil {
return p, err
}
p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-%02d-01 00:00:00", year, month))
if err != nil {
return p, err
}
p.End = p.Start.AddDate(0, 1, 0)
return p, nil
}
// CreateFromQuarter create a Period object from a Year and a Quarter.
func CreateFromQuarter(year, quarter int) (p Period, err error) {
if quarter, err = validateRange(quarter, 1, 4); err != nil {
return p, err
}
p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-%02d-01 00:00:00", year, ((quarter-1)*3)+1))
if err != nil {
return p, err
}
p.End = p.Start.AddDate(0, 3, 0)
return p, nil
}
// CreateFromSemester create a Period object from a Year and a Quarter.
func CreateFromSemester(year, semester int) (p Period, err error) {
if semester, err = validateRange(semester, 1, 2); err != nil {
return p, err
}
p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-%02d-01 00:00:00", year, ((semester-1)*6)+1))
if err != nil {
return p, err
}
p.End = p.Start.AddDate(0, 6, 0)
return p, nil
}
// CreateFromYear create a Period object from a Year
func CreateFromYear(year int) (p Period, err error) {
p.Start, err = time.Parse(YMDHIS, fmt.Sprintf("%d-01-01 00:00:00", year))
if err != nil {
return p, err
}
p.End = p.Start.AddDate(1, 0, 0)
return p, nil
}
// CreateFromDuration create a Period object from a starting point and an interval.
func CreateFromDuration(start time.Time, duration time.Duration) (p Period) {
p.Start = start
p.End = p.Start.Add(duration)
return p
}
// CreateFromDurationBeforeEnd create a Period object from end time.Time with duration
func CreateFromDurationBeforeEnd(end time.Time, duration time.Duration) (p Period) {
p.Start = end.Add(-1 * duration)
p.End = end
return p
}
// Contains if Period contains time.Time
func (p *Period) Contains(index time.Time) bool {
return (-1 < compareDate(index, p.Start)) &&
(-1 == compareDate(index, p.End))
}
// WithDuration modify Period duration
func (p *Period) WithDuration(duration time.Duration) {
p.End = p.Start.Add(duration)
}
// Add add duration to period
func (p *Period) Add(duration time.Duration) {
p.End = p.End.Add(duration)
}
// Sub substract duration to period
func (p *Period) Sub(duration time.Duration) {
p.End = p.End.Add(-1 * duration)
}
// Next modify Period to next period with same interval
func (p *Period) Next() {
clone := *p
duration := clone.GetDurationInterval()
p.Start = clone.End
p.End = clone.End.Add(duration)
}
// Previous modify Period to previous period with same interval
func (p *Period) Previous() {
clone := *p
duration := clone.GetDurationInterval()
p.Start = clone.Start.Add(-1 * duration)
p.End = clone.Start
}
// GetDurationInterval get Period duration interval
func (p *Period) GetDurationInterval() time.Duration {
return p.End.Sub(p.Start)
}
// Overlaps if another Period overlaps this Period
func (p *Period) Overlaps(period Period) bool {
if abuts, _ := p.Abuts(period); abuts {
return false
}
return (-1 == compareDate(p.Start, period.End)) &&
(1 == compareDate(p.End, period.Start))
}
// After tells whether a Period is entirely after the specified index
func (p *Period) After(period Period) bool {
return p.Start.After(period.End)
}
// Before tells whether a Period is entirely before the specified index
func (p *Period) Before(period Period) bool {
return p.End.Before(period.Start)
}
// Abuts tells whether two Period object abuts
func (p *Period) Abuts(period Period) (bool, int) {
if p.Start.Equal(period.End) || p.End.Equal(period.Start) {
var pos int = -1
if compareDate(p.Start, period.End) == 0 {
pos = 0
} else if compareDate(p.End, period.Start) == 0 {
pos = 1
}
return true, pos
}
return false, -1
}
// Diff return Priod diff between two Period
func (p *Period) Diff(period Period) ([]Period, error) {
if p.Overlaps(period) == false {
return nil, ShouldOverlapsError
}
var res = []Period{}
var period1 Period = createFromDatepoints(p.Start, period.Start)
var period2 Period = createFromDatepoints(p.End, period.End)
if compareDate(period1.Start, period1.End) != 0 {
res = append(res, period1)
}
if compareDate(period2.Start, period2.End) != 0 {
res = append(res, period2)
}
return res, nil
}
// Merge Merge one or more Period objects to return a new Period object.
// The resultant object englobes the largest duration possible.
func (p *Period) Merge(periods ...Period) {
// allPeriods := []Period{}
for _, period := range periods {
if 1 == compareDate(p.Start, period.Start) {
p.Start = period.Start
}
if -1 == compareDate(p.End, period.End) {
p.End = period.End
}
}
}
// Intersect computes the intersection between two Period objects.
func (p *Period) Intersect(period Period) (Period, error) {
var newPeriod Period
if abuts, _ := p.Abuts(period); abuts {
return newPeriod, BothShouldNotAbuts
}
if newPeriod.Start = p.Start; 1 == compareDate(period.Start, p.Start) {
newPeriod.Start = period.Start
}
if newPeriod.End = p.End; -1 == compareDate(period.End, p.End) {
newPeriod.End = period.End
}
return newPeriod, nil
}
// Gap gab between two Period
func (p *Period) Gap(period Period) Period {
var newPeriod Period
if 1 == compareDate(period.Start, p.Start) {
newPeriod.Start = p.End
newPeriod.End = period.Start
return newPeriod
}
newPeriod.Start = period.End
newPeriod.End = p.Start
return newPeriod
}
// CompareDuration compares two Period objects according to their duration.
func (p *Period) CompareDuration(period Period) int {
return compareDate(p.End, period.End)
}
// DurationGreaterThan tells whether the current Period object duration
// is greater than the submitted one.
func (p *Period) DurationGreaterThan(period Period) bool {
return 1 == p.CompareDuration(period)
}
// DurationLessThan tells whether the current Period object duration
// is less than the submitted one.
func (p *Period) DurationLessThan(period Period) bool {
return -1 == p.CompareDuration(period)
}
// SameDurationAs tells whether the current Period object duration
// is equal to the submitted one
func (p *Period) SameDurationAs(period Period) bool {
return p.GetDurationInterval() == period.GetDurationInterval()
}
// DurationDiff create a Period object from a Year and a Quarter.
func (p *Period) DurationDiff(period Period) time.Duration {
return time.Duration(p.TimestampDurationDiff(period)) * time.Nanosecond
}
// TimestampDurationDiff difference in Nanoseconds between two Period
func (p *Period) TimestampDurationDiff(period Period) int64 {
return p.GetDurationInterval().Nanoseconds() - period.GetDurationInterval().Nanoseconds()
}
func createFromDatepoints(date1, date2 time.Time) Period {
if 1 == compareDate(date1, date2) {
return Period{date2, date1}
}
return Period{date1, date2}
}