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
|
package calendar
import (
"strconv"
"strings"
"time"
)
// resolveDate returns the date (UTC midnight) that spec names in year, or
// ok=false if the spec is unparseable.
func resolveDate(spec DateSpec, year int, easter time.Time) (time.Time, bool) {
s := strings.TrimSpace(string(spec))
switch {
case len(s) == 5 && s[2] == '-': // MM-DD
mo, err1 := strconv.Atoi(s[0:2])
da, err2 := strconv.Atoi(s[3:5])
if err1 != nil || err2 != nil {
return time.Time{}, false
}
return time.Date(year, time.Month(mo), da, 0, 0, 0, 0, time.UTC), true
case strings.HasPrefix(s, "easter"):
return offset(easter, s[len("easter"):])
case strings.HasPrefix(s, "christmas"):
xmas := time.Date(year, 12, 25, 0, 0, 0, 0, time.UTC)
return offset(xmas, s[len("christmas"):])
case strings.HasPrefix(s, "sunday-after "):
base, ok := resolveDate(DateSpec(strings.TrimPrefix(s, "sunday-after ")), year, easter)
if !ok {
return time.Time{}, false
}
return nextWeekday(base.AddDate(0, 0, 1), time.Sunday), true
}
return time.Time{}, false
}
// offset parses "±N" and adds N days to base.
func offset(base time.Time, pm string) (time.Time, bool) {
if pm == "" {
return base, true
}
n, err := strconv.Atoi(pm) // strconv.Atoi handles a leading '+' and '-'
if err != nil {
return time.Time{}, false
}
return base.AddDate(0, 0, n), true
}
// nextWeekday returns the first day >= from whose weekday is wd.
func nextWeekday(from time.Time, wd time.Weekday) time.Time {
delta := (int(wd) - int(from.Weekday()) + 7) % 7
return from.AddDate(0, 0, delta)
}
|