pax_global_header00006660000000000000000000000064137027374540014526gustar00rootroot0000000000000052 comment=5a7db71efe3ace8cf0dbdc225e590a716d027087 rrule-go-1.6.0/000077500000000000000000000000001370273745400132665ustar00rootroot00000000000000rrule-go-1.6.0/.gitignore000066400000000000000000000004261370273745400152600ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof debug .idea rrule-go-1.6.0/.travis.yml000066400000000000000000000005201370273745400153740ustar00rootroot00000000000000sudo: false language: go go: - "1.10" - "1.10.1" - "1.10.2" - "1.10.3" - "1.10.4" - "1.11" - "1.11.1" - "1.12" - "1.12.6" before_install: - go get -t -v ./... - go get github.com/mattn/goveralls script: - go test -coverprofile=rrule.coverprofile - goveralls -coverprofile=rrule.coverprofile -service=travis-ci rrule-go-1.6.0/LICENSE000066400000000000000000000020601370273745400142710ustar00rootroot00000000000000MIT License Copyright (c) 2016-2020 Teambition Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rrule-go-1.6.0/Makefile000066400000000000000000000002601370273745400147240ustar00rootroot00000000000000test: go test --race cover: rm -f *.coverprofile go test -coverprofile=rrule.coverprofile go tool cover -html=rrule.coverprofile rm -f *.coverprofile .PHONY: test cover rrule-go-1.6.0/README.md000066400000000000000000000070501370273745400145470ustar00rootroot00000000000000# rrule-go Go library for working with recurrence rules for calendar dates. [![Build Status](http://img.shields.io/travis/teambition/rrule-go.svg?style=flat-square)](https://travis-ci.org/teambition/rrule-go) [![Coverage Status](http://img.shields.io/coveralls/teambition/rrule-go.svg?style=flat-square)](https://coveralls.io/r/teambition/rrule-go) [![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/teambition/rrule-go/master/LICENSE) [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/teambition/rrule-go) The rrule module offers a complete implementation of the recurrence rules documented in the [iCalendar RFC](http://www.ietf.org/rfc/rfc2445.txt). It is a partial port of the rrule module from the excellent [python-dateutil](http://labix.org/python-dateutil/) library. ## Demo ### rrule.RRule ```go package main import ( "fmt" "time" "github.com/teambition/rrule-go" ) func exampleRRule() { fmt.Println("Daily, for 10 occurrences.") r, _ := rrule.NewRRule(rrule.ROption{ Freq: rrule.DAILY, Count: 10, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.Local)}) fmt.Println(r.All()) // [1997-09-02 09:00:00 +0800 CST // 1997-09-03 09:00:00 +0800 CST // ... // 1997-09-10 09:00:00 +0800 CST // 1997-09-11 09:00:00 +0800 CST] fmt.Println(r.Between( time.Date(1997, 9, 6, 0, 0, 0, 0, time.Local), time.Date(1997, 9, 8, 0, 0, 0, 0, time.Local), true)) // [1997-09-06 09:00:00 +0800 // CST 1997-09-07 09:00:00 +0800 CST] fmt.Println(r) // FREQ=DAILY;DTSTART=19970902T010000Z;COUNT=10 } ``` ### rrule.Set ```go package main import ( "fmt" "time" "github.com/teambition/rrule-go" ) func exampleRRuleSet() { fmt.Println("\nDaily, for 7 days, jumping Saturday and Sunday occurrences.") set := rrule.Set{} r, _ := rrule.NewRRule(rrule.ROption{ Freq: rrule.DAILY, Count: 7, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.Local)}) set.RRule(r) r, _ = rrule.NewRRule(rrule.ROption{ Freq: rrule.YEARLY, Byweekday: []rrule.Weekday{rrule.SA, rrule.SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.Local)}) set.ExRule(r) fmt.Println(set.All()) // [1997-09-02 09:00:00 +0800 CST // 1997-09-03 09:00:00 +0800 CST // 1997-09-04 09:00:00 +0800 CST // 1997-09-05 09:00:00 +0800 CST // 1997-09-08 09:00:00 +0800 CST] fmt.Println("\nWeekly, for 4 weeks, plus one time on day 7, and not on day 16.") set = rrule.Set{} r, _ = rrule.NewRRule(rrule.ROption{ Freq: rrule.WEEKLY, Count: 4, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.Local)}) set.RRule(r) set.RDate(time.Date(1997, 9, 7, 9, 0, 0, 0, time.Local)) set.ExDate(time.Date(1997, 9, 16, 9, 0, 0, 0, time.Local)) fmt.Println(set.All()) // [1997-09-02 09:00:00 +0800 CST // 1997-09-07 09:00:00 +0800 CST // 1997-09-09 09:00:00 +0800 CST // 1997-09-23 09:00:00 +0800 CST] } ``` ### rrule.StrToRRule ```go func exampleStr() { fmt.Println() r, _ := rrule.StrToRRule("FREQ=DAILY;INTERVAL=10;COUNT=5") fmt.Println(r.All()) // [2017-03-15 14:12:02 +0800 CST // 2017-03-25 14:12:02 +0800 CST // 2017-04-04 14:12:02 +0800 CST // 2017-04-14 14:12:02 +0800 CST // 2017-04-24 14:12:02 +0800 CST] } ``` For more examples see [python-dateutil](http://labix.org/python-dateutil/) documentation. ## License Gear is licensed under the [MIT](https://github.com/teambition/gear/blob/master/LICENSE) license. Copyright © 2017-2019 [Teambition](https://www.teambition.com). rrule-go-1.6.0/example/000077500000000000000000000000001370273745400147215ustar00rootroot00000000000000rrule-go-1.6.0/example/main.go000066400000000000000000000053451370273745400162030ustar00rootroot00000000000000package main import ( "fmt" "time" "github.com/teambition/rrule-go" ) func exampleRRule() { fmt.Println("Daily, for 10 occurrences.") r, _ := rrule.NewRRule(rrule.ROption{ Freq: rrule.DAILY, Count: 10, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.Local)}) fmt.Println(r.All()) // [1997-09-02 09:00:00 +0800 CST // 1997-09-03 09:00:00 +0800 CST // ... // 1997-09-10 09:00:00 +0800 CST // 1997-09-11 09:00:00 +0800 CST] fmt.Println(r.Between( time.Date(1997, 9, 6, 0, 0, 0, 0, time.Local), time.Date(1997, 9, 8, 0, 0, 0, 0, time.Local), true)) // [1997-09-06 09:00:00 +0800 CST // 1997-09-07 09:00:00 +0800 CST] fmt.Println(r) // FREQ=DAILY;DTSTART=19970902T010000Z;COUNT=10 fmt.Println("\nEvery four years, the first Tuesday after a Monday in November, 3 occurrences (U.S. Presidential Election day).") r, _ = rrule.NewRRule(rrule.ROption{ Freq: rrule.YEARLY, Interval: 4, Count: 3, Bymonth: []int{11}, Byweekday: []rrule.Weekday{rrule.TU}, Bymonthday: []int{2, 3, 4, 5, 6, 7, 8}, Dtstart: time.Date(1996, 11, 5, 9, 0, 0, 0, time.Local)}) fmt.Println(r.All()) // [1996-11-05 09:00:00 +0800 CST // 2000-11-07 09:00:00 +0800 CST // 2004-11-02 09:00:00 +0800 CST] } func exampleRRuleSet() { fmt.Println("\nDaily, for 7 days, jumping Saturday and Sunday occurrences.") set := rrule.Set{} r, _ := rrule.NewRRule(rrule.ROption{ Freq: rrule.DAILY, Count: 7, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.Local)}) set.RRule(r) fmt.Println(set.All()) // [1997-09-02 09:00:00 +0800 CST // 1997-09-03 09:00:00 +0800 CST // 1997-09-04 09:00:00 +0800 CST // 1997-09-05 09:00:00 +0800 CST // 1997-09-08 09:00:00 +0800 CST] fmt.Println("\nWeekly, for 4 weeks, plus one time on day 7, and not on day 16.") set = rrule.Set{} r, _ = rrule.NewRRule(rrule.ROption{ Freq: rrule.WEEKLY, Count: 4, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.Local)}) set.RRule(r) set.RDate(time.Date(1997, 9, 7, 9, 0, 0, 0, time.Local)) set.ExDate(time.Date(1997, 9, 16, 9, 0, 0, 0, time.Local)) fmt.Println(set.All()) // [1997-09-02 09:00:00 +0800 CST // 1997-09-07 09:00:00 +0800 CST // 1997-09-09 09:00:00 +0800 CST // 1997-09-23 09:00:00 +0800 CST] } func exampleStrToRRule() { fmt.Println() r, _ := rrule.StrToRRule("FREQ=DAILY;INTERVAL=10;COUNT=5") fmt.Println(r.All()) // [2017-03-15 14:12:02 +0800 CST // 2017-03-25 14:12:02 +0800 CST // 2017-04-04 14:12:02 +0800 CST // 2017-04-14 14:12:02 +0800 CST // 2017-04-24 14:12:02 +0800 CST] } func exampleStrToRRuleSet() { s, _ := rrule.StrToRRuleSet(`RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 RDATE:20060102T150405Z`) fmt.Println(s.All()) } func main() { exampleRRule() exampleRRuleSet() exampleStrToRRule() exampleStrToRRuleSet() } rrule-go-1.6.0/go.mod000066400000000000000000000000571370273745400143760ustar00rootroot00000000000000module github.com/teambition/rrule-go go 1.14 rrule-go-1.6.0/go.sum000066400000000000000000000000001370273745400144070ustar00rootroot00000000000000rrule-go-1.6.0/rrule.go000066400000000000000000000607571370273745400147650ustar00rootroot00000000000000package rrule import ( "errors" "fmt" "sort" "time" ) // Every mask is 7 days longer to handle cross-year weekly periods. var ( M366MASK []int M365MASK []int MDAY366MASK []int MDAY365MASK []int NMDAY366MASK []int NMDAY365MASK []int WDAYMASK []int M366RANGE = []int{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} M365RANGE = []int{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365} ) func init() { M366MASK = concat(repeat(1, 31), repeat(2, 29), repeat(3, 31), repeat(4, 30), repeat(5, 31), repeat(6, 30), repeat(7, 31), repeat(8, 31), repeat(9, 30), repeat(10, 31), repeat(11, 30), repeat(12, 31), repeat(1, 7)) M365MASK = concat(M366MASK[:59], M366MASK[60:]) M29, M30, M31 := rang(1, 30), rang(1, 31), rang(1, 32) MDAY366MASK = concat(M31, M29, M31, M30, M31, M30, M31, M31, M30, M31, M30, M31, M31[:7]) MDAY365MASK = concat(MDAY366MASK[:59], MDAY366MASK[60:]) M29, M30, M31 = rang(-29, 0), rang(-30, 0), rang(-31, 0) NMDAY366MASK = concat(M31, M29, M31, M30, M31, M30, M31, M31, M30, M31, M30, M31, M31[:7]) NMDAY365MASK = concat(NMDAY366MASK[:31], NMDAY366MASK[32:]) for i := 0; i < 55; i++ { WDAYMASK = append(WDAYMASK, []int{0, 1, 2, 3, 4, 5, 6}...) } } // Frequency denotes the period on which the rule is evaluated. type Frequency int // Constants const ( YEARLY Frequency = iota MONTHLY WEEKLY DAILY HOURLY MINUTELY SECONDLY ) // Weekday specifying the nth weekday. // Field N could be positive or negative (like MO(+2) or MO(-3). // Not specifying N (0) is the same as specifying +1. type Weekday struct { weekday int n int } // Nth return the nth weekday // __call__ - Cannot call the object directly, // do it through e.g. TH.nth(-1) instead, func (wday *Weekday) Nth(n int) Weekday { return Weekday{wday.weekday, n} } // N returns index of the week, e.g. for 3MO, N() will return 3 func (wday *Weekday) N() int { return wday.n } // Day returns index of the day in a week (0 for MO, 6 for SU) func (wday *Weekday) Day() int { return wday.weekday } // Weekdays var ( MO = Weekday{weekday: 0} TU = Weekday{weekday: 1} WE = Weekday{weekday: 2} TH = Weekday{weekday: 3} FR = Weekday{weekday: 4} SA = Weekday{weekday: 5} SU = Weekday{weekday: 6} ) // ROption offers options to construct a RRule instance type ROption struct { Freq Frequency Dtstart time.Time Interval int Wkst Weekday Count int Until time.Time Bysetpos []int Bymonth []int Bymonthday []int Byyearday []int Byweekno []int Byweekday []Weekday Byhour []int Byminute []int Bysecond []int Byeaster []int } // RRule offers a small, complete, and very fast, implementation of the recurrence rules // documented in the iCalendar RFC, including support for caching of results. type RRule struct { OrigOptions ROption Options ROption freq Frequency dtstart time.Time interval int wkst int count int until time.Time bysetpos []int bymonth []int bymonthday, bynmonthday []int byyearday []int byweekno []int byweekday []int bynweekday []Weekday byhour []int byminute []int bysecond []int byeaster []int timeset []time.Time len int } // NewRRule construct a new RRule instance func NewRRule(arg ROption) (*RRule, error) { if err := validateBounds(arg); err != nil { return nil, err } r := RRule{} r.OrigOptions = arg r.build(arg) return &r, nil } func (r *RRule) build(arg ROption) { // FREQ default to YEARLY r.freq = arg.Freq // INTERVAL default to 1 if arg.Interval < 1 { arg.Interval = 1 } r.interval = arg.Interval if arg.Count < 0 { arg.Count = 0 } r.count = arg.Count // DTSTART default to now if arg.Dtstart.IsZero() { arg.Dtstart = time.Now().UTC() } arg.Dtstart = arg.Dtstart.Truncate(time.Second) r.dtstart = arg.Dtstart // UNTIL if arg.Until.IsZero() { // add largest representable duration (approximately 290 years). r.until = r.dtstart.Add(time.Duration(1<<63 - 1)) } else { arg.Until = arg.Until.Truncate(time.Second) r.until = arg.Until } r.wkst = arg.Wkst.weekday r.bysetpos = arg.Bysetpos if len(arg.Byweekno) == 0 && len(arg.Byyearday) == 0 && len(arg.Bymonthday) == 0 && len(arg.Byweekday) == 0 && len(arg.Byeaster) == 0 { if r.freq == YEARLY { if len(arg.Bymonth) == 0 { arg.Bymonth = []int{int(r.dtstart.Month())} } arg.Bymonthday = []int{r.dtstart.Day()} } else if r.freq == MONTHLY { arg.Bymonthday = []int{r.dtstart.Day()} } else if r.freq == WEEKLY { arg.Byweekday = []Weekday{{weekday: toPyWeekday(r.dtstart.Weekday())}} } } r.bymonth = arg.Bymonth r.byyearday = arg.Byyearday r.byeaster = arg.Byeaster for _, mday := range arg.Bymonthday { if mday > 0 { r.bymonthday = append(r.bymonthday, mday) } else if mday < 0 { r.bynmonthday = append(r.bynmonthday, mday) } } r.byweekno = arg.Byweekno for _, wday := range arg.Byweekday { if wday.n == 0 || r.freq > MONTHLY { r.byweekday = append(r.byweekday, wday.weekday) } else { r.bynweekday = append(r.bynweekday, wday) } } if len(arg.Byhour) == 0 { if r.freq < HOURLY { r.byhour = []int{r.dtstart.Hour()} } } else { r.byhour = arg.Byhour } if len(arg.Byminute) == 0 { if r.freq < MINUTELY { r.byminute = []int{r.dtstart.Minute()} } } else { r.byminute = arg.Byminute } if len(arg.Bysecond) == 0 { if r.freq < SECONDLY { r.bysecond = []int{r.dtstart.Second()} } } else { r.bysecond = arg.Bysecond } // Reset the timeset value r.timeset = []time.Time{} if r.freq < HOURLY { for _, hour := range r.byhour { for _, minute := range r.byminute { for _, second := range r.bysecond { r.timeset = append(r.timeset, time.Date(1, 1, 1, hour, minute, second, 0, r.dtstart.Location())) } } } sort.Sort(timeSlice(r.timeset)) } r.Options = arg } // validateBounds checks the RRule's options are within the boundaries defined // in RRFC 5545. This is useful to ensure that the RRule can even have any times, // as going outside these bounds trivially will never have any dates. This can catch // obvious user error. func validateBounds(arg ROption) error { bounds := []struct { field []int param string bound []int plusMinus bool // If the bound also applies for -x to -y. }{ {arg.Bysecond, "bysecond", []int{0, 59}, false}, {arg.Byminute, "byminute", []int{0, 59}, false}, {arg.Byhour, "byhour", []int{0, 23}, false}, {arg.Bymonthday, "bymonthday", []int{1, 31}, true}, {arg.Byyearday, "byyearday", []int{1, 366}, true}, {arg.Byweekno, "byweekno", []int{1, 53}, true}, {arg.Bymonth, "bymonth", []int{1, 12}, false}, {arg.Bysetpos, "bysetpos", []int{1, 366}, true}, } checkBounds := func(param string, value int, bounds []int, plusMinus bool) error { if !(value >= bounds[0] && value <= bounds[1]) && (!plusMinus || !(value <= -bounds[0] && value >= -bounds[1])) { plusMinusBounds := "" if plusMinus { plusMinusBounds = fmt.Sprintf(" or %d and %d", -bounds[0], -bounds[1]) } return fmt.Errorf("%s must be between %d and %d%s", param, bounds[0], bounds[1], plusMinusBounds) } return nil } for _, b := range bounds { for _, value := range b.field { if err := checkBounds(b.param, value, b.bound, b.plusMinus); err != nil { return err } } } // Days can optionally specify weeks, like BYDAY=+2MO for the 2nd Monday // of the month/year. for _, w := range arg.Byweekday { if w.n > 53 || w.n < -53 { return errors.New("byday must be between 1 and 53 or -1 and -53") } } if arg.Interval < 0 { return errors.New("interval must be greater than 0") } return nil } type iterInfo struct { rrule *RRule lastyear int lastmonth time.Month yearlen int nextyearlen int firstyday time.Time yearweekday int mmask []int mrange []int mdaymask []int nmdaymask []int wdaymask []int wnomask []int nwdaymask []int eastermask []int } func (info *iterInfo) rebuild(year int, month time.Month) { // Every mask is 7 days longer to handle cross-year weekly periods. if year != info.lastyear { info.yearlen = 365 + isLeap(year) info.nextyearlen = 365 + isLeap(year+1) info.firstyday = time.Date( year, time.January, 1, 0, 0, 0, 0, info.rrule.dtstart.Location()) info.yearweekday = toPyWeekday(info.firstyday.Weekday()) info.wdaymask = WDAYMASK[info.yearweekday:] if info.yearlen == 365 { info.mmask = M365MASK info.mdaymask = MDAY365MASK info.nmdaymask = NMDAY365MASK info.mrange = M365RANGE } else { info.mmask = M366MASK info.mdaymask = MDAY366MASK info.nmdaymask = NMDAY366MASK info.mrange = M366RANGE } if len(info.rrule.byweekno) == 0 { info.wnomask = nil } else { info.wnomask = make([]int, info.yearlen+7) firstwkst := pymod(7-info.yearweekday+info.rrule.wkst, 7) no1wkst := firstwkst var wyearlen int if no1wkst >= 4 { no1wkst = 0 // Number of days in the year, plus the days we got from last year. wyearlen = info.yearlen + pymod(info.yearweekday-info.rrule.wkst, 7) } else { // Number of days in the year, minus the days we left in last year. wyearlen = info.yearlen - no1wkst } div, mod := divmod(wyearlen, 7) numweeks := div + mod/4 for _, n := range info.rrule.byweekno { if n < 0 { n += numweeks + 1 } if !(0 < n && n <= numweeks) { continue } var i int if n > 1 { i = no1wkst + (n-1)*7 if no1wkst != firstwkst { i -= 7 - firstwkst } } else { i = no1wkst } for j := 0; j < 7; j++ { info.wnomask[i] = 1 i++ if info.wdaymask[i] == info.rrule.wkst { break } } } if contains(info.rrule.byweekno, 1) { // Check week number 1 of next year as well // TODO: Check -numweeks for next year. i := no1wkst + numweeks*7 if no1wkst != firstwkst { i -= 7 - firstwkst } if i < info.yearlen { // If week starts in next year, we // don't care about it. for j := 0; j < 7; j++ { info.wnomask[i] = 1 i++ if info.wdaymask[i] == info.rrule.wkst { break } } } } if no1wkst != 0 { // Check last week number of last year as // well. If no1wkst is 0, either the year // started on week start, or week number 1 // got days from last year, so there are no // days from last year's last week number in // this year. var lnumweeks int if !contains(info.rrule.byweekno, -1) { lyearweekday := toPyWeekday(time.Date( year-1, 1, 1, 0, 0, 0, 0, info.rrule.dtstart.Location()).Weekday()) lno1wkst := pymod(7-lyearweekday+info.rrule.wkst, 7) lyearlen := 365 + isLeap(year-1) if lno1wkst >= 4 { lno1wkst = 0 lnumweeks = 52 + pymod(lyearlen+pymod(lyearweekday-info.rrule.wkst, 7), 7)/4 } else { lnumweeks = 52 + pymod(info.yearlen-no1wkst, 7)/4 } } else { lnumweeks = -1 } if contains(info.rrule.byweekno, lnumweeks) { for i := 0; i < no1wkst; i++ { info.wnomask[i] = 1 } } } } } if len(info.rrule.bynweekday) != 0 && (month != info.lastmonth || year != info.lastyear) { var ranges [][]int if info.rrule.freq == YEARLY { if len(info.rrule.bymonth) != 0 { for _, month := range info.rrule.bymonth { ranges = append(ranges, info.mrange[month-1:month+1]) } } else { ranges = [][]int{[]int{0, info.yearlen}} } } else if info.rrule.freq == MONTHLY { ranges = [][]int{info.mrange[month-1 : month+1]} } if len(ranges) != 0 { // Weekly frequency won't get here, so we may not // care about cross-year weekly periods. info.nwdaymask = make([]int, info.yearlen) for _, x := range ranges { first, last := x[0], x[1] last-- for _, y := range info.rrule.bynweekday { wday, n := y.weekday, y.n var i int if n < 0 { i = last + (n+1)*7 i -= pymod(info.wdaymask[i]-wday, 7) } else { i = first + (n-1)*7 i += pymod(7-info.wdaymask[i]+wday, 7) } if first <= i && i <= last { info.nwdaymask[i] = 1 } } } } } if len(info.rrule.byeaster) != 0 { info.eastermask = make([]int, info.yearlen+7) eyday := easter(year).YearDay() - 1 for _, offset := range info.rrule.byeaster { info.eastermask[eyday+offset] = 1 } } info.lastyear = year info.lastmonth = month } func (info *iterInfo) getdayset(freq Frequency, year int, month time.Month, day int) ([]*int, int, int) { switch freq { case YEARLY: set := make([]*int, info.yearlen) for i := 0; i < info.yearlen; i++ { temp := i set[i] = &temp } return set, 0, info.yearlen case MONTHLY: set := make([]*int, info.yearlen) start, end := info.mrange[month-1], info.mrange[month] for i := start; i < end; i++ { temp := i set[i] = &temp } return set, start, end case WEEKLY: // We need to handle cross-year weeks here. set := make([]*int, info.yearlen+7) i := time.Date(year, month, day, 0, 0, 0, 0, time.UTC).YearDay() - 1 start := i for j := 0; j < 7; j++ { temp := i set[i] = &temp i++ // if (not (0 <= i < self.yearlen) or // self.wdaymask[i] == self.rrule._wkst): // This will cross the year boundary, if necessary. if info.wdaymask[i] == info.rrule.wkst { break } } return set, start, i } // DAILY, HOURLY, MINUTELY, SECONDLY: set := make([]*int, info.yearlen) i := time.Date(year, month, day, 0, 0, 0, 0, time.UTC).YearDay() - 1 set[i] = &i return set, i, i + 1 } func (info *iterInfo) gettimeset(freq Frequency, hour, minute, second int) (result []time.Time) { switch freq { case HOURLY: for _, minute := range info.rrule.byminute { for _, second := range info.rrule.bysecond { result = append(result, time.Date(1, 1, 1, hour, minute, second, 0, info.rrule.dtstart.Location())) } } sort.Sort(timeSlice(result)) case MINUTELY: for _, second := range info.rrule.bysecond { result = append(result, time.Date(1, 1, 1, hour, minute, second, 0, info.rrule.dtstart.Location())) } sort.Sort(timeSlice(result)) case SECONDLY: result = []time.Time{time.Date(1, 1, 1, hour, minute, second, 0, info.rrule.dtstart.Location())} } return } // rIterator is a iterator of RRule type rIterator struct { year int month time.Month day int hour int minute int second int weekday int ii iterInfo timeset []time.Time total int count int remain []time.Time finished bool } func (iterator *rIterator) generate() { r := iterator.ii.rrule for len(iterator.remain) == 0 { // Get dayset with the right frequency dayset, start, end := iterator.ii.getdayset(r.freq, iterator.year, iterator.month, iterator.day) // Do the "hard" work ;-) filtered := false for _, i := range dayset[start:end] { if len(r.bymonth) != 0 && !contains(r.bymonth, iterator.ii.mmask[*i]) || len(r.byweekno) != 0 && iterator.ii.wnomask[*i] == 0 || len(r.byweekday) != 0 && !contains(r.byweekday, iterator.ii.wdaymask[*i]) || len(iterator.ii.nwdaymask) != 0 && iterator.ii.nwdaymask[*i] == 0 || len(r.byeaster) != 0 && iterator.ii.eastermask[*i] == 0 || (len(r.bymonthday) != 0 || len(r.bynmonthday) != 0) && !contains(r.bymonthday, iterator.ii.mdaymask[*i]) && !contains(r.bynmonthday, iterator.ii.nmdaymask[*i]) || len(r.byyearday) != 0 && (*i < iterator.ii.yearlen && !contains(r.byyearday, *i+1) && !contains(r.byyearday, -iterator.ii.yearlen+*i) || *i >= iterator.ii.yearlen && !contains(r.byyearday, *i+1-iterator.ii.yearlen) && !contains(r.byyearday, -iterator.ii.nextyearlen+*i-iterator.ii.yearlen)) { dayset[*i] = nil filtered = true } } // Output results if len(r.bysetpos) != 0 && len(iterator.timeset) != 0 { poslist := []time.Time{} for _, pos := range r.bysetpos { var daypos, timepos int if pos < 0 { daypos, timepos = divmod(pos, len(iterator.timeset)) } else { daypos, timepos = divmod(pos-1, len(iterator.timeset)) } temp := []int{} for _, x := range dayset[start:end] { if x != nil { temp = append(temp, *x) } } i, err := pySubscript(temp, daypos) if err != nil { continue } timeTemp := iterator.timeset[timepos] date := iterator.ii.firstyday.AddDate(0, 0, i) res := time.Date(date.Year(), date.Month(), date.Day(), timeTemp.Hour(), timeTemp.Minute(), timeTemp.Second(), timeTemp.Nanosecond(), timeTemp.Location()) if !timeContains(poslist, res) { poslist = append(poslist, res) } } sort.Sort(timeSlice(poslist)) for _, res := range poslist { if !r.until.IsZero() && res.After(r.until) { r.len = iterator.total iterator.finished = true return } else if !res.Before(r.dtstart) { iterator.total++ iterator.remain = append(iterator.remain, res) if iterator.count != 0 { iterator.count-- if iterator.count == 0 { r.len = iterator.total iterator.finished = true return } } } } } else { for _, i := range dayset[start:end] { if i == nil { continue } date := iterator.ii.firstyday.AddDate(0, 0, *i) for _, timeTemp := range iterator.timeset { res := time.Date(date.Year(), date.Month(), date.Day(), timeTemp.Hour(), timeTemp.Minute(), timeTemp.Second(), timeTemp.Nanosecond(), timeTemp.Location()) if !r.until.IsZero() && res.After(r.until) { r.len = iterator.total iterator.finished = true return } else if !res.Before(r.dtstart) { iterator.total++ iterator.remain = append(iterator.remain, res) if iterator.count != 0 { iterator.count-- if iterator.count == 0 { r.len = iterator.total iterator.finished = true return } } } } } } // Handle frequency and interval fixday := false if r.freq == YEARLY { iterator.year += r.interval if iterator.year > MAXYEAR { r.len = iterator.total iterator.finished = true return } iterator.ii.rebuild(iterator.year, iterator.month) } else if r.freq == MONTHLY { iterator.month += time.Month(r.interval) if iterator.month > 12 { div, mod := divmod(int(iterator.month), 12) iterator.month = time.Month(mod) iterator.year += div if iterator.month == 0 { iterator.month = 12 iterator.year-- } if iterator.year > MAXYEAR { r.len = iterator.total iterator.finished = true return } } iterator.ii.rebuild(iterator.year, iterator.month) } else if r.freq == WEEKLY { if r.wkst > iterator.weekday { iterator.day += -(iterator.weekday + 1 + (6 - r.wkst)) + r.interval*7 } else { iterator.day += -(iterator.weekday - r.wkst) + r.interval*7 } iterator.weekday = r.wkst fixday = true } else if r.freq == DAILY { iterator.day += r.interval fixday = true } else if r.freq == HOURLY { if filtered { // Jump to one iteration before next day iterator.hour += ((23 - iterator.hour) / r.interval) * r.interval } for { iterator.hour += r.interval div, mod := divmod(iterator.hour, 24) if div != 0 { iterator.hour = mod iterator.day += div fixday = true } if len(r.byhour) == 0 || contains(r.byhour, iterator.hour) { break } } iterator.timeset = iterator.ii.gettimeset(r.freq, iterator.hour, iterator.minute, iterator.second) } else if r.freq == MINUTELY { if filtered { // Jump to one iteration before next day iterator.minute += ((1439 - (iterator.hour*60 + iterator.minute)) / r.interval) * r.interval } for { iterator.minute += r.interval div, mod := divmod(iterator.minute, 60) if div != 0 { iterator.minute = mod iterator.hour += div div, mod = divmod(iterator.hour, 24) if div != 0 { iterator.hour = mod iterator.day += div fixday = true filtered = false } } if (len(r.byhour) == 0 || contains(r.byhour, iterator.hour)) && (len(r.byminute) == 0 || contains(r.byminute, iterator.minute)) { break } } iterator.timeset = iterator.ii.gettimeset(r.freq, iterator.hour, iterator.minute, iterator.second) } else if r.freq == SECONDLY { if filtered { // Jump to one iteration before next day iterator.second += (((86399 - (iterator.hour*3600 + iterator.minute*60 + iterator.second)) / r.interval) * r.interval) } for { iterator.second += r.interval div, mod := divmod(iterator.second, 60) if div != 0 { iterator.second = mod iterator.minute += div div, mod = divmod(iterator.minute, 60) if div != 0 { iterator.minute = mod iterator.hour += div div, mod = divmod(iterator.hour, 24) if div != 0 { iterator.hour = mod iterator.day += div fixday = true } } } if (len(r.byhour) == 0 || contains(r.byhour, iterator.hour)) && (len(r.byminute) == 0 || contains(r.byminute, iterator.minute)) && (len(r.bysecond) == 0 || contains(r.bysecond, iterator.second)) { break } } iterator.timeset = iterator.ii.gettimeset(r.freq, iterator.hour, iterator.minute, iterator.second) } if fixday && iterator.day > 28 { daysinmonth := daysIn(iterator.month, iterator.year) if iterator.day > daysinmonth { for iterator.day > daysinmonth { iterator.day -= daysinmonth iterator.month++ if iterator.month == 13 { iterator.month = 1 iterator.year++ if iterator.year > MAXYEAR { r.len = iterator.total iterator.finished = true return } } daysinmonth = daysIn(iterator.month, iterator.year) } iterator.ii.rebuild(iterator.year, iterator.month) } } } } // next returns next occurrence and true if it exists, else zero value and false func (iterator *rIterator) next() (time.Time, bool) { if !iterator.finished { iterator.generate() } if len(iterator.remain) == 0 { return time.Time{}, false } value := iterator.remain[0] iterator.remain = iterator.remain[1:] return value, true } // Iterator return an iterator for RRule func (r *RRule) Iterator() Next { iterator := rIterator{} iterator.year, iterator.month, iterator.day = r.dtstart.Date() iterator.hour, iterator.minute, iterator.second = r.dtstart.Clock() iterator.weekday = toPyWeekday(r.dtstart.Weekday()) iterator.ii = iterInfo{rrule: r} iterator.ii.rebuild(iterator.year, iterator.month) if r.freq < HOURLY { iterator.timeset = r.timeset } else { if r.freq >= HOURLY && len(r.byhour) != 0 && !contains(r.byhour, iterator.hour) || r.freq >= MINUTELY && len(r.byminute) != 0 && !contains(r.byminute, iterator.minute) || r.freq >= SECONDLY && len(r.bysecond) != 0 && !contains(r.bysecond, iterator.second) { iterator.timeset = []time.Time{} } else { iterator.timeset = iterator.ii.gettimeset(r.freq, iterator.hour, iterator.minute, iterator.second) } } iterator.count = r.count return iterator.next } // All returns all occurrences of the RRule. func (r *RRule) All() []time.Time { return all(r.Iterator()) } // Between returns all the occurrences of the RRule between after and before. // The inc keyword defines what happens if after and/or before are themselves occurrences. // With inc == True, they will be included in the list, if they are found in the recurrence set. func (r *RRule) Between(after, before time.Time, inc bool) []time.Time { return between(r.Iterator(), after, before, inc) } // Before returns the last recurrence before the given datetime instance, // or time.Time's zero value if no recurrence match. // The inc keyword defines what happens if dt is an occurrence. // With inc == True, if dt itself is an occurrence, it will be returned. func (r *RRule) Before(dt time.Time, inc bool) time.Time { return before(r.Iterator(), dt, inc) } // After returns the first recurrence after the given datetime instance, // or time.Time's zero value if no recurrence match. // The inc keyword defines what happens if dt is an occurrence. // With inc == True, if dt itself is an occurrence, it will be returned. func (r *RRule) After(dt time.Time, inc bool) time.Time { return after(r.Iterator(), dt, inc) } // DTStart set a new DTStart for the rule and recalculates the timeset if needed. func (r *RRule) DTStart(dt time.Time) { r.OrigOptions.Dtstart = dt.Truncate(time.Second) r.build(r.OrigOptions) } // Until set a new Until for the rule and recalculates the timeset if needed. func (r *RRule) Until(ut time.Time) { r.OrigOptions.Until = ut.Truncate(time.Second) r.build(r.OrigOptions) } rrule-go-1.6.0/rrule_test.go000066400000000000000000003623311370273745400160150ustar00rootroot00000000000000package rrule import ( "testing" "time" ) func timesEqual(value, want []time.Time) bool { if len(value) != len(want) { return false } for index := range value { if value[index] != want[index] { return false } } return true } func TestNoDtstart(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY}) if time.Now().Unix()-r.dtstart.Unix() > 1 { t.Errorf(`default Dtstart shold be time.Now(), but got %s`, r.dtstart.Format(time.RFC3339)) } } func TestBadBySetPos(t *testing.T) { _, e := NewRRule(ROption{Freq: MONTHLY, Count: 1, Bysetpos: []int{0}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) if e == nil { t.Error("get nil, want error") } } func TestBadBySetPosMany(t *testing.T) { _, e := NewRRule(ROption{Freq: MONTHLY, Count: 1, Bysetpos: []int{-1, 0, 1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) if e == nil { t.Error("get nil, want error") } } func TestByNegativeMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bymonthday: []int{-1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 30, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 31, 9, 0, 0, 0, time.UTC), time.Date(1997, 11, 30, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyMaxYear(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Interval: 15, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), }) value := r.All()[1] want := time.Date(1998, 12, 2, 9, 0, 0, 0, time.UTC) if value != want { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyMaxYear(t *testing.T) { // Purposefully doesn't match anything for code coverage. r, _ := NewRRule(ROption{Freq: WEEKLY, Bymonthday: []int{31}, Byyearday: []int{1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), }) value := r.All() want := []time.Time{} if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestInvalidRRules(t *testing.T) { tests := []struct { desc string rrule ROption wantErr string }{ { desc: "Bysecond under", rrule: ROption{Freq: YEARLY, Bysecond: []int{-1}}, wantErr: "bysecond must be between 0 and 59", }, { desc: "Bysecond over", rrule: ROption{Freq: YEARLY, Bysecond: []int{60}}, wantErr: "bysecond must be between 0 and 59", }, { desc: "Byminute under", rrule: ROption{Freq: YEARLY, Byminute: []int{-1}}, wantErr: "byminute must be between 0 and 59", }, { desc: "Byminute over", rrule: ROption{Freq: YEARLY, Byminute: []int{60}}, wantErr: "byminute must be between 0 and 59", }, { desc: "Byhour under", rrule: ROption{Freq: YEARLY, Byhour: []int{-1}}, wantErr: "byhour must be between 0 and 23", }, { desc: "Byhour over", rrule: ROption{Freq: YEARLY, Byhour: []int{24}}, wantErr: "byhour must be between 0 and 23", }, { desc: "Bymonthday under", rrule: ROption{Freq: YEARLY, Bymonthday: []int{0}}, wantErr: "bymonthday must be between 1 and 31 or -1 and -31", }, { desc: "Bymonthday over", rrule: ROption{Freq: YEARLY, Bymonthday: []int{32}}, wantErr: "bymonthday must be between 1 and 31 or -1 and -31", }, { desc: "Bymonthday under negative", rrule: ROption{Freq: YEARLY, Bymonthday: []int{-32}}, wantErr: "bymonthday must be between 1 and 31 or -1 and -31", }, { desc: "Byyearday under", rrule: ROption{Freq: YEARLY, Byyearday: []int{0}}, wantErr: "byyearday must be between 1 and 366 or -1 and -366", }, { desc: "Byyearday over", rrule: ROption{Freq: YEARLY, Byyearday: []int{367}}, wantErr: "byyearday must be between 1 and 366 or -1 and -366", }, { desc: "Byyearday under negative", rrule: ROption{Freq: YEARLY, Byyearday: []int{-367}}, wantErr: "byyearday must be between 1 and 366 or -1 and -366", }, { desc: "Byweekno under", rrule: ROption{Freq: YEARLY, Byweekno: []int{0}}, wantErr: "byweekno must be between 1 and 53 or -1 and -53", }, { desc: "Byweekno over", rrule: ROption{Freq: YEARLY, Byweekno: []int{54}}, wantErr: "byweekno must be between 1 and 53 or -1 and -53", }, { desc: "Byweekno under negative", rrule: ROption{Freq: YEARLY, Byweekno: []int{-54}}, wantErr: "byweekno must be between 1 and 53 or -1 and -53", }, { desc: "Bymonth under", rrule: ROption{Freq: YEARLY, Bymonth: []int{0}}, wantErr: "bymonth must be between 1 and 12", }, { desc: "Bymonth over", rrule: ROption{Freq: YEARLY, Bymonth: []int{13}}, wantErr: "bymonth must be between 1 and 12", }, { desc: "Bysetpos under", rrule: ROption{Freq: YEARLY, Bysetpos: []int{0}}, wantErr: "bysetpos must be between 1 and 366 or -1 and -366", }, { desc: "Bysetpos over", rrule: ROption{Freq: YEARLY, Bysetpos: []int{367}}, wantErr: "bysetpos must be between 1 and 366 or -1 and -366", }, { desc: "Bysetpos under negative", rrule: ROption{Freq: YEARLY, Bysetpos: []int{-367}}, wantErr: "bysetpos must be between 1 and 366 or -1 and -366", }, { desc: "Byday under", rrule: ROption{Freq: YEARLY, Byweekday: []Weekday{{1, -54}}}, wantErr: "byday must be between 1 and 53 or -1 and -53", }, { desc: "Byday over", rrule: ROption{Freq: YEARLY, Byweekday: []Weekday{{1, 54}}}, wantErr: "byday must be between 1 and 53 or -1 and -53", }, { desc: "Interval under", rrule: ROption{Freq: DAILY, Interval: -1}, wantErr: "interval must be greater than 0", }, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { _, err := NewRRule(tc.rrule) if err == nil || err.Error() != tc.wantErr { t.Errorf("got %q, want %q", err, tc.wantErr) } }) } } func TestHourlyInvalidAndRepeatedBysetpos(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Bysetpos: []int{1, -1, 2}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), Until: time.Date(1997, 9, 2, 11, 0, 0, 0, time.UTC)}) value := r.All() want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 10, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 11, 0, 0, 0, time.UTC)} if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestNoAfter(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 5, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := time.Time{} value := r.After(time.Date(1997, 9, 6, 9, 0, 0, 0, time.UTC), false) if value != want { t.Errorf("get %v, want %v", value, want) } } // Test cases from Python Dateutil func TestYearly(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1998, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1999, 9, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyInterval(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Interval: 2, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1999, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(2001, 9, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyIntervalLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Interval: 100, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(2097, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(2197, 9, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMonth(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bymonth: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 2, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 2, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bymonthday: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 1, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMonthAndMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{5, 7}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 5, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 7, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 5, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 25, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 6, 9, 0, 0, 0, time.UTC), time.Date(1998, 12, 31, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByNWeekDayLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byweekday: []Weekday{TU.Nth(3), TH.Nth(-3)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 11, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 20, 9, 0, 0, 0, time.UTC), time.Date(1998, 12, 17, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMonthAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 6, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 8, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMonthAndNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 6, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 29, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMonthAndNWeekDayLarge(t *testing.T) { // This is interesting because the TH.Nth(-3) ends up before // the TU.Nth(3). r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU.Nth(3), TH.Nth(-3)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 15, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 20, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 12, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 2, 3, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMonthAndMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 3, 9, 0, 0, 0, time.UTC), time.Date(2001, 3, 1, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 4, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 4, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMonthAndYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 4, Bymonth: []int{4, 7}, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1999, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMonthAndYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 4, Bymonth: []int{4, 7}, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1999, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByWeekNo(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byweekno: []int{20}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 5, 11, 9, 0, 0, 0, time.UTC), time.Date(1998, 5, 12, 9, 0, 0, 0, time.UTC), time.Date(1998, 5, 13, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByWeekNoAndWeekDay(t *testing.T) { // That's a nice one. The first days of week number one // may be in the last year. r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byweekno: []int{1}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 29, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 4, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByWeekNoAndWeekDayLarge(t *testing.T) { // Another nice test. The last days of week number 52/53 // may be in the next year. r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byweekno: []int{52}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(1998, 12, 27, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByWeekNoAndWeekDayLast(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byweekno: []int{-1}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 3, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByEaster(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byeaster: []int{0}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 12, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 4, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 23, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByEasterPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byeaster: []int{1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 13, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 5, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 24, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByEasterNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byeaster: []int{-1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 11, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 3, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 22, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByWeekNoAndWeekDay53(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byweekno: []int{53}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(2004, 12, 27, 9, 0, 0, 0, time.UTC), time.Date(2009, 12, 28, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByHour(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byhour: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 0, 0, time.UTC), time.Date(1998, 9, 2, 6, 0, 0, 0, time.UTC), time.Date(1998, 9, 2, 18, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 0, 0, time.UTC), time.Date(1998, 9, 2, 9, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyBySecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 18, 0, time.UTC), time.Date(1998, 9, 2, 9, 0, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByHourAndMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 0, 0, time.UTC), time.Date(1998, 9, 2, 6, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByHourAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byhour: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 0, 18, 0, time.UTC), time.Date(1998, 9, 2, 6, 0, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyByHourAndMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestYearlyBySetPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bymonthday: []int{15}, Byhour: []int{6, 18}, Bysetpos: []int{3, -3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 11, 15, 18, 0, 0, 0, time.UTC), time.Date(1998, 2, 15, 6, 0, 0, 0, time.UTC), time.Date(1998, 11, 15, 18, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthly(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 11, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyInterval(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Interval: 2, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 11, 2, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyIntervalLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Interval: 18, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1999, 3, 2, 9, 0, 0, 0, time.UTC), time.Date(2000, 9, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMonth(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bymonth: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 2, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 2, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bymonthday: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 1, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMonthAndMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{5, 7}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 5, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 7, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 5, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 25, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 7, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByNWeekDayLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byweekday: []Weekday{TU.Nth(3), TH.Nth(-3)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 11, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 16, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 16, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMonthAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 6, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 8, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMonthAndNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 6, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 29, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMonthAndNWeekDayLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU.Nth(3), TH.Nth(-3)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 15, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 20, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 12, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 2, 3, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMonthAndMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 3, 9, 0, 0, 0, time.UTC), time.Date(2001, 3, 1, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 4, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 4, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMonthAndYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 4, Bymonth: []int{4, 7}, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1999, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMonthAndYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 4, Bymonth: []int{4, 7}, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1999, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByWeekNo(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byweekno: []int{20}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 5, 11, 9, 0, 0, 0, time.UTC), time.Date(1998, 5, 12, 9, 0, 0, 0, time.UTC), time.Date(1998, 5, 13, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByWeekNoAndWeekDay(t *testing.T) { // That's a nice one. The first days of week number one // may be in the last year. r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byweekno: []int{1}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 29, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 4, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByWeekNoAndWeekDayLarge(t *testing.T) { // Another nice test. The last days of week number 52/53 // may be in the next year. r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byweekno: []int{52}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(1998, 12, 27, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByWeekNoAndWeekDayLast(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byweekno: []int{-1}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 3, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByWeekNoAndWeekDay53(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byweekno: []int{53}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(2004, 12, 27, 9, 0, 0, 0, time.UTC), time.Date(2009, 12, 28, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByEaster(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byeaster: []int{0}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 12, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 4, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 23, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByEasterPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byeaster: []int{1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 13, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 5, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 24, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByEasterNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byeaster: []int{-1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 11, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 3, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 22, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByHour(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byhour: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 0, 0, time.UTC), time.Date(1997, 10, 2, 6, 0, 0, 0, time.UTC), time.Date(1997, 10, 2, 18, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 0, 0, time.UTC), time.Date(1997, 10, 2, 9, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyBySecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 18, 0, time.UTC), time.Date(1997, 10, 2, 9, 0, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByHourAndMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 0, 0, time.UTC), time.Date(1997, 10, 2, 6, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByHourAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byhour: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 0, 18, 0, time.UTC), time.Date(1997, 10, 2, 6, 0, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyByHourAndMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMonthlyBySetPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 3, Bymonthday: []int{13, 17}, Byhour: []int{6, 18}, Bysetpos: []int{3, -3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 13, 18, 0, 0, 0, time.UTC), time.Date(1997, 9, 17, 6, 0, 0, 0, time.UTC), time.Date(1997, 10, 13, 18, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeekly(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 16, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyInterval(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Interval: 2, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 16, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 30, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyIntervalLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Interval: 20, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 20, 9, 0, 0, 0, time.UTC), time.Date(1998, 6, 9, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMonth(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Bymonth: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 6, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 13, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 20, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Bymonthday: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 1, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMonthAndMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{5, 7}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 5, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 7, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 5, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMonthAndWeekDay(t *testing.T) { // This test is interesting, because it crosses the year // boundary in a weekly period to find day '1' as a // valid recurrence. r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 6, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 8, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMonthAndNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 6, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 8, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 2, 3, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMonthAndMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 3, 9, 0, 0, 0, time.UTC), time.Date(2001, 3, 1, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 4, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 4, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMonthAndYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 4, Bymonth: []int{1, 7}, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1999, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMonthAndYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 4, Bymonth: []int{1, 7}, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1999, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByWeekNo(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byweekno: []int{20}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 5, 11, 9, 0, 0, 0, time.UTC), time.Date(1998, 5, 12, 9, 0, 0, 0, time.UTC), time.Date(1998, 5, 13, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByWeekNoAndWeekDay(t *testing.T) { // That's a nice one. The first days of week number one // may be in the last year. r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byweekno: []int{1}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 29, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 4, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByWeekNoAndWeekDayLarge(t *testing.T) { // Another nice test. The last days of week number 52/53 // may be in the next year. r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byweekno: []int{52}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(1998, 12, 27, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByWeekNoAndWeekDayLast(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byweekno: []int{-1}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 3, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByWeekNoAndWeekDay53(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byweekno: []int{53}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(2004, 12, 27, 9, 0, 0, 0, time.UTC), time.Date(2009, 12, 28, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByEaster(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byeaster: []int{0}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 12, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 4, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 23, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByEasterPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byeaster: []int{1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 13, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 5, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 24, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByEasterNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byeaster: []int{-1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 11, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 3, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 22, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByHour(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byhour: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 6, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 18, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyBySecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 18, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByHourAndMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 0, 0, time.UTC), time.Date(1997, 9, 9, 6, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByHourAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byhour: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 0, 18, 0, time.UTC), time.Date(1997, 9, 9, 6, 0, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyByHourAndMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWeeklyBySetPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Byweekday: []Weekday{TU, TH}, Byhour: []int{6, 18}, Bysetpos: []int{3, -3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 6, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 18, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDaily(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyInterval(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Interval: 2, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 6, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyIntervalLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Interval: 92, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 12, 3, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 5, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMonth(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Bymonth: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 2, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Bymonthday: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 1, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMonthAndMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{5, 7}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 5, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 7, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 5, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMonthAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 6, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 8, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMonthAndNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 6, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 8, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 2, 3, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMonthAndMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 3, 3, 9, 0, 0, 0, time.UTC), time.Date(2001, 3, 1, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 4, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 4, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 9, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMonthAndYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 4, Bymonth: []int{1, 7}, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1999, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMonthAndYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 4, Bymonth: []int{1, 7}, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1998, 7, 19, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 1, 9, 0, 0, 0, time.UTC), time.Date(1999, 7, 19, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByWeekNo(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byweekno: []int{20}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 5, 11, 9, 0, 0, 0, time.UTC), time.Date(1998, 5, 12, 9, 0, 0, 0, time.UTC), time.Date(1998, 5, 13, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByWeekNoAndWeekDay(t *testing.T) { // That's a nice one. The first days of week number one // may be in the last year. r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byweekno: []int{1}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 29, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 4, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 3, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByWeekNoAndWeekDayLarge(t *testing.T) { // Another nice test. The last days of week number 52/53 // may be in the next year. r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byweekno: []int{52}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(1998, 12, 27, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByWeekNoAndWeekDayLast(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byweekno: []int{-1}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(1999, 1, 3, 9, 0, 0, 0, time.UTC), time.Date(2000, 1, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByWeekNoAndWeekDay53(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byweekno: []int{53}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 12, 28, 9, 0, 0, 0, time.UTC), time.Date(2004, 12, 27, 9, 0, 0, 0, time.UTC), time.Date(2009, 12, 28, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByEaster(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byeaster: []int{0}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 12, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 4, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 23, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByEasterPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byeaster: []int{1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 13, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 5, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 24, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByEasterNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byeaster: []int{-1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 11, 9, 0, 0, 0, time.UTC), time.Date(1999, 4, 3, 9, 0, 0, 0, time.UTC), time.Date(2000, 4, 22, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByHour(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byhour: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 6, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 18, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 0, 0, time.UTC), time.Date(1997, 9, 3, 9, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyBySecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 18, 0, time.UTC), time.Date(1997, 9, 3, 9, 0, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByHourAndMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 0, 0, time.UTC), time.Date(1997, 9, 3, 6, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByHourAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byhour: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 0, 18, 0, time.UTC), time.Date(1997, 9, 3, 6, 0, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyByHourAndMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDailyBySetPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{15, 45}, Bysetpos: []int{3, -3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 15, 0, 0, time.UTC), time.Date(1997, 9, 3, 6, 45, 0, 0, time.UTC), time.Date(1997, 9, 3, 18, 15, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourly(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 10, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 11, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyInterval(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Interval: 2, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 11, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 13, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyIntervalLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Interval: 769, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 10, 4, 10, 0, 0, 0, time.UTC), time.Date(1997, 11, 5, 11, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMonth(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Bymonth: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 1, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Bymonthday: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 3, 0, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 1, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMonthAndMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{5, 7}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 5, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 5, 1, 0, 0, 0, time.UTC), time.Date(1998, 1, 5, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 10, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 11, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 10, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 11, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMonthAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 1, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMonthAndNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 1, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 1, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMonthAndMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 1, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 4, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 31, 1, 0, 0, 0, time.UTC), time.Date(1997, 12, 31, 2, 0, 0, 0, time.UTC), time.Date(1997, 12, 31, 3, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 4, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 31, 1, 0, 0, 0, time.UTC), time.Date(1997, 12, 31, 2, 0, 0, 0, time.UTC), time.Date(1997, 12, 31, 3, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMonthAndYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 4, Bymonth: []int{4, 7}, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 10, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 1, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 2, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 3, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMonthAndYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 4, Bymonth: []int{4, 7}, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 10, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 1, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 2, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 3, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByWeekNo(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byweekno: []int{20}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 5, 11, 0, 0, 0, 0, time.UTC), time.Date(1998, 5, 11, 1, 0, 0, 0, time.UTC), time.Date(1998, 5, 11, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByWeekNoAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byweekno: []int{1}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 29, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 29, 1, 0, 0, 0, time.UTC), time.Date(1997, 12, 29, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByWeekNoAndWeekDayLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byweekno: []int{52}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 28, 1, 0, 0, 0, time.UTC), time.Date(1997, 12, 28, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByWeekNoAndWeekDayLast(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byweekno: []int{-1}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 28, 1, 0, 0, 0, time.UTC), time.Date(1997, 12, 28, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByWeekNoAndWeekDay53(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byweekno: []int{53}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 12, 28, 0, 0, 0, 0, time.UTC), time.Date(1998, 12, 28, 1, 0, 0, 0, time.UTC), time.Date(1998, 12, 28, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByEaster(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byeaster: []int{0}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 12, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 12, 1, 0, 0, 0, time.UTC), time.Date(1998, 4, 12, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByEasterPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byeaster: []int{1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 13, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 13, 1, 0, 0, 0, time.UTC), time.Date(1998, 4, 13, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByEasterNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byeaster: []int{-1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 11, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 11, 1, 0, 0, 0, time.UTC), time.Date(1998, 4, 11, 2, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByHour(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byhour: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 6, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 18, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 0, 0, time.UTC), time.Date(1997, 9, 2, 10, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyBySecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 18, 0, time.UTC), time.Date(1997, 9, 2, 10, 0, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByHourAndMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 0, 0, time.UTC), time.Date(1997, 9, 3, 6, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByHourAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byhour: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 0, 18, 0, time.UTC), time.Date(1997, 9, 3, 6, 0, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyByHourAndMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestHourlyBySetPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: HOURLY, Count: 3, Byminute: []int{15, 45}, Bysecond: []int{15, 45}, Bysetpos: []int{3, -3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 15, 45, 0, time.UTC), time.Date(1997, 9, 2, 9, 45, 15, 0, time.UTC), time.Date(1997, 9, 2, 10, 15, 45, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutely(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 1, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyInterval(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Interval: 2, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 2, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 4, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyIntervalLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Interval: 1501, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 10, 1, 0, 0, time.UTC), time.Date(1997, 9, 4, 11, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMonth(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Bymonth: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 1, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Bymonthday: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 3, 0, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 0, 1, 0, 0, time.UTC), time.Date(1997, 9, 3, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMonthAndMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{5, 7}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 5, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 5, 0, 1, 0, 0, time.UTC), time.Date(1998, 1, 5, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 1, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 1, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMonthAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 1, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMonthAndNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 1, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 1, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMonthAndMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 1, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 4, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 31, 0, 1, 0, 0, time.UTC), time.Date(1997, 12, 31, 0, 2, 0, 0, time.UTC), time.Date(1997, 12, 31, 0, 3, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 4, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 31, 0, 1, 0, 0, time.UTC), time.Date(1997, 12, 31, 0, 2, 0, 0, time.UTC), time.Date(1997, 12, 31, 0, 3, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMonthAndYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 4, Bymonth: []int{4, 7}, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 10, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 0, 1, 0, 0, time.UTC), time.Date(1998, 4, 10, 0, 2, 0, 0, time.UTC), time.Date(1998, 4, 10, 0, 3, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMonthAndYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 4, Bymonth: []int{4, 7}, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 10, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 0, 1, 0, 0, time.UTC), time.Date(1998, 4, 10, 0, 2, 0, 0, time.UTC), time.Date(1998, 4, 10, 0, 3, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByWeekNo(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byweekno: []int{20}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 5, 11, 0, 0, 0, 0, time.UTC), time.Date(1998, 5, 11, 0, 1, 0, 0, time.UTC), time.Date(1998, 5, 11, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByWeekNoAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byweekno: []int{1}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 29, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 29, 0, 1, 0, 0, time.UTC), time.Date(1997, 12, 29, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByWeekNoAndWeekDayLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byweekno: []int{52}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 28, 0, 1, 0, 0, time.UTC), time.Date(1997, 12, 28, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByWeekNoAndWeekDayLast(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byweekno: []int{-1}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 28, 0, 1, 0, 0, time.UTC), time.Date(1997, 12, 28, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByWeekNoAndWeekDay53(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byweekno: []int{53}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 12, 28, 0, 0, 0, 0, time.UTC), time.Date(1998, 12, 28, 0, 1, 0, 0, time.UTC), time.Date(1998, 12, 28, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByEaster(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byeaster: []int{0}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 12, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 12, 0, 1, 0, 0, time.UTC), time.Date(1998, 4, 12, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByEasterPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byeaster: []int{1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 13, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 13, 0, 1, 0, 0, time.UTC), time.Date(1998, 4, 13, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByEasterNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byeaster: []int{-1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 11, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 11, 0, 1, 0, 0, time.UTC), time.Date(1998, 4, 11, 0, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByHour(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byhour: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 18, 1, 0, 0, time.UTC), time.Date(1997, 9, 2, 18, 2, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 0, 0, time.UTC), time.Date(1997, 9, 2, 10, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyBySecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 18, 0, time.UTC), time.Date(1997, 9, 2, 9, 1, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByHourAndMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 0, 0, time.UTC), time.Date(1997, 9, 3, 6, 6, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByHourAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byhour: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 0, 18, 0, time.UTC), time.Date(1997, 9, 2, 18, 1, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyByHourAndMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestMinutelyBySetPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: MINUTELY, Count: 3, Bysecond: []int{15, 30, 45}, Bysetpos: []int{3, -3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 15, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 45, 0, time.UTC), time.Date(1997, 9, 2, 9, 1, 15, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondly(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 1, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyInterval(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Interval: 2, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 2, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 4, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyIntervalLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Interval: 90061, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 10, 1, 1, 0, time.UTC), time.Date(1997, 9, 4, 11, 2, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMonth(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Bymonth: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 0, 1, 0, time.UTC), time.Date(1998, 1, 1, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Bymonthday: []int{1, 3}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 3, 0, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 0, 0, 1, 0, time.UTC), time.Date(1997, 9, 3, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMonthAndMonthDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{5, 7}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 5, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 5, 0, 0, 1, 0, time.UTC), time.Date(1998, 1, 5, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 1, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 1, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMonthAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 0, 1, 0, time.UTC), time.Date(1998, 1, 1, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMonthAndNWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Bymonth: []int{1, 3}, Byweekday: []Weekday{TU.Nth(1), TH.Nth(-1)}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 0, 1, 0, time.UTC), time.Date(1998, 1, 1, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 0, 1, 0, time.UTC), time.Date(1998, 1, 1, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMonthAndMonthDayAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Bymonth: []int{1, 3}, Bymonthday: []int{1, 3}, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(1998, 1, 1, 0, 0, 1, 0, time.UTC), time.Date(1998, 1, 1, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 4, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 31, 0, 0, 1, 0, time.UTC), time.Date(1997, 12, 31, 0, 0, 2, 0, time.UTC), time.Date(1997, 12, 31, 0, 0, 3, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 4, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 31, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 31, 0, 0, 1, 0, time.UTC), time.Date(1997, 12, 31, 0, 0, 2, 0, time.UTC), time.Date(1997, 12, 31, 0, 0, 3, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMonthAndYearDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 4, Bymonth: []int{4, 7}, Byyearday: []int{1, 100, 200, 365}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 10, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 0, 0, 1, 0, time.UTC), time.Date(1998, 4, 10, 0, 0, 2, 0, time.UTC), time.Date(1998, 4, 10, 0, 0, 3, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMonthAndYearDayNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 4, Bymonth: []int{4, 7}, Byyearday: []int{-365, -266, -166, -1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 10, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 10, 0, 0, 1, 0, time.UTC), time.Date(1998, 4, 10, 0, 0, 2, 0, time.UTC), time.Date(1998, 4, 10, 0, 0, 3, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByWeekNo(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byweekno: []int{20}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 5, 11, 0, 0, 0, 0, time.UTC), time.Date(1998, 5, 11, 0, 0, 1, 0, time.UTC), time.Date(1998, 5, 11, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByWeekNoAndWeekDay(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byweekno: []int{1}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 29, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 29, 0, 0, 1, 0, time.UTC), time.Date(1997, 12, 29, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByWeekNoAndWeekDayLarge(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byweekno: []int{52}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 28, 0, 0, 1, 0, time.UTC), time.Date(1997, 12, 28, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByWeekNoAndWeekDayLast(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byweekno: []int{-1}, Byweekday: []Weekday{SU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 12, 28, 0, 0, 0, 0, time.UTC), time.Date(1997, 12, 28, 0, 0, 1, 0, time.UTC), time.Date(1997, 12, 28, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByWeekNoAndWeekDay53(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byweekno: []int{53}, Byweekday: []Weekday{MO}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 12, 28, 0, 0, 0, 0, time.UTC), time.Date(1998, 12, 28, 0, 0, 1, 0, time.UTC), time.Date(1998, 12, 28, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByEaster(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byeaster: []int{0}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 12, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 12, 0, 0, 1, 0, time.UTC), time.Date(1998, 4, 12, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByEasterPos(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byeaster: []int{1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 13, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 13, 0, 0, 1, 0, time.UTC), time.Date(1998, 4, 13, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByEasterNeg(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byeaster: []int{-1}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1998, 4, 11, 0, 0, 0, 0, time.UTC), time.Date(1998, 4, 11, 0, 0, 1, 0, time.UTC), time.Date(1998, 4, 11, 0, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByHour(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byhour: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 0, 0, time.UTC), time.Date(1997, 9, 2, 18, 0, 1, 0, time.UTC), time.Date(1997, 9, 2, 18, 0, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 9, 6, 1, 0, time.UTC), time.Date(1997, 9, 2, 9, 6, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyBySecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 0, 18, 0, time.UTC), time.Date(1997, 9, 2, 9, 1, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByHourAndMinute(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 0, 0, time.UTC), time.Date(1997, 9, 2, 18, 6, 1, 0, time.UTC), time.Date(1997, 9, 2, 18, 6, 2, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByHourAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byhour: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 0, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 0, 18, 0, time.UTC), time.Date(1997, 9, 2, 18, 1, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 9, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 9, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByHourAndMinuteAndSecond(t *testing.T) { r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Byhour: []int{6, 18}, Byminute: []int{6, 18}, Bysecond: []int{6, 18}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 18, 6, 6, 0, time.UTC), time.Date(1997, 9, 2, 18, 6, 18, 0, time.UTC), time.Date(1997, 9, 2, 18, 18, 6, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSecondlyByHourAndMinuteAndSecondBug(t *testing.T) { // This explores a bug found by Mathieu Bridon. r, _ := NewRRule(ROption{Freq: SECONDLY, Count: 3, Bysecond: []int{0}, Byminute: []int{1}, Dtstart: time.Date(2010, 3, 22, 12, 1, 0, 0, time.UTC)}) want := []time.Time{time.Date(2010, 3, 22, 12, 1, 0, 0, time.UTC), time.Date(2010, 3, 22, 13, 1, 0, 0, time.UTC), time.Date(2010, 3, 22, 14, 1, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestUntilNotMatching(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), Until: time.Date(1997, 9, 5, 8, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestUntilMatching(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), Until: time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestUntilSingle(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), Until: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestUntilEmpty(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), Until: time.Date(1997, 9, 1, 9, 0, 0, 0, time.UTC)}) want := []time.Time{} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestUntilWithDate(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), Until: time.Date(1997, 9, 5, 0, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWkStIntervalMO(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Interval: 2, Byweekday: []Weekday{TU, SU}, Wkst: MO, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 7, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 16, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestWkStIntervalSU(t *testing.T) { r, _ := NewRRule(ROption{Freq: WEEKLY, Count: 3, Interval: 2, Byweekday: []Weekday{TU, SU}, Wkst: SU, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 14, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 16, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDTStartIsDate(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Dtstart: time.Date(1997, 9, 2, 0, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 0, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 0, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 0, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestDTStartWithMicroseconds(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, Count: 3, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 500000000, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestUntil(t *testing.T) { r1, _ := NewRRule(ROption{Freq: DAILY, Dtstart: time.Date(1997, 9, 2, 0, 0, 0, 0, time.UTC)}) r1.Until(time.Date(1998, 9, 2, 0, 0, 0, 0, time.UTC)) r2, _ := NewRRule(ROption{Freq: DAILY, Dtstart: time.Date(1997, 9, 2, 0, 0, 0, 0, time.UTC), Until: time.Date(1998, 9, 2, 0, 0, 0, 0, time.UTC)}) v1 := r1.All() v2 := r2.All() if !timesEqual(v1, v2) { t.Errorf("get %v, want %v", v1, v2) } r3, _ := NewRRule(ROption{Freq: MONTHLY, Dtstart: time.Date(MAXYEAR-100, 1, 1, 0, 0, 0, 0, time.UTC)}) r3.Until(time.Date(MAXYEAR+100, 1, 1, 0, 0, 0, 0, time.UTC)) v3 := r3.All() if len(v3) != 101*12 { t.Errorf("get %v, want %v", len(v3), 101*12) } } func TestMaxYear(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Count: 3, Bymonth: []int{2}, Bymonthday: []int{31}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{} value := r.All() if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestBefore(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, // Count:5, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC) value := r.Before(time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC), false) if value != want { t.Errorf("get %v, want %v", value, want) } } func TestBeforeInc(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, // Count:5, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC) value := r.Before(time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC), true) if value != want { t.Errorf("get %v, want %v", value, want) } } func TestAfter(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, // Count:5, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC) value := r.After(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), false) if value != want { t.Errorf("get %v, want %v", value, want) } } func TestAfterInc(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, // Count:5, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC) value := r.After(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), true) if value != want { t.Errorf("get %v, want %v", value, want) } } func TestBetween(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, // Count:5, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC)} value := r.Between(time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 6, 9, 0, 0, 0, time.UTC), false) if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestBetweenInc(t *testing.T) { r, _ := NewRRule(ROption{Freq: DAILY, // Count:5, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 6, 9, 0, 0, 0, time.UTC)} value := r.Between(time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 6, 9, 0, 0, 0, time.UTC), true) if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestAllWithDefaultUtil(t *testing.T) { r, _ := NewRRule(ROption{Freq: YEARLY, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) value := r.All() if len(value) > 300 || len(value) < 200 { t.Errorf("No default Util time") } r, _ = NewRRule(ROption{Freq: YEARLY}) if len(r.All()) != len(value) { t.Errorf("No default Util time") } } func TestWeekdayGetters(t *testing.T) { wd := Weekday{n: 2, weekday: 0} if wd.N() != 2 { t.Errorf("Ord week getter is wrong") } if wd.Day() != 0 { t.Errorf("Day index getter is wrong") } } func TestRuleChangeDTStartTimezoneRespected(t *testing.T) { /* https://golang.org/pkg/time/#LoadLocation "The time zone database needed by LoadLocation may not be present on all systems, especially non-Unix systems. LoadLocation looks in the directory or uncompressed zip file named by the ZONEINFO environment variable, if any, then looks in known installation locations on Unix systems, and finally looks in $GOROOT/lib/time/zoneinfo.zip." */ loc, err := time.LoadLocation("CET") if err != nil { t.Fatal("expected", nil, "got", err) } rule, err := NewRRule( ROption{ Freq: DAILY, Count: 10, Wkst: MO, Dtstart: time.Date(2019, 3, 6, 1, 1, 1, 0, loc), }, ) if err != nil { t.Fatal("expected", nil, "got", err) } rule.DTStart(time.Date(2019, 3, 6, 0, 0, 0, 0, time.UTC)) events := rule.All() if len(events) != 10 { t.Fatal("expected", 10, "got", len(events)) } for _, e := range events { if e.Location().String() != "UTC" { t.Fatal("expected", "UTC", "got", e.Location().String()) } h, m, s := e.Clock() if (h + m + s) != 0 { t.Fatal("expected", "0", "got", h, m, s) } } } rrule-go-1.6.0/rruleset.go000066400000000000000000000127361370273745400154730ustar00rootroot00000000000000package rrule import ( "fmt" "sort" "time" ) // Set allows more complex recurrence setups, mixing multiple rules, dates, exclusion rules, and exclusion dates type Set struct { dtstart time.Time rrule *RRule rdate []time.Time exdate []time.Time } // Recurrence returns a slice of all the recurrence rules for a set func (set *Set) Recurrence() []string { var res []string if !set.dtstart.IsZero() { // No colon, DTSTART may have TZID, which would require a semicolon after DTSTART res = append(res, fmt.Sprintf("DTSTART%s", timeToRFCDatetimeStr(set.dtstart))) } if set.rrule != nil { res = append(res, fmt.Sprintf("RRULE:%s", set.rrule.OrigOptions.RRuleString())) } for _, item := range set.rdate { res = append(res, fmt.Sprintf("RDATE:%s", timeToStr(item))) } for _, item := range set.exdate { res = append(res, fmt.Sprintf("EXDATE:%s", timeToStr(item))) } return res } // DTStart sets dtstart property for set func (set *Set) DTStart(dtstart time.Time) { set.dtstart = dtstart.Truncate(time.Second) if set.rrule != nil { set.rrule.DTStart(set.dtstart) } } // GetDTStart gets dtstart for set func (set *Set) GetDTStart() time.Time { return set.dtstart } // RRule set the RRULE for set. // There is the only one RRULE in the set as https://tools.ietf.org/html/rfc5545#appendix-A.1 func (set *Set) RRule(rrule *RRule) { if !rrule.OrigOptions.Dtstart.IsZero() { set.dtstart = rrule.dtstart } else if !set.dtstart.IsZero() { rrule.DTStart(set.dtstart) } set.rrule = rrule } // GetRRule returns the rrules in the set func (set *Set) GetRRule() *RRule { return set.rrule } // RDate include the given datetime instance in the recurrence set generation. func (set *Set) RDate(rdate time.Time) { set.rdate = append(set.rdate, rdate.Truncate(time.Second)) } // SetRDates sets explicitly added dates (rdates) in the set func (set *Set) SetRDates(rdates []time.Time) { set.rdate = make([]time.Time, 0, len(rdates)) for _, rdate := range rdates { set.rdate = append(set.rdate, rdate.Truncate(time.Second)) } } // GetRDate returns explicitly added dates (rdates) in the set func (set *Set) GetRDate() []time.Time { return set.rdate } // ExDate include the given datetime instance in the recurrence set exclusion list. // Dates included that way will not be generated, // even if some inclusive rrule or rdate matches them. func (set *Set) ExDate(exdate time.Time) { set.exdate = append(set.exdate, exdate.Truncate(time.Second)) } // SetExDates sets explicitly excluded dates (exdates) in the set func (set *Set) SetExDates(exdates []time.Time) { set.exdate = make([]time.Time, 0, len(exdates)) for _, exdate := range exdates { set.exdate = append(set.exdate, exdate.Truncate(time.Second)) } } // GetExDate returns explicitly excluded dates (exdates) in the set func (set *Set) GetExDate() []time.Time { return set.exdate } type genItem struct { dt time.Time gen Next } type genItemSlice []genItem func (s genItemSlice) Len() int { return len(s) } func (s genItemSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s genItemSlice) Less(i, j int) bool { return s[i].dt.Before(s[j].dt) } func addGenList(genList *[]genItem, next Next) { dt, ok := next() if ok { *genList = append(*genList, genItem{dt, next}) } } // Iterator returns an iterator for rrule.Set func (set *Set) Iterator() (next func() (time.Time, bool)) { rlist := []genItem{} exlist := []genItem{} sort.Sort(timeSlice(set.rdate)) addGenList(&rlist, timeSliceIterator(set.rdate)) if set.rrule != nil { addGenList(&rlist, set.rrule.Iterator()) } sort.Sort(genItemSlice(rlist)) sort.Sort(timeSlice(set.exdate)) addGenList(&exlist, timeSliceIterator(set.exdate)) sort.Sort(genItemSlice(exlist)) lastdt := time.Time{} return func() (time.Time, bool) { for len(rlist) != 0 { dt := rlist[0].dt var ok bool rlist[0].dt, ok = rlist[0].gen() if !ok { rlist = rlist[1:] } sort.Sort(genItemSlice(rlist)) if lastdt.IsZero() || !lastdt.Equal(dt) { for len(exlist) != 0 && exlist[0].dt.Before(dt) { exlist[0].dt, ok = exlist[0].gen() if !ok { exlist = exlist[1:] } sort.Sort(genItemSlice(exlist)) } lastdt = dt if len(exlist) == 0 || !dt.Equal(exlist[0].dt) { return dt, true } } } return time.Time{}, false } } // All returns all occurrences of the rrule.Set. func (set *Set) All() []time.Time { return all(set.Iterator()) } // Between returns all the occurrences of the rrule between after and before. // The inc keyword defines what happens if after and/or before are themselves occurrences. // With inc == True, they will be included in the list, if they are found in the recurrence set. func (set *Set) Between(after, before time.Time, inc bool) []time.Time { return between(set.Iterator(), after, before, inc) } // Before Returns the last recurrence before the given datetime instance, // or time.Time's zero value if no recurrence match. // The inc keyword defines what happens if dt is an occurrence. // With inc == True, if dt itself is an occurrence, it will be returned. func (set *Set) Before(dt time.Time, inc bool) time.Time { return before(set.Iterator(), dt, inc) } // After returns the first recurrence after the given datetime instance, // or time.Time's zero value if no recurrence match. // The inc keyword defines what happens if dt is an occurrence. // With inc == True, if dt itself is an occurrence, it will be returned. func (set *Set) After(dt time.Time, inc bool) time.Time { return after(set.Iterator(), dt, inc) } rrule-go-1.6.0/rruleset_test.go000066400000000000000000000273611370273745400165320ustar00rootroot00000000000000package rrule import ( "testing" "time" ) func TestSet(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: YEARLY, Count: 2, Byweekday: []Weekday{TU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) value := set.All() want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)} if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSetOverlapping(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: YEARLY, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) v1 := set.All() if len(v1) > 300 || len(v1) < 200 { t.Errorf("No default Util time") } } func TestSetString(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: YEARLY, Count: 1, Byweekday: []Weekday{TU}, Dtstart: time.Date(1997, 9, 2, 8, 0, 0, 0, time.UTC)}) set.RRule(r) set.ExDate(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)) set.ExDate(time.Date(1997, 9, 11, 9, 0, 0, 0, time.UTC)) set.ExDate(time.Date(1997, 9, 18, 9, 0, 0, 0, time.UTC)) set.RDate(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)) set.RDate(time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)) want := `DTSTART:19970902T080000Z RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TU RDATE:19970904T090000Z RDATE:19970909T090000Z EXDATE:19970904T090000Z EXDATE:19970911T090000Z EXDATE:19970918T090000Z` value := set.String() if want != value { t.Errorf("get %v, want %v", value, want) } } func TestSetDTStart(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: YEARLY, Count: 1, Byweekday: []Weekday{TU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) set.ExDate(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)) set.ExDate(time.Date(1997, 9, 11, 9, 0, 0, 0, time.UTC)) set.ExDate(time.Date(1997, 9, 18, 9, 0, 0, 0, time.UTC)) set.RDate(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)) set.RDate(time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)) nyLoc, _ := time.LoadLocation("America/New_York") set.DTStart(time.Date(1997, 9, 3, 9, 0, 0, 0, nyLoc)) want := `DTSTART;TZID=America/New_York:19970903T090000 RRULE:FREQ=YEARLY;COUNT=1;BYDAY=TU RDATE:19970904T090000Z RDATE:19970909T090000Z EXDATE:19970904T090000Z EXDATE:19970911T090000Z EXDATE:19970918T090000Z` value := set.String() if want != value { t.Errorf("get \n%v\n want \n%v\n", value, want) } sset, err := StrToRRuleSet(set.String()) if err != nil { t.Errorf("Could not create RSET from set output") } if sset.String() != set.String() { t.Errorf("RSET created from set output different than original set, %s", sset.String()) } } func TestSetRecurrence(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: YEARLY, Count: 1, Byweekday: []Weekday{TU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) value := set.Recurrence() if len(value) != 2 { t.Errorf("Wrong length for recurrence got=%v want=%v", len(value), 2) } want := "DTSTART:19970902T090000Z\nRRULE:FREQ=YEARLY;COUNT=1;BYDAY=TU" if set.String() != want { t.Errorf("get %s, want %v", set.String(), want) } } func TestSetDate(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: YEARLY, Count: 1, Byweekday: []Weekday{TU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) set.RDate(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)) set.RDate(time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)) value := set.All() want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)} if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSetRDates(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: YEARLY, Count: 1, Byweekday: []Weekday{TU}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) set.SetRDates([]time.Time{ time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC), }) value := set.All() want := []time.Time{ time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC), } if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSetExDate(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: YEARLY, Count: 6, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) set.ExDate(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)) set.ExDate(time.Date(1997, 9, 11, 9, 0, 0, 0, time.UTC)) set.ExDate(time.Date(1997, 9, 18, 9, 0, 0, 0, time.UTC)) value := set.All() want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 16, 9, 0, 0, 0, time.UTC)} if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSetExDates(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: YEARLY, Count: 6, Byweekday: []Weekday{TU, TH}, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) set.SetExDates([]time.Time{ time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 11, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 18, 9, 0, 0, 0, time.UTC), }) value := set.All() want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 16, 9, 0, 0, 0, time.UTC)} if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSetExDateRevOrder(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: MONTHLY, Count: 5, Bymonthday: []int{10}, Dtstart: time.Date(2004, 1, 1, 9, 0, 0, 0, time.UTC)}) set.RRule(r) set.ExDate(time.Date(2004, 4, 10, 9, 0, 0, 0, time.UTC)) set.ExDate(time.Date(2004, 2, 10, 9, 0, 0, 0, time.UTC)) value := set.All() want := []time.Time{time.Date(2004, 1, 10, 9, 0, 0, 0, time.UTC), time.Date(2004, 3, 10, 9, 0, 0, 0, time.UTC), time.Date(2004, 5, 10, 9, 0, 0, 0, time.UTC)} if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSetDateAndExDate(t *testing.T) { set := Set{} set.RDate(time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)) set.RDate(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)) set.RDate(time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC)) set.RDate(time.Date(1997, 9, 11, 9, 0, 0, 0, time.UTC)) set.RDate(time.Date(1997, 9, 16, 9, 0, 0, 0, time.UTC)) set.RDate(time.Date(1997, 9, 18, 9, 0, 0, 0, time.UTC)) set.ExDate(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC)) set.ExDate(time.Date(1997, 9, 11, 9, 0, 0, 0, time.UTC)) set.ExDate(time.Date(1997, 9, 18, 9, 0, 0, 0, time.UTC)) value := set.All() want := []time.Time{time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 9, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 16, 9, 0, 0, 0, time.UTC)} if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSetBefore(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: DAILY, Count: 7, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) want := time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC) value := set.Before(time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC), false) if value != want { t.Errorf("get %v, want %v", value, want) } } func TestSetBeforeInc(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: DAILY, Count: 7, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) want := time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC) value := set.Before(time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC), true) if value != want { t.Errorf("get %v, want %v", value, want) } } func TestSetAfter(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: DAILY, Count: 7, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) want := time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC) value := set.After(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), false) if value != want { t.Errorf("get %v, want %v", value, want) } } func TestSetAfterInc(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: DAILY, Count: 7, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) want := time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC) value := set.After(time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), true) if value != want { t.Errorf("get %v, want %v", value, want) } } func TestSetBetween(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: DAILY, Count: 7, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) value := set.Between(time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 6, 9, 0, 0, 0, time.UTC), false) want := []time.Time{time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC)} if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSetBetweenInc(t *testing.T) { set := Set{} r, _ := NewRRule(ROption{Freq: DAILY, Count: 7, Dtstart: time.Date(1997, 9, 2, 9, 0, 0, 0, time.UTC)}) set.RRule(r) value := set.Between(time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 6, 9, 0, 0, 0, time.UTC), true) want := []time.Time{time.Date(1997, 9, 3, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 4, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 5, 9, 0, 0, 0, time.UTC), time.Date(1997, 9, 6, 9, 0, 0, 0, time.UTC)} if !timesEqual(value, want) { t.Errorf("get %v, want %v", value, want) } } func TestSetTrickyTimeZones(t *testing.T) { set := Set{} moscow, _ := time.LoadLocation("Europe/Moscow") newYork, _ := time.LoadLocation("America/New_York") tehran, _ := time.LoadLocation("Asia/Tehran") r, _ := NewRRule(ROption{ Freq: DAILY, Count: 4, Dtstart: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).In(moscow), }) set.RRule(r) set.ExDate(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).In(newYork)) set.ExDate(time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC).In(tehran)) set.ExDate(time.Date(2000, 1, 3, 0, 0, 0, 0, time.UTC).In(moscow)) set.ExDate(time.Date(2000, 1, 4, 0, 0, 0, 0, time.UTC)) occurrences := set.All() if len(occurrences) > 0 { t.Errorf("No all occurrences excluded by ExDate: [%+v]", occurrences) } } func TestSetDtStart(t *testing.T) { ogr := []string{"DTSTART;TZID=America/Los_Angeles:20181115T000000", "RRULE:FREQ=DAILY;INTERVAL=1;WKST=SU;UNTIL=20181117T235959"} set, _ := StrSliceToRRuleSet(ogr) ogoc := set.All() set.DTStart(set.GetDTStart().AddDate(0, 0, 1)) noc := set.All() if len(noc) != len(ogoc)-1 { t.Fatalf("As per the new DTStart the new occurences should exactly be one less that the original, new :%d original: %d", len(noc), len(ogoc)) } for i := range noc { if noc[i] != ogoc[i+1] { t.Errorf("New occurences should just offset by one, mismatch at %d, expected: %+v, actual: %+v", i, ogoc[i+1], noc[i]) } } } func TestRuleSetChangeDTStartTimezoneRespected(t *testing.T) { /* https://golang.org/pkg/time/#LoadLocation "The time zone database needed by LoadLocation may not be present on all systems, especially non-Unix systems. LoadLocation looks in the directory or uncompressed zip file named by the ZONEINFO environment variable, if any, then looks in known installation locations on Unix systems, and finally looks in $GOROOT/lib/time/zoneinfo.zip." */ loc, err := time.LoadLocation("CET") if err != nil { t.Fatal("expected", nil, "got", err) } ruleSet := &Set{} rule, err := NewRRule( ROption{ Freq: DAILY, Count: 10, Wkst: MO, Byhour: []int{10}, Byminute: []int{0}, Bysecond: []int{0}, Dtstart: time.Date(2019, 3, 6, 0, 0, 0, 0, loc), }, ) if err != nil { t.Fatal("expected", nil, "got", err) } ruleSet.RRule(rule) ruleSet.DTStart(time.Date(2019, 3, 6, 0, 0, 0, 0, time.UTC)) events := ruleSet.All() if len(events) != 10 { t.Fatal("expected", 10, "got", len(events)) } for _, e := range events { if e.Location().String() != "UTC" { t.Fatal("expected", "UTC", "got", e.Location().String()) } } } rrule-go-1.6.0/str.go000066400000000000000000000315071370273745400144330ustar00rootroot00000000000000package rrule import ( "errors" "fmt" "strconv" "strings" "time" ) const ( // DateTimeFormat is date-time format used in iCalendar (RFC 5545) DateTimeFormat = "20060102T150405Z" // LocalDateTimeFormat is a date-time format without Z prefix LocalDateTimeFormat = "20060102T150405" // DateFormat is date format used in iCalendar (RFC 5545) DateFormat = "20060102" ) func timeToStr(time time.Time) string { return time.UTC().Format(DateTimeFormat) } func strToTimeInLoc(str string, loc *time.Location) (time.Time, error) { if len(str) == len(DateFormat) { return time.ParseInLocation(DateFormat, str, loc) } if len(str) == len(LocalDateTimeFormat) { return time.ParseInLocation(LocalDateTimeFormat, str, loc) } // date-time format carries zone info return time.Parse(DateTimeFormat, str) } func (f Frequency) String() string { return [...]string{ "YEARLY", "MONTHLY", "WEEKLY", "DAILY", "HOURLY", "MINUTELY", "SECONDLY"}[f] } func strToFreq(str string) (Frequency, error) { freqMap := map[string]Frequency{ "YEARLY": YEARLY, "MONTHLY": MONTHLY, "WEEKLY": WEEKLY, "DAILY": DAILY, "HOURLY": HOURLY, "MINUTELY": MINUTELY, "SECONDLY": SECONDLY, } result, ok := freqMap[str] if !ok { return 0, errors.New("undefined frequency: " + str) } return result, nil } func (wday Weekday) String() string { s := [...]string{"MO", "TU", "WE", "TH", "FR", "SA", "SU"}[wday.weekday] if wday.n == 0 { return s } return fmt.Sprintf("%+d%s", wday.n, s) } func strToWeekday(str string) (Weekday, error) { if len(str) < 2 { return Weekday{}, errors.New("undefined weekday: " + str) } weekMap := map[string]Weekday{ "MO": MO, "TU": TU, "WE": WE, "TH": TH, "FR": FR, "SA": SA, "SU": SU} result, ok := weekMap[str[len(str)-2:]] if !ok { return Weekday{}, errors.New("undefined weekday: " + str) } if len(str) > 2 { n, e := strconv.Atoi(str[:len(str)-2]) if e != nil { return Weekday{}, e } result.n = n } return result, nil } func strToWeekdays(value string) ([]Weekday, error) { contents := strings.Split(value, ",") result := make([]Weekday, len(contents)) var e error for i, s := range contents { result[i], e = strToWeekday(s) if e != nil { return nil, e } } return result, nil } func appendIntsOption(options []string, key string, value []int) []string { if len(value) == 0 { return options } valueStr := make([]string, len(value)) for i, v := range value { valueStr[i] = strconv.Itoa(v) } return append(options, fmt.Sprintf("%s=%s", key, strings.Join(valueStr, ","))) } func strToInts(value string) ([]int, error) { contents := strings.Split(value, ",") result := make([]int, len(contents)) var e error for i, s := range contents { result[i], e = strconv.Atoi(s) if e != nil { return nil, e } } return result, nil } // String returns RRULE string with DTSTART if exists. e.g. // DTSTART;TZID=America/New_York:19970105T083000 // RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30 func (option *ROption) String() string { str := option.RRuleString() if option.Dtstart.IsZero() { return str } return fmt.Sprintf("DTSTART%s\n%s", timeToRFCDatetimeStr(option.Dtstart), str) } // RRuleString returns RRULE string exclude DTSTART func (option *ROption) RRuleString() string { result := []string{fmt.Sprintf("FREQ=%v", option.Freq)} if option.Interval != 0 { result = append(result, fmt.Sprintf("INTERVAL=%v", option.Interval)) } if option.Wkst != MO { result = append(result, fmt.Sprintf("WKST=%v", option.Wkst)) } if option.Count != 0 { result = append(result, fmt.Sprintf("COUNT=%v", option.Count)) } if !option.Until.IsZero() { result = append(result, fmt.Sprintf("UNTIL=%v", timeToStr(option.Until))) } result = appendIntsOption(result, "BYSETPOS", option.Bysetpos) result = appendIntsOption(result, "BYMONTH", option.Bymonth) result = appendIntsOption(result, "BYMONTHDAY", option.Bymonthday) result = appendIntsOption(result, "BYYEARDAY", option.Byyearday) result = appendIntsOption(result, "BYWEEKNO", option.Byweekno) if len(option.Byweekday) != 0 { valueStr := make([]string, len(option.Byweekday)) for i, wday := range option.Byweekday { valueStr[i] = wday.String() } result = append(result, fmt.Sprintf("BYDAY=%s", strings.Join(valueStr, ","))) } result = appendIntsOption(result, "BYHOUR", option.Byhour) result = appendIntsOption(result, "BYMINUTE", option.Byminute) result = appendIntsOption(result, "BYSECOND", option.Bysecond) result = appendIntsOption(result, "BYEASTER", option.Byeaster) return strings.Join(result, ";") } // StrToROption converts string to ROption. func StrToROption(rfcString string) (*ROption, error) { return StrToROptionInLocation(rfcString, time.UTC) } // StrToROptionInLocation is same as StrToROption but in case local // time is supplied as date-time/date field (ex. UNTIL), it is parsed // as a time in a given location (time zone) func StrToROptionInLocation(rfcString string, loc *time.Location) (*ROption, error) { rfcString = strings.TrimSpace(rfcString) strs := strings.Split(rfcString, "\n") var rruleStr, dtstartStr string switch len(strs) { case 1: rruleStr = strs[0] case 2: dtstartStr = strs[0] rruleStr = strs[1] default: return nil, errors.New("invalid RRULE string") } result := ROption{} freqSet := false if dtstartStr != "" { firstName, err := processRRuleName(dtstartStr) if err != nil { return nil, fmt.Errorf("expect DTSTART but: %s", err) } if firstName != "DTSTART" { return nil, fmt.Errorf("expect DTSTART but: %s", firstName) } result.Dtstart, err = strToDtStart(dtstartStr[len(firstName)+1:], time.UTC) if err != nil { return nil, fmt.Errorf("strToDtStart failed: %s", err) } } for _, attr := range strings.Split(rruleStr, ";") { keyValue := strings.Split(attr, "=") if len(keyValue) != 2 { return nil, errors.New("wrong format") } key, value := keyValue[0], keyValue[1] if len(value) == 0 { return nil, errors.New(key + " option has no value") } var e error switch key { case "FREQ": result.Freq, e = strToFreq(value) freqSet = true case "DTSTART": result.Dtstart, e = strToTimeInLoc(value, loc) case "INTERVAL": result.Interval, e = strconv.Atoi(value) case "WKST": result.Wkst, e = strToWeekday(value) case "COUNT": result.Count, e = strconv.Atoi(value) case "UNTIL": result.Until, e = strToTimeInLoc(value, loc) case "BYSETPOS": result.Bysetpos, e = strToInts(value) case "BYMONTH": result.Bymonth, e = strToInts(value) case "BYMONTHDAY": result.Bymonthday, e = strToInts(value) case "BYYEARDAY": result.Byyearday, e = strToInts(value) case "BYWEEKNO": result.Byweekno, e = strToInts(value) case "BYDAY": result.Byweekday, e = strToWeekdays(value) case "BYHOUR": result.Byhour, e = strToInts(value) case "BYMINUTE": result.Byminute, e = strToInts(value) case "BYSECOND": result.Bysecond, e = strToInts(value) case "BYEASTER": result.Byeaster, e = strToInts(value) default: return nil, errors.New("unknown RRULE property: " + key) } if e != nil { return nil, e } } if !freqSet { // Per RFC 5545, FREQ is mandatory and supposed to be the first // parameter. We'll just confirm it exists because we do not // have a meaningful default nor a way to confirm if we parsed // a value from the options this returns. return nil, errors.New("RRULE property FREQ is required") } return &result, nil } func (r *RRule) String() string { return r.OrigOptions.String() } func (set *Set) String() string { res := set.Recurrence() return strings.Join(res, "\n") } // StrToRRule converts string to RRule func StrToRRule(rfcString string) (*RRule, error) { option, e := StrToROption(rfcString) if e != nil { return nil, e } return NewRRule(*option) } // StrToRRuleSet converts string to RRuleSet func StrToRRuleSet(s string) (*Set, error) { s = strings.TrimSpace(s) if s == "" { return nil, errors.New("empty string") } ss := strings.Split(s, "\n") return StrSliceToRRuleSet(ss) } // StrSliceToRRuleSet converts given str slice to RRuleSet // In case there is a time met in any rule without specified time zone, when // it is parsed in UTC (see StrSliceToRRuleSetInLoc) func StrSliceToRRuleSet(ss []string) (*Set, error) { return StrSliceToRRuleSetInLoc(ss, time.UTC) } // StrSliceToRRuleSetInLoc is same as StrSliceToRRuleSet, but by default parses local times // in specified default location func StrSliceToRRuleSetInLoc(ss []string, defaultLoc *time.Location) (*Set, error) { if len(ss) == 0 { return &Set{}, nil } set := Set{} // According to RFC DTSTART is always the first line. firstName, err := processRRuleName(ss[0]) if err != nil { return nil, err } if firstName == "DTSTART" { dt, err := strToDtStart(ss[0][len(firstName)+1:], defaultLoc) if err != nil { return nil, fmt.Errorf("strToDtStart failed: %v", err) } // default location should be taken from DTSTART property to correctly // parse local times met in RDATE,EXDATE and other rules defaultLoc = dt.Location() set.DTStart(dt) // We've processed the first one ss = ss[1:] } for _, line := range ss { name, err := processRRuleName(line) if err != nil { return nil, err } rule := line[len(name)+1:] switch name { case "RRULE": rOpt, err := StrToROption(rule) if err != nil { return nil, fmt.Errorf("StrToROption failed: %v", err) } r, err := NewRRule(*rOpt) if err != nil { return nil, fmt.Errorf("NewRRule failed: %v", r) } set.RRule(r) case "RDATE", "EXDATE": ts, err := StrToDatesInLoc(rule, defaultLoc) if err != nil { return nil, fmt.Errorf("strToDates failed: %v", err) } for _, t := range ts { if name == "RDATE" { set.RDate(t) } else { set.ExDate(t) } } } } return &set, nil } // https://tools.ietf.org/html/rfc5545#section-3.3.5 // DTSTART:19970714T133000 ; Local time // DTSTART:19970714T173000Z ; UTC time // DTSTART;TZID=America/New_York:19970714T133000 ; Local time and time zone reference func timeToRFCDatetimeStr(time time.Time) string { if time.Location().String() != "UTC" { return fmt.Sprintf(";TZID=%s:%s", time.Location().String(), time.Format(LocalDateTimeFormat)) } return fmt.Sprintf(":%s", time.Format(DateTimeFormat)) } // StrToDates is intended to parse RDATE and EXDATE properties supporting only // VALUE=DATE-TIME (DATE and PERIOD are not supported). // Accepts string with format: "VALUE=DATE-TIME;[TZID=...]:{time},{time},...,{time}" // or simply "{time},{time},...{time}" and parses it to array of dates // In case no time zone specified in str, when all dates are parsed in UTC func StrToDates(str string) (ts []time.Time, err error) { return StrToDatesInLoc(str, time.UTC) } // StrToDatesInLoc same as StrToDates but it consideres default location to parse dates in // in case no location specified with TZID parameter func StrToDatesInLoc(str string, defaultLoc *time.Location) (ts []time.Time, err error) { tmp := strings.Split(str, ":") if len(tmp) > 2 { return nil, fmt.Errorf("bad format") } loc := defaultLoc if len(tmp) == 2 { params := strings.Split(tmp[0], ";") for _, param := range params { if strings.HasPrefix(param, "TZID=") { loc, err = parseTZID(param) } else if param != "VALUE=DATE-TIME" && param != "VALUE=DATE" { err = fmt.Errorf("unsupported: %v", param) } if err != nil { return nil, fmt.Errorf("bad dates param: %s", err.Error()) } } tmp = tmp[1:] } for _, datestr := range strings.Split(tmp[0], ",") { t, err := strToTimeInLoc(datestr, loc) if err != nil { return nil, fmt.Errorf("strToTime failed: %v", err) } ts = append(ts, t) } return } // processRRuleName processes the name of an RRule off a multi-line RRule set func processRRuleName(line string) (string, error) { line = strings.ToUpper(strings.TrimSpace(line)) if line == "" { return "", fmt.Errorf("bad format %v", line) } nameLen := strings.IndexAny(line, ";:") if nameLen <= 0 { return "", fmt.Errorf("bad format %v", line) } name := line[:nameLen] if strings.IndexAny(name, "=") > 0 { return "", fmt.Errorf("bad format %v", line) } return name, nil } // strToDtStart accepts string with format: "(TZID={timezone}:)?{time}" and parses it to a date // may be used to parse DTSTART rules, without the DTSTART; part. func strToDtStart(str string, defaultLoc *time.Location) (time.Time, error) { tmp := strings.Split(str, ":") if len(tmp) > 2 || len(tmp) == 0 { return time.Time{}, fmt.Errorf("bad format") } if len(tmp) == 2 { // tzid loc, err := parseTZID(tmp[0]) if err != nil { return time.Time{}, err } return strToTimeInLoc(tmp[1], loc) } // no tzid, len == 1 return strToTimeInLoc(tmp[0], defaultLoc) } func parseTZID(s string) (*time.Location, error) { if !strings.HasPrefix(s, "TZID=") || len(s) == len("TZID=") { return nil, fmt.Errorf("bad TZID parameter format") } return time.LoadLocation(s[len("TZID="):]) } rrule-go-1.6.0/str_test.go000066400000000000000000000267161370273745400155000ustar00rootroot00000000000000package rrule import ( "fmt" "testing" "time" ) func TestRFCRuleToStr(t *testing.T) { nyLoc, _ := time.LoadLocation("America/New_York") dtStart := time.Date(2018, 1, 1, 9, 0, 0, 0, nyLoc) r, _ := NewRRule(ROption{Freq: MONTHLY, Dtstart: dtStart}) want := "DTSTART;TZID=America/New_York:20180101T090000\nFREQ=MONTHLY" if r.String() != want { t.Errorf("Expected RFC string %s, got %v", want, r.String()) } } func TestRFCSetToString(t *testing.T) { nyLoc, _ := time.LoadLocation("America/New_York") dtStart := time.Date(2018, 1, 1, 9, 0, 0, 0, nyLoc) r, _ := NewRRule(ROption{Freq: MONTHLY, Dtstart: dtStart}) want := "DTSTART;TZID=America/New_York:20180101T090000\nFREQ=MONTHLY" if r.String() != want { t.Errorf("Expected RFC string %s, got %v", want, r.String()) } expectedSetStr := "DTSTART;TZID=America/New_York:20180101T090000\nRRULE:FREQ=MONTHLY" set := Set{} set.RRule(r) set.DTStart(dtStart) if set.String() != expectedSetStr { t.Errorf("Expected RFC Set string %s, got %s", expectedSetStr, set.String()) } } func TestCompatibility(t *testing.T) { str := "FREQ=WEEKLY;DTSTART=20120201T093000Z;INTERVAL=5;WKST=TU;COUNT=2;UNTIL=20130130T230000Z;BYSETPOS=2;BYMONTH=3;BYYEARDAY=95;BYWEEKNO=1;BYDAY=MO,+2FR;BYHOUR=9;BYMINUTE=30;BYSECOND=0;BYEASTER=-1" r, _ := StrToRRule(str) want := "DTSTART:20120201T093000Z\nFREQ=WEEKLY;INTERVAL=5;WKST=TU;COUNT=2;UNTIL=20130130T230000Z;BYSETPOS=2;BYMONTH=3;BYYEARDAY=95;BYWEEKNO=1;BYDAY=MO,+2FR;BYHOUR=9;BYMINUTE=30;BYSECOND=0;BYEASTER=-1" if s := r.String(); s != want { t.Errorf("StrToRRule(%q).String() = %q, want %q", str, s, want) } r, _ = StrToRRule(want) if s := r.String(); s != want { t.Errorf("StrToRRule(%q).String() = %q, want %q", want, want, want) } } func TestInvalidString(t *testing.T) { cases := []string{ "", " ", "FREQ", "FREQ=HELLO", "BYMONTH=", "FREQ=WEEKLY;HELLO=WORLD", "FREQ=WEEKLY;BYMONTHDAY=I", "FREQ=WEEKLY;BYDAY=M", "FREQ=WEEKLY;BYDAY=MQ", "FREQ=WEEKLY;BYDAY=+MO", "BYDAY=MO", } for _, item := range cases { if _, e := StrToRRule(item); e == nil { t.Errorf("StrToRRule(%q) = nil, want error", item) } } } func TestSetStr(t *testing.T) { setStr := "RRULE:FREQ=DAILY;UNTIL=20180517T235959Z\n" + "EXDATE;VALUE=DATE-TIME:20180525T070000Z,20180530T130000Z\n" + "RDATE;VALUE=DATE-TIME:20180801T131313Z,20180902T141414Z\n" set, err := StrToRRuleSet(setStr) if err != nil { t.Fatalf("StrToRRuleSet(%s) returned error: %v", setStr, err) } rule := set.GetRRule() if rule == nil { t.Errorf("Unexpected rrule parsed") } if rule.String() != "FREQ=DAILY;UNTIL=20180517T235959Z" { t.Errorf("Unexpected rrule: %s", rule.String()) } // matching parsed EXDates exDates := set.GetExDate() if len(exDates) != 2 { t.Errorf("Unexpected number of exDates: %v != 2, %v", len(exDates), exDates) } if [2]string{timeToStr(exDates[0]), timeToStr(exDates[1])} != [2]string{"20180525T070000Z", "20180530T130000Z"} { t.Errorf("Unexpected exDates: %v", exDates) } // matching parsed RDates rDates := set.GetRDate() if len(rDates) != 2 { t.Errorf("Unexpected number of rDates: %v != 2, %v", len(rDates), rDates) } if [2]string{timeToStr(rDates[0]), timeToStr(rDates[1])} != [2]string{"20180801T131313Z", "20180902T141414Z"} { t.Errorf("Unexpected exDates: %v", exDates) } } func TestStrToDtStart(t *testing.T) { validCases := []string{ "19970714T133000", "19970714T173000Z", "TZID=America/New_York:19970714T133000", } invalidCases := []string{ "DTSTART;TZID=America/New_York:19970714T133000", "19970714T1330000", "DTSTART;TZID=:20180101T090000", "TZID=:20180101T090000", "TZID=notatimezone:20180101T090000", "DTSTART:19970714T133000", "DTSTART:19970714T133000Z", "DTSTART;:19970714T133000Z", "DTSTART;:1997:07:14T13:30:00Z", ";:19970714T133000Z", " ", "", } for _, item := range validCases { if _, e := strToDtStart(item, time.UTC); e != nil { t.Errorf("strToDtStart(%q) error = %s, want nil", item, e.Error()) } } for _, item := range invalidCases { if _, e := strToDtStart(item, time.UTC); e == nil { t.Errorf("strToDtStart(%q) err = nil, want not nil", item) } } } func TestStrToDates(t *testing.T) { validCases := []string{ "19970714T133000", "19970714T173000Z", "VALUE=DATE-TIME:19970714T133000,19980714T133000,19980714T133000", "VALUE=DATE-TIME;TZID=America/New_York:19970714T133000,19980714T133000,19980714T133000", "VALUE=DATE:19970714T133000,19980714T133000,19980714T133000", } invalidCases := []string{ "VALUE:DATE:TIME:19970714T133000,19980714T133000,19980714T133000", ";:19970714T133000Z", " ", "", "VALUE=DATE-TIME;TZID=:19970714T133000", "VALUE=PERIOD:19970714T133000Z/19980714T133000Z", } for _, item := range validCases { if _, e := StrToDates(item); e != nil { t.Errorf("StrToDates(%q) error = %s, want nil", item, e.Error()) } if _, e := StrToDatesInLoc(item, time.Local); e != nil { t.Errorf("StrToDates(%q) error = %s, want nil", item, e.Error()) } } for _, item := range invalidCases { if _, e := StrToDates(item); e == nil { t.Errorf("StrToDates(%q) err = nil, want not nil", item) } if _, e := StrToDatesInLoc(item, time.Local); e == nil { t.Errorf("StrToDates(%q) err = nil, want not nil", item) } } } func TestStrToDatesTimeIsCorrect(t *testing.T) { nyLoc, _ := time.LoadLocation("America/New_York") inputs := []string{ "VALUE=DATE-TIME:19970714T133000", "VALUE=DATE-TIME;TZID=America/New_York:19970714T133000", } exp := []time.Time{ time.Date(1997, 7, 14, 13, 30, 0, 0, time.UTC), time.Date(1997, 7, 14, 13, 30, 0, 0, nyLoc), } for i, s := range inputs { ts, err := StrToDates(s) if err != nil { t.Fatalf("StrToDates(%s): error = %s", s, err.Error()) } if len(ts) != 1 { t.Fatalf("StrToDates(%s): bad answer: %v", s, ts) } if !ts[0].Equal(exp[i]) { t.Fatalf("StrToDates(%s): bad answer: %v, expected: %v", s, ts[0], exp[i]) } } } func TestProcessRRuleName(t *testing.T) { validCases := []string{ "DTSTART;TZID=America/New_York:19970714T133000", "RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,TU", "EXDATE;VALUE=DATE-TIME:20180525T070000Z,20180530T130000Z", "RDATE;TZID=America/New_York;VALUE=DATE-TIME:20180801T131313Z,20180902T141414Z", } invalidCases := []string{ "TZID=America/New_York:19970714T133000", "19970714T1330000", ";:19970714T133000Z", "FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,TU", " ", } for _, item := range validCases { if _, e := processRRuleName(item); e != nil { t.Errorf("processRRuleName(%q) error = %s, want nil", item, e.Error()) } } for _, item := range invalidCases { if _, e := processRRuleName(item); e == nil { t.Errorf("processRRuleName(%q) err = nil, want not nil", item) } } } func TestSetStrCompatibility(t *testing.T) { badInputStrs := []string{ "", "FREQ=DAILY;UNTIL=20180517T235959Z", "DTSTART:;", "RRULE:;", } for _, badInputStr := range badInputStrs { _, err := StrToRRuleSet(badInputStr) if err == nil { t.Fatalf("StrToRRuleSet(%s) didn't return error", badInputStr) } } inputStr := "DTSTART;TZID=America/New_York:20180101T090000\n" + "RRULE:FREQ=DAILY;UNTIL=20180517T235959Z\n" + "RRULE:FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,TU\n" + "EXRULE:FREQ=MONTHLY;UNTIL=20180520;BYMONTHDAY=1,2,3\n" + "EXDATE;VALUE=DATE-TIME:20180525T070000Z,20180530T130000Z\n" + "RDATE;VALUE=DATE-TIME:20180801T131313Z,20180902T141414Z\n" set, err := StrToRRuleSet(inputStr) if err != nil { t.Fatalf("StrToRRuleSet(%s) returned error: %v", inputStr, err) } nyLoc, _ := time.LoadLocation("America/New_York") dtWantTime := time.Date(2018, 1, 1, 9, 0, 0, 0, nyLoc) rrule := set.GetRRule() if rrule.String() != "DTSTART;TZID=America/New_York:20180101T090000\nFREQ=WEEKLY;INTERVAL=2;BYDAY=MO,TU" { t.Errorf("Unexpected rrule: %s", rrule.String()) } if !dtWantTime.Equal(rrule.dtstart) { t.Fatalf("Expected RRule dtstart to be %v got %v", dtWantTime, rrule.dtstart) } if !dtWantTime.Equal(set.GetDTStart()) { t.Fatalf("Expected Set dtstart to be %v got %v", dtWantTime, set.GetDTStart()) } // matching parsed EXDates exDates := set.GetExDate() if len(exDates) != 2 { t.Errorf("Unexpected number of exDates: %v != 2, %v", len(exDates), exDates) } if [2]string{timeToStr(exDates[0]), timeToStr(exDates[1])} != [2]string{"20180525T070000Z", "20180530T130000Z"} { t.Errorf("Unexpected exDates: %v", exDates) } // matching parsed RDates rDates := set.GetRDate() if len(rDates) != 2 { t.Errorf("Unexpected number of rDates: %v != 2, %v", len(rDates), rDates) } if [2]string{timeToStr(rDates[0]), timeToStr(rDates[1])} != [2]string{"20180801T131313Z", "20180902T141414Z"} { t.Errorf("Unexpected exDates: %v", exDates) } dtWantAfter := time.Date(2018, 1, 2, 9, 0, 0, 0, nyLoc) dtAfter := set.After(dtWantTime, false) if !dtWantAfter.Equal(dtAfter) { t.Errorf("Next time wrong should be %s but is %s", dtWantAfter, dtAfter) } // String to set to string comparison setStr := set.String() setFromSetStr, err := StrToRRuleSet(setStr) if setStr != setFromSetStr.String() { t.Errorf("Expected string output\n %s \nbut got\n %s\n", setStr, setFromSetStr.String()) } } func TestSetParseLocalTimes(t *testing.T) { moscow, _ := time.LoadLocation("Europe/Moscow") t.Run("DtstartTimeZoneIsUsed", func(t *testing.T) { input := []string{ "DTSTART;TZID=Europe/Moscow:20180220T090000", "RDATE;VALUE=DATE-TIME:20180223T100000", } s, err := StrSliceToRRuleSet(input) if err != nil { t.Error(err) } d := s.GetRDate()[0] if !d.Equal(time.Date(2018, 02, 23, 10, 0, 0, 0, moscow)) { t.Error("Bad time parsed: ", d) } }) t.Run("DtstartTimeZoneValidOutput", func(t *testing.T) { input := []string{ "DTSTART;TZID=Europe/Moscow:20180220T090000", "RDATE;VALUE=DATE-TIME:20180223T100000", } expected := "DTSTART;TZID=Europe/Moscow:20180220T090000\nRDATE:20180223T070000Z" s, err := StrSliceToRRuleSet(input) if err != nil { t.Error(err) } sRRule := s.String() if sRRule != expected { t.Error(fmt.Sprintf("DTSTART output not valid. Expected: \n%s \n Got: \n%s", expected, sRRule)) } }) t.Run("DtstartUTCValidOutput", func(t *testing.T) { input := []string{ "DTSTART:20180220T090000Z", "RDATE;VALUE=DATE-TIME:20180223T100000", } expected := "DTSTART:20180220T090000Z\nRDATE:20180223T100000Z" s, err := StrSliceToRRuleSet(input) if err != nil { t.Error(err) } sRRule := s.String() if sRRule != expected { t.Error(fmt.Sprintf("DTSTART output not valid. Expected: \n%s \n Got: \n%s", expected, sRRule)) } }) t.Run("SpecifiedDefaultZoneIsUsed", func(t *testing.T) { input := []string{ "RDATE;VALUE=DATE-TIME:20180223T100000", } s, err := StrSliceToRRuleSetInLoc(input, moscow) if err != nil { t.Error(err) } d := s.GetRDate()[0] if !d.Equal(time.Date(2018, 02, 23, 10, 0, 0, 0, moscow)) { t.Error("Bad time parsed: ", d) } }) } func TestRDateValueDateStr(t *testing.T) { input := []string{ "RDATE;VALUE=DATE:20180223", } s, err := StrSliceToRRuleSet(input) if err != nil { t.Error(err) } d := s.GetRDate()[0] if !d.Equal(time.Date(2018, 02, 23, 0, 0, 0, 0, time.UTC)) { t.Error("Bad time parsed: ", d) } } func TestStrSetEmptySliceParse(t *testing.T) { s, err := StrSliceToRRuleSet([]string{}) if err != nil { t.Error(err) } if s == nil { t.Error("Empty set should not be nil") } } func TestStrSetParseErrors(t *testing.T) { inputs := [][]string{ {"RRULE:XXX"}, {"RDATE;TZD=X:1"}, } for _, ss := range inputs { if _, err := StrSliceToRRuleSet(ss); err == nil { t.Error("Expected parse error for rules: ", ss) } } } rrule-go-1.6.0/util.go000066400000000000000000000066661370273745400146100ustar00rootroot00000000000000package rrule import ( "errors" "math" "time" ) // MAXYEAR const ( MAXYEAR = 9999 ) // Next is a generator of time.Time. // It returns false of Ok if there is no value to generate. type Next func() (value time.Time, ok bool) type timeSlice []time.Time func (s timeSlice) Len() int { return len(s) } func (s timeSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s timeSlice) Less(i, j int) bool { return s[i].Before(s[j]) } // Python: MO-SU: 0 - 6 // Golang: SU-SAT 0 - 6 func toPyWeekday(from time.Weekday) int { return []int{6, 0, 1, 2, 3, 4, 5}[from] } // year -> 1 if leap year, else 0." func isLeap(year int) int { if year%4 == 0 && (year%100 != 0 || year%400 == 0) { return 1 } return 0 } // daysIn returns the number of days in a month for a given year. func daysIn(m time.Month, year int) int { return time.Date(year, m+1, 0, 0, 0, 0, 0, time.UTC).Day() } // mod in Python func pymod(a, b int) int { r := a % b // If r and b differ in sign, add b to wrap the result to the correct sign. if r*b < 0 { r += b } return r } // divmod in Python func divmod(a, b int) (div, mod int) { return int(math.Floor(float64(a) / float64(b))), pymod(a, b) } func contains(list []int, elem int) bool { for _, t := range list { if t == elem { return true } } return false } func timeContains(list []time.Time, elem time.Time) bool { for _, t := range list { if t == elem { return true } } return false } func repeat(value, count int) []int { result := []int{} for i := 0; i < count; i++ { result = append(result, value) } return result } func concat(slices ...[]int) []int { result := []int{} for _, item := range slices { result = append(result, item...) } return result } func rang(start, end int) []int { result := []int{} for i := start; i < end; i++ { result = append(result, i) } return result } func pySubscript(slice []int, index int) (int, error) { if index < 0 { index += len(slice) } if index < 0 || index >= len(slice) { return 0, errors.New("index error") } return slice[index], nil } func timeSliceIterator(s []time.Time) func() (time.Time, bool) { index := 0 return func() (time.Time, bool) { if index >= len(s) { return time.Time{}, false } result := s[index] index++ return result, true } } func easter(year int) time.Time { g := year % 19 c := year / 100 h := (c - c/4 - (8*c+13)/25 + 19*g + 15) % 30 i := h - (h/28)*(1-(h/28)*(29/(h+1))*((21-g)/11)) j := (year + year/4 + i + 2 - c + c/4) % 7 p := i - j d := 1 + (p+27+(p+6)/40)%31 m := 3 + (p+26)/30 return time.Date(year, time.Month(m), d, 0, 0, 0, 0, time.UTC) } func all(next Next) []time.Time { result := []time.Time{} for { v, ok := next() if !ok { return result } result = append(result, v) } } func between(next Next, after, before time.Time, inc bool) []time.Time { result := []time.Time{} for { v, ok := next() if !ok || inc && v.After(before) || !inc && !v.Before(before) { return result } if inc && !v.Before(after) || !inc && v.After(after) { result = append(result, v) } } } func before(next Next, dt time.Time, inc bool) time.Time { result := time.Time{} for { v, ok := next() if !ok || inc && v.After(dt) || !inc && !v.Before(dt) { return result } result = v } } func after(next Next, dt time.Time, inc bool) time.Time { for { v, ok := next() if !ok { return time.Time{} } if inc && !v.Before(dt) || !inc && v.After(dt) { return v } } }