-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathrecurrence.go
151 lines (128 loc) · 3.82 KB
/
recurrence.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
package rrule
import (
"strings"
"time"
)
// Recurrence expresses a complex pattern of repeating events composed of individual
// patterns and extra days that are filtered by exclusion patterns and days.
type Recurrence struct {
// Dtstart specifies the time to begin recurrence. If zero, time.Now is
// used when an iterator is generated. The location of Dtstart is the
// location that will be used to process the recurrence, which is
// particularly relevant for calculations affected by Daylight Savings.
Dtstart time.Time
// FloatingLocation determines how the Recurrence is encoded to string.
// If true, Dtstart, RDates, and ExDates will be written in local time,
// excluding the offset or timezone indicator, to represent a local time
// independent of timezone. See ParseRecurrence or RFC 5545 for more
// detail.
FloatingLocation bool
// Patterns and instances to include. Repeated instances are included only
// once, even if defined by multiple patterns.
//
// The Dtstart property of RRule and ExRule patterns are
// ignored, including when the above Dtstart property is zero.
RRules []RRule
RDates []time.Time
// Patterns and instances to exclude. These take precedence over the
// inclusions. Note: this feature was deprecated in RFC5545, noting its
// limited (and buggy) adoption and real-world use case. It is
// implemented here, nonetheless, for maximum flexibility and
// compatibility.
ExRules []RRule
ExDates []time.Time
}
// String returns the RFC 5545 representation of the recurrence, which is a
// newline delimited format.
func (r *Recurrence) String() string {
b := &strings.Builder{}
if !r.Dtstart.IsZero() {
b.WriteString(formatTime("DTSTART", r.Dtstart, r.FloatingLocation))
b.WriteString("\n")
}
for _, rrule := range r.RRules {
b.WriteString("RRULE:")
b.WriteString(rrule.String())
b.WriteString("\n")
}
for _, exrule := range r.ExRules {
b.WriteString("EXRULE:")
b.WriteString(exrule.String())
b.WriteString("\n")
}
for _, rdate := range r.RDates {
b.WriteString(formatTime("RDATE", rdate, r.FloatingLocation))
b.WriteString("\n")
}
for _, exdate := range r.ExDates {
b.WriteString(formatTime("EXDATE", exdate, r.FloatingLocation))
b.WriteString("\n")
}
return b.String()
}
func (r *Recurrence) setDtstart() {
for i, rr := range r.RRules {
rr.Dtstart = r.Dtstart
r.RRules[i] = rr
}
for i, rr := range r.ExRules {
rr.Dtstart = r.Dtstart
r.ExRules[i] = rr
}
}
// All returns all instances from the beginning of the iterator up to a limited
// number. If the limit is 0, all instances are returned, which will include all
// instances until (roughly) Go's maximum useful time.Time, in the year 219248499.
func All(it Iterator, limit int) []time.Time {
var all []time.Time
for {
next := it.Next()
if next == nil {
break
}
all = append(all, *next)
if limit > 0 && len(all) == limit {
break
}
}
return all
}
// Iterator returns an iterator for the recurrence.
func (r Recurrence) Iterator() Iterator {
r.setDtstart()
ri := &recurrenceIterator{
rrules: groupIteratorFromRRules(r.RRules),
exrules: groupIteratorFromRRules(r.ExRules),
}
ri.rrules.iters = append(ri.rrules.iters, &iterator{queue: r.RDates})
ri.exrules.iters = append(ri.exrules.iters, &iterator{queue: r.ExDates})
return ri
}
type recurrenceIterator struct {
rrules *groupIterator
exrules *groupIterator
}
func (ri *recurrenceIterator) Peek() *time.Time {
next := ri.rrules.Peek()
for {
if next == nil {
return nil
}
nextException := ri.exrules.Peek()
if nextException != nil && nextException.Before(*next) {
ri.exrules.Next()
continue
}
if nextException != nil && nextException.Equal(*next) {
next = ri.rrules.Next()
continue
}
break
}
return next
}
func (ri *recurrenceIterator) Next() *time.Time {
t := ri.Peek()
ri.rrules.Next()
return t
}