Skip to content

Commit ecc404e

Browse files
committed
finished frequences; details remain. added bench comparison
1 parent 4b51652 commit ecc404e

File tree

3 files changed

+311
-5
lines changed

3 files changed

+311
-5
lines changed

benchcmp.sh

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
NEW=$(mktemp)
4+
OLD=$(mktemp)
5+
6+
go test -run xxx -bench RRule > $NEW
7+
go test -run xxx -bench Teambition | sed 's#Teambition#RRule#' > $OLD
8+
9+
benchcmp $OLD $NEW
10+
11+
rm $OLD $NEW

rrule.go

+199-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package rrule
22

33
import (
4+
"fmt"
45
"time"
56
)
67

@@ -224,14 +225,20 @@ func (rrule RRule) Iterator() Iterator {
224225
switch rrule.Frequency {
225226
case Secondly:
226227
return setSecondly(rrule)
228+
case Minutely:
229+
return setMinutely(rrule)
230+
case Hourly:
231+
return setHourly(rrule)
227232
case Daily:
228233
return setDaily(rrule)
229234
case Weekly:
230235
return setWeekly(rrule)
231236
case Monthly:
232237
return setMonthly(rrule)
238+
case Yearly:
239+
return setYearly(rrule)
233240
default:
234-
panic("not implemented")
241+
panic(fmt.Sprintf("invalid frequency %v", rrule.Frequency))
235242
}
236243
}
237244

@@ -276,6 +283,89 @@ func setSecondly(rrule RRule) *iterator {
276283
}
277284
}
278285

