-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtime.go
298 lines (250 loc) · 8.43 KB
/
time.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
// Copyright © 2020 Danila Petrunko. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package rdate
import (
"sync"
"time"
)
type TimeShortcut string
const (
TimeAsIs TimeShortcut = "as is"
TimeStartOfThisDay TimeShortcut = "start this day"
TimeEndOfThisDay TimeShortcut = "end this day"
TimeStartOfThisWeek TimeShortcut = "start this week"
TimeEndOfThisWeek TimeShortcut = "end this week"
TimeStartOfThisMonth TimeShortcut = "start this month"
TimeEndOfThisMonth TimeShortcut = "end this month"
TimeStartOfThisQuart TimeShortcut = "start this quart"
TimeEndOfThisQuart TimeShortcut = "end this quart"
TimeStartOfThisHalfYear TimeShortcut = "start this half year"
TimeEndOfThisHalfYear TimeShortcut = "end this half year"
TimeStartOfThisYear TimeShortcut = "start this year"
TimeEndOfThisYear TimeShortcut = "end this year"
TimeStartOfPrevDay TimeShortcut = "start prev day"
TimeEndOfPrevDay TimeShortcut = "end prev day"
TimeStartOfPrevWeek TimeShortcut = "start prev week"
TimeEndOfPrevWeek TimeShortcut = "end prev week"
TimeStartOfPrevMonth TimeShortcut = "start prev month"
TimeEndOfPrevMonth TimeShortcut = "end prev month"
TimeStartOfPrevQuart TimeShortcut = "start prev quart"
TimeEndOfPrevQuart TimeShortcut = "end prev quart"
TimeStartOfPrevHalfYear TimeShortcut = "start prev half year"
TimeEndOfPrevHalfYear TimeShortcut = "end prev half year"
TimeStartOfPrevYear TimeShortcut = "start prev year"
TimeEndOfPrevYear TimeShortcut = "end prev year"
)
type StartOfWeek int8
const (
StartOfWeekMonday StartOfWeek = iota + 1
StartOfWeekSunday
)
// TimeFactory is used to make new Time objects by passing the pivot and the shortcut.
// Method NewTime and RequireTime uses the default time factory which is created
// by calling NewTimeFactory during the init.
type TimeFactory interface {
// Make creates a new Time object by using the rule which is found (or not)
// by the given TimeShortcut.
// If the rule is not found, ok will be false and t will be a zero-value of Time.
Make(pivot time.Time, sc TimeShortcut) (t Time, ok bool)
// Require creates new Time object by using the rule which is found (or not)
// by the given TimeShortcut.
// If the rule is not found, the result will be a zero-value of Time.
// This method should be used only if you are sure about existence of given shortcut.
Require(pivot time.Time, sc TimeShortcut) Time
// Extend appends new rules (or replaces existing ones if there are any rules
// with the same shortcuts) to the time factory.
Extend(rules []TimeRule)
// SetStartOfWeek sets the start of the week for the time factory.
// It can be Monday or Sunday.
// The default value is Monday.
SetStartOfWeek(s StartOfWeek)
// SetStringer sets your own TimeStringer implementation
// for every new Time object which is created by this factory.
SetStringer(s TimeStringer)
}
type unsafeTimeFactory struct {
rules map[TimeShortcut]TimeRule
s TimeStringer
}
func newUnsafeTimeFactory(rules []TimeRule, s TimeStringer) TimeFactory {
f := &unsafeTimeFactory{
rules: map[TimeShortcut]TimeRule{},
s: s,
}
f.Extend(rules)
return f
}
// Make implements the TimeFactory Make method.
func (f *unsafeTimeFactory) Make(pivot time.Time, sc TimeShortcut) (t Time, ok bool) {
r, ok := f.rules[sc]
if !ok {
return Time{}, false
}
return Time{
t: r.Calculate(pivot),
s: f.s,
}, true
}
// Require implements the TimeFactory Require method.
func (f *unsafeTimeFactory) Require(pivot time.Time, sc TimeShortcut) Time {
t, ok := f.Make(pivot, sc)
if !ok {
t = Time{}
}
return t
}
// SetStringer implements the TimeFactory SetStringer method.
func (f *unsafeTimeFactory) SetStringer(s TimeStringer) {
f.s = s
}
// Extend implements the TimeFactory Extend method.
func (f *unsafeTimeFactory) Extend(rules []TimeRule) {
for _, r := range rules {
f.rules[r.Shortcut()] = r
}
}
// SetStartOfWeek implements the TimeFactory SetStartOfWeek method.
func (f *unsafeTimeFactory) SetStartOfWeek(s StartOfWeek) {
var rules []TimeRule
switch s {
case StartOfWeekMonday:
rules = []TimeRule{
&timeRuleStartOfThisWeek{},
&timeRuleEndOfThisWeek{},
&timeRuleStartOfPrevWeek{},
&timeRuleEndOfPrevWeek{},
}
case StartOfWeekSunday:
rules = []TimeRule{
&timeRuleStartOfThisWeekS{},
&timeRuleEndOfThisWeekS{},
&timeRuleStartOfPrevWeekS{},
&timeRuleEndOfPrevWeekS{},
}
}
f.Extend(rules)
}
var defaultRules = []TimeRule{
&timeRuleAsIs{},
thisDayStartRule,
thisDayEndRule,
&timeRuleStartOfPrevDay{},
&timeRuleEndOfPrevDay{},
&timeRuleStartOfThisWeek{},
&timeRuleEndOfThisWeek{},
&timeRuleStartOfPrevWeek{},
&timeRuleEndOfPrevWeek{},
&timeRuleStartOfThisMonth{},
&timeRuleEndOfThisMonth{},
&timeRuleStartOfPrevMonth{},
&timeRuleEndOfPrevMonth{},
&timeRuleStartOfThisQuart{},
&timeRuleEndOfThisQuart{},
&timeRuleStartOfPrevQuart{},
&timeRuleEndOfPrevQuart{},
&timeRuleStartOfThisHalfYear{},
&timeRuleEndOfThisHalfYear{},
&timeRuleStartOfPrevHalfYear{},
&timeRuleEndOfPrevHalfYear{},
&timeRuleStartOfThisYear{},
&timeRuleEndOfThisYear{},
&timeRuleStartOfPrevYear{},
&timeRuleEndOfPrevYear{},
}
var defaultTimeFactory = NewTimeFactory()
// safeTimeFactory is a decorator which wraps a time factory for concurrent use
// by multiple goroutines.
type safeTimeFactory struct {
f TimeFactory
rw sync.RWMutex
}
// Make implements the TimeFactory Make method.
func (f *safeTimeFactory) Make(pivot time.Time, sc TimeShortcut) (t Time, ok bool) {
f.rw.RLock()
defer f.rw.RUnlock()
return f.f.Make(pivot, sc)
}
// Require implements the TimeFactory Require method.
func (f *safeTimeFactory) Require(pivot time.Time, sc TimeShortcut) Time {
f.rw.RLock()
defer f.rw.RUnlock()
return f.f.Require(pivot, sc)
}
// SetStringer implements the TimeFactory SetStringer method.
func (f *safeTimeFactory) SetStringer(s TimeStringer) {
f.rw.Lock()
defer f.rw.Unlock()
f.f.SetStringer(s)
}
// Extend implements the TimeFactory Extend method.
func (f *safeTimeFactory) Extend(rules []TimeRule) {
f.rw.Lock()
defer f.rw.Unlock()
f.f.Extend(rules)
}
// SetStartOfWeek implements the TimeFactory SetStartOfWeek method.
func (f *safeTimeFactory) SetStartOfWeek(s StartOfWeek) {
f.rw.Lock()
defer f.rw.Unlock()
f.f.SetStartOfWeek(s)
}
func newSafeTimeFactory(f TimeFactory) TimeFactory {
return &safeTimeFactory{f: f}
}
// NewTimeFactory creates a time factory which is ready to extend and
// safe for concurrent use by multiple goroutines.
func NewTimeFactory() TimeFactory {
return newSafeTimeFactory(
NewNonblockingTimeFactory())
}
// NewNonblockingTimeFactory creates an unsafe time factory which is ready to extend.
// It means that there is no synchronization mechanisms in it.
// On the one hand extending of this factory is not a thread-safe operation,
// but on the other hand it makes Make and Require methods to be non-blocking.
// (eliminates the cache coherency problem)
//
// It might be useful when your application is under high load and your time factory
// doesn't use Extend method at all or use it once during the init.
func NewNonblockingTimeFactory() TimeFactory {
return newUnsafeTimeFactory(defaultRules, &defaultTimeStringer{})
}
// SetDefaultTimeFactory sets your own time factory as default.
//
// After that you can use NewTime or RequireTime functions
// without concreting a factory like that rdate.NewTime(...)
// or rdate.RequireTime(...))
func SetDefaultTimeFactory(f TimeFactory) {
defaultTimeFactory = f
}
// SetDefaultStartOfWeek sets the start of the week for the default time factory.
// It can be Monday or Sunday.
func SetDefaultStartOfWeek(s StartOfWeek) {
defaultTimeFactory.SetStartOfWeek(s)
}
type Time struct {
t time.Time
s TimeStringer
}
// NewTime calls Make method of the default time factory.
func NewTime(pivot time.Time, sc TimeShortcut) (t Time, ok bool) {
return defaultTimeFactory.Make(pivot, sc)
}
// RequireTime calls Require method of the default time factory.
func RequireTime(pivot time.Time, sc TimeShortcut) Time {
return defaultTimeFactory.Require(pivot, sc)
}
// Time is a getter of the internal time.Time value of the type.
func (t Time) Time() time.Time {
return t.t
}
func (t Time) String() string {
if t.s == nil {
return ""
}
return t.s.String(t.t)
}
// IsZero reports if the value is a zero-value of the type
func (t Time) IsZero() bool {
return t.s == nil && t.t.IsZero()
}