This repository has been archived by the owner on Apr 14, 2018. It is now read-only.
forked from rickar/cal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcal.go
236 lines (210 loc) · 6.13 KB
/
cal.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
// (c) 2014 Rick Arnold. Licensed under the BSD license (see LICENSE).
package cal
import "time"
// IsWeekend reports whether the given date falls on a weekend.
func IsWeekend(date time.Time) bool {
day := date.Weekday()
return day == time.Saturday || day == time.Sunday
}
// IsWeekdayN reports whether the given date is the nth occurrence of the
// day in the month.
//
// The value of n affects the direction of counting:
// n > 0: counting begins at the first day of the month.
// n == 0: the result is always false.
// n < 0: counting begins at the end of the month.
func IsWeekdayN(date time.Time, day time.Weekday, n int) bool {
cday := date.Weekday()
if cday != day || n == 0 {
return false
}
if n > 0 {
return (date.Day()-1)/7 == (n - 1)
} else {
n = -n
last := time.Date(date.Year(), date.Month()+1,
1, 12, 0, 0, 0, date.Location())
lastCount := 0
for {
last = last.AddDate(0, 0, -1)
if last.Weekday() == day {
lastCount++
}
if lastCount == n || last.Month() != date.Month() {
break
}
}
return lastCount == n && last.Month() == date.Month() &&
last.Day() == date.Day()
}
}
// MonthStart reports the starting day of the month in t. The time portion is
// unchanged.
func MonthStart(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), 1, t.Hour(), t.Minute(), t.Second(),
t.Nanosecond(), t.Location())
}
// MonthEnd reports the ending day of the month in t. The time portion is
// unchanged.
func MonthEnd(t time.Time) time.Time {
return time.Date(t.Year(), t.Month()+1, 0, t.Hour(), t.Minute(),
t.Second(), t.Nanosecond(), t.Location())
}
// JulianDayNumber reports the Julian Day Number for t. Note that Julian days
// start at 12:00 UTC.
func JulianDayNumber(t time.Time) int {
// algorithm from http://www.tondering.dk/claus/cal/julperiod.php#formula
utc := t.UTC()
a := (14 - int(utc.Month())) / 12
y := utc.Year() + 4800 - a
m := int(utc.Month()) + 12*a - 3
jdn := utc.Day() + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045
if utc.Hour() < 12 {
jdn -= 1
}
return jdn
}
// JulianDate reports the Julian Date (which includes time as a fraction) for t.
func JulianDate(t time.Time) float32 {
utc := t.UTC()
jdn := JulianDayNumber(t)
if utc.Hour() < 12 {
jdn += 1
}
return float32(jdn) + (float32(utc.Hour())-12.0)/24.0 +
float32(utc.Minute())/1440.0 + float32(utc.Second())/86400.0
}
// Calendar represents a yearly calendar with a list of holidays.
type Calendar struct {
holidays [13][]Holiday // 0 for offset based holidays, 1-12 for month based
Observed ObservedRule
WorkingSaturday bool // True if saturday is considered a work day
}
// NewCalendar creates a new Calendar with no holidays defined.
func NewCalendar() *Calendar {
c := &Calendar{}
for i := range c.holidays {
c.holidays[i] = make([]Holiday, 0, 2)
}
return c
}
// AddHoliday adds a holiday to the calendar's list.
func (c *Calendar) AddHoliday(h Holiday) {
c.holidays[h.Month] = append(c.holidays[h.Month], h)
}
// True if the given date is a weekend
// If this calendar considers saturday to be a work day, only Sunday is a weekend.
func (c *Calendar) IsWeekend(date time.Time) bool {
day := date.Weekday()
return (!c.WorkingSaturday && day == time.Saturday) || day == time.Sunday
}
// IsHoliday reports whether a given date is a holiday. It does not account
// for the observation of holidays on alternate days.
func (c *Calendar) IsHoliday(date time.Time) bool {
idx := date.Month()
for i := range c.holidays[idx] {
if c.holidays[idx][i].matches(date) {
return true
}
}
for i := range c.holidays[0] {
if c.holidays[0][i].matches(date) {
return true
}
}
return false
}
// IsWorkday reports whether a given date is a work day (business day).
func (c *Calendar) IsWorkday(date time.Time) bool {
if c.IsWeekend(date) || c.IsHoliday(date) {
return false
}
if c.Observed == ObservedExact {
return true
}
day := date.Weekday()
if c.Observed == ObservedMonday && day == time.Monday {
sun := date.AddDate(0, 0, -1)
sat := date.AddDate(0, 0, -2)
return !c.IsHoliday(sat) && !c.IsHoliday(sun)
} else if c.Observed == ObservedNearest {
if day == time.Friday {
sat := date.AddDate(0, 0, 1)
return !c.IsHoliday(sat)
} else if day == time.Monday {
sun := date.AddDate(0, 0, -1)
return !c.IsHoliday(sun)
}
}
return true
}
// countWorkdays reports the number of workdays from the given date to the end
// of the month.
func (c *Calendar) countWorkdays(dt time.Time, month time.Month) int {
n := 0
for ; month == dt.Month(); dt = dt.AddDate(0, 0, 1) {
if c.IsWorkday(dt) {
n++
}
}
return n
}
// Workdays reports the total number of workdays for the given year and month.
func (c *Calendar) Workdays(year int, month time.Month) int {
return c.countWorkdays(time.Date(year, month, 1, 12, 0, 0, 0, time.UTC), month)
}
// WorkdaysRemain reports the total number of remaining workdays in the month
// for the given date.
func (c *Calendar) WorkdaysRemain(date time.Time) int {
return c.countWorkdays(date.AddDate(0, 0, 1), date.Month())
}
// WorkdayN reports the day of the month that corresponds to the nth workday
// for the given year and month.
//
// The value of n affects the direction of counting:
// n > 0: counting begins at the first day of the month.
// n == 0: the result is always 0.
// n < 0: counting begins at the end of the month.
func (c *Calendar) WorkdayN(year int, month time.Month, n int) int {
var date time.Time
var add int
if n == 0 {
return 0
}
if n > 0 {
date = time.Date(year, month, 1, 12, 0, 0, 0, time.UTC)
add = 1
} else {
date = time.Date(year, month+1, 1, 12, 0, 0, 0, time.UTC).AddDate(0, 0, -1)
add = -1
n = -n
}
ndays := 0
for ; month == date.Month(); date = date.AddDate(0, 0, add) {
if c.IsWorkday(date) {
ndays++
if ndays == n {
return date.Day()
}
}
}
return 0
}
func (c *Calendar) CountWorkdays(start, end time.Time) int64 {
factor := 1
if end.Before(start) {
factor = -1
start, end = end, start
}
result := 0
var i time.Time
for i = start; i.Before(end); i = i.AddDate(0, 0, 1) {
if c.IsWorkday(i) {
result++
}
}
if i.Equal(end) && c.IsWorkday(end) {
result++
}
return int64(factor * result)
}