286+
func setMinutely(rrule RRule) *iterator {
287+
start := rrule.Dtstart
288+
if start.IsZero() {
289+
start = time.Now()
290+
}
291+
292+
interval := 1
293+
if rrule.Interval != 0 {
294+
interval = rrule.Interval
295+
}
296+
297+
current := start
298+
299+
return &iterator{
300+
minTime: start,
301+
queueCap: rrule.Count,
302+
next: func() *time.Time {
303+
ret := current // copy current
304+
current = current.Add(time.Duration(interval) * time.Minute)
305+
return &ret
306+
},
307+
308+
valid: func(t time.Time) bool {
309+
return checkLimiters(t,
310+
validMonth(rrule.ByMonths),
311+
validWeek(rrule.ByWeekNumbers),
312+
validYearDay(rrule.ByYearDays),
313+
validMonthDay(rrule.ByMonthDays),
314+
validWeekday(rrule.ByWeekdays),
315+
validHour(rrule.ByHours),
316+
validMinute(rrule.ByMinutes),
317+
)
318+
},
319+
320+
variations: func(t time.Time) []time.Time {
321+
tt := expandBySeconds([]time.Time{t}, rrule.BySeconds...)
322+
return tt
323+
},
324+
}
325+
}
326+
327+
func setHourly(rrule RRule) *iterator {
328+
start := rrule.Dtstart
329+
if start.IsZero() {
330+
start = time.Now()
331+
}
332+
333+
interval := 1
334+
if rrule.Interval != 0 {
335+
interval = rrule.Interval
336+
}
337+
338+
current := start
339+
340+
return &iterator{
341+
minTime: start,
342+
queueCap: rrule.Count,
343+
next: func() *time.Time {
344+
ret := current // copy current
345+
current = current.Add(time.Duration(interval) * time.Hour)
346+
return &ret
347+
},
348+
349+
valid: func(t time.Time) bool {
350+
return true
351+
return checkLimiters(t,
352+
validMonth(rrule.ByMonths),
353+
validWeek(rrule.ByWeekNumbers),
354+
validYearDay(rrule.ByYearDays),
355+
validMonthDay(rrule.ByMonthDays),
356+
validWeekday(rrule.ByWeekdays),
357+
validHour(rrule.ByHours),
358+
)
359+
},
360+
361+
variations: func(t time.Time) []time.Time {
362+
tt := expandByMinutes([]time.Time{t}, rrule.ByMinutes...)
363+
tt = expandBySeconds(tt, rrule.BySeconds...)
364+
return tt
365+
},
366+
}
367+
}
368+
279369
func setMonthly(rrule RRule) *iterator {
280370
start := rrule.Dtstart
281371
if start.IsZero() {
@@ -404,6 +494,48 @@ func setWeekly(rrule RRule) *iterator {
404494
}
405495
}
406496

497+
func setYearly(rrule RRule) *iterator {
498+
start := rrule.Dtstart
499+
if start.IsZero() {
500+
start = time.Now()
501+
}
502+
503+
interval := 1
504+
if rrule.Interval != 0 {
505+
interval = rrule.Interval
506+
}
507+
508+
current := start
509+
510+
return &iterator{
511+
minTime: start,
512+
queueCap: rrule.Count,
513+
next: func() *time.Time {
514+
ret := current // copy current
515+
current = current.AddDate(interval, 0, 0)
516+
return &ret
517+
},
518+
519+
valid: func(t time.Time) bool {
520+
return checkLimiters(t,
521+
validMonth(rrule.ByMonths),
522+
)
523+
},
524+
525+
variations: func(t time.Time) []time.Time {
526+
tt := expandBySeconds([]time.Time{t}, rrule.BySeconds...)
527+
tt = expandByMinutes(tt, rrule.ByMinutes...)
528+
tt = expandByHours(tt, rrule.ByHours...)
529+
// tt = expandByWeekdays(tt, rrule.weekStart(), rrule.ByWeekdays...)
530+
tt = expandByMonthDays(tt, rrule.ByMonthDays...)
531+
tt = expandByYearDays(tt, rrule.ByYearDays...)
532+
tt = expandByWeekNumbers(tt, rrule.weekStart(), rrule.ByWeekNumbers...)
533+
tt = expandByMonths(tt, rrule.IB, rrule.ByMonths...)
534+
return tt
535+
},
536+
}
537+
}
538+
407539
func (rrule *RRule) weekStart() time.Weekday {
408540
if rrule.WeekStart == nil {
409541
return time.Monday
@@ -461,7 +593,7 @@ func expandByWeekdays(tt []time.Time, weekStart time.Weekday, weekdays ...Qualif
461593
return tt
462594
}
463595

464-
e := make([]time.Time, len(tt)*len(weekdays))
596+
e := make([]time.Time, 0, len(tt)*len(weekdays))
465597
for _, t := range tt {
466598
t = backToWeekday(t, weekStart)
467599
for _, wd := range weekdays {
@@ -575,7 +707,7 @@ func expandByMonthDays(tt []time.Time, monthdays ...int) []time.Time {
575707
return tt
576708
}
577709

578-
e := make([]time.Time, len(tt)*len(monthdays))
710+
e := make([]time.Time, 0, len(tt)*len(monthdays))
579711
for _, t := range tt {
580712
for _, md := range monthdays {
581713
e = append(e, time.Date(t.Year(), t.Month(), md, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location()))
@@ -585,6 +717,70 @@ func expandByMonthDays(tt []time.Time, monthdays ...int) []time.Time {
585717
return e
586718
}
587719

720+
func expandByYearDays(tt []time.Time, yeardays ...int) []time.Time {
721+
if len(yeardays) == 0 {
722+
return tt
723+
}
724+
725+
e := make([]time.Time, 0, len(tt)*len(yeardays))
726+
for _, t := range tt {
727+
yearStart := time.Date(t.Year(), time.January, 1, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
728+
729+
for _, yd := range yeardays {
730+
e = append(e, yearStart.AddDate(0, 0, yd))
731+
}
732+
}
733+
734+
return e
735+
}
736+
737+
func expandByWeekNumbers(tt []time.Time, weekStarts time.Weekday, weekNumbers ...int) []time.Time {
738+
if len(weekNumbers) == 0 {
739+
return tt
740+
}
741+
742+
e := make([]time.Time, 0, len(tt)*len(weekNumbers))
743+
for _, t := range tt {
744+
yearStart := time.Date(t.Year(), time.January, 1, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
745+
yearStart = forwardToWeekday(yearStart, t.Weekday())
746+
747+
for _, w := range weekNumbers {
748+
e = append(e, yearStart.AddDate(0, 0, (w-1)*7))
749+
}
750+
}
751+
752+
return e
753+
}
754+
755+
func expandByMonths(tt []time.Time, ib InvalidBehavior, months ...time.Month) []time.Time {
756+
if len(months) == 0 {
757+
return tt
758+
}
759+
760+
e := make([]time.Time, 0, len(tt)*len(months))
761+
for _, t := range tt {
762+
for _, m := range months {
763+
set := time.Date(t.Year(), m, t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
764+
if set.Month() != m {
765+
switch ib {
766+
case PrevInvalid:
767+
set = time.Date(t.Year(), t.Month()+1, -1, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
768+
e = append(e, set)
769+
case NextInvalid:
770+
set = time.Date(t.Year(), t.Month()+1, 1, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
771+
e = append(e, set)
772+
case OmitInvalid:
773+
// do nothing
774+
}
775+
} else {
776+
e = append(e, set)
777+
}
778+
}
779+
}
780+
781+
return e
782+
}
783+
588784
type iterator struct {
589785
queue []time.Time
590786
totalQueued uint64

rrule_test.go

+101-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"time"
66

77
"github.com/stretchr/testify/assert"
8+
rrule "github.com/teambition/rrule-go"
89
)
910

1011
var now = time.Date(2018, 8, 25, 9, 8, 7, 6, time.UTC)
@@ -25,6 +26,28 @@ var cases = []struct {
2526
Dates: []string{"2018-08-25T09:08:07Z", "2018-08-25T09:08:08Z", "2018-08-25T09:08:09Z"},
2627
Terminal: true,
2728
},
29+
{
30+
Name: "simple minutely",
31+
RRule: RRule{
32+
Frequency: Minutely,
33+
Count: 3,
34+
Dtstart: now,
35+
},
36+
Dates: []string{"2018-08-25T09:08:07Z", "2018-08-25T09:09:07Z", "2018-08-25T09:10:07Z"},
37+
Terminal: true,
38+
},
39+
40+
{
41+
Name: "simple hourly",
42+
RRule: RRule{
43+
Frequency: Hourly,
44+
Count: 3,
45+
Dtstart: now,
46+
},
47+
Dates: []string{"2018-08-25T09:08:07Z", "2018-08-25T10:08:07Z", "2018-08-25T11:08:07Z"},
48+
Terminal: true,
49+
},
50+
2851
{
2952
Name: "simple daily",
3053
RRule: RRule{
@@ -94,13 +117,89 @@ func BenchmarkRRule(b *testing.B) {
94117
for _, tc := range cases {
95118
b.Run(tc.Name, func(b *testing.B) {
96119
for i := 0; i < b.N; i++ {
97-
dates := tc.RRule.All(0)
98-
assert.Equal(b, tc.Dates, rfcAll(dates))
120+
tc.RRule.All(0)
99121
}
100122
})
101123
}
102124
}
103125

126+
func rruleToROption(r RRule) rrule.ROption {
127+
converted := rrule.ROption{
128+
Dtstart: r.Dtstart,
129+
130+
Until: r.Until,
131+
Count: int(r.Count),
132+
Interval: r.Interval,
133+
134+
Bysecond: r.BySeconds,
135+
Byminute: r.ByMinutes,
136+
Byhour: r.ByHours,
137+
Bymonthday: r.ByMonthDays,
138+
Byweekno: r.ByWeekNumbers,
139+
Byyearday: r.ByYearDays,
140+
Bysetpos: r.BySetPos,
141+
142+
Bymonth: make([]int, 0, len(r.ByMonths)),
143+
Byweekday: make([]rrule.Weekday, 0, len(r.ByWeekdays)),
144+
}
145+
146+
switch r.Frequency {
147+
case Secondly:
148+
converted.Freq = rrule.SECONDLY
149+
case Minutely:
150+
converted.Freq = rrule.MINUTELY
151+
case Hourly:
152+
converted.Freq = rrule.HOURLY
153+
case Daily:
154+
converted.Freq = rrule.DAILY
155+
case Weekly:
156+
converted.Freq = rrule.WEEKLY
157+
case Monthly:
158+
converted.Freq = rrule.MONTHLY
159+
case Yearly:
160+
converted.Freq = rrule.YEARLY
161+
}
162+
163+
for _, m := range r.ByMonths {
164+
converted.Bymonth = append(converted.Bymonth, int(m))
165+
}
166+
for _, wd := range r.ByWeekdays {
167+
switch wd.WD {
168+
case time.Sunday:
169+
converted.Byweekday = append(converted.Byweekday, rrule.SU)
170+
case time.Monday:
171+
converted.Byweekday = append(converted.Byweekday, rrule.MO)
172+
case time.Tuesday:
173+
converted.Byweekday = append(converted.Byweekday, rrule.TU)
174+
case time.Wednesday:
175+
converted.Byweekday = append(converted.Byweekday, rrule.WE)
176+
case time.Thursday:
177+
converted.Byweekday = append(converted.Byweekday, rrule.TH)
178+
case time.Friday:
179+
converted.Byweekday = append(converted.Byweekday, rrule.FR)
180+
case time.Saturday:
181+
converted.Byweekday = append(converted.Byweekday, rrule.SA)
182+
}
183+
}
184+
185+
return converted
186+
}
187+
188+
func BenchmarkTeambition(b *testing.B) {
189+
for _, tc := range cases {
190+
191+
ro := rruleToROption(tc.RRule)
192+
193+
b.Run(tc.Name, func(b *testing.B) {
194+
for i := 0; i < b.N; i++ {
195+
teambitionRRule, _ := rrule.NewRRule(ro)
196+
teambitionRRule.All()
197+
}
198+
})
199+
}
200+
201+
}
202+
104203
func rfcAll(times []time.Time) []string {
105204
strs := make([]string, len(times))
106205
for i, t := range times {

0 commit comments

Comments
 (0)