Added support for DST with configurable behavior.
This commit is contained in:
parent
f0984319b4
commit
0dc543e5ad
|
@ -69,6 +69,7 @@ Other details
|
||||||
* If only five fields are present, a `0` second field is prepended and a wildcard year field is appended, that is, `* * * * Mon` internally become `0 * * * * Mon *`.
|
* If only five fields are present, a `0` second field is prepended and a wildcard year field is appended, that is, `* * * * Mon` internally become `0 * * * * Mon *`.
|
||||||
* Domain for day-of-week field is [0-7] instead of [0-6], 7 being Sunday (like 0). This to comply with http://linux.die.net/man/5/crontab#.
|
* Domain for day-of-week field is [0-7] instead of [0-6], 7 being Sunday (like 0). This to comply with http://linux.die.net/man/5/crontab#.
|
||||||
* As of now, the behavior of the code is undetermined if a malformed cron expression is supplied
|
* As of now, the behavior of the code is undetermined if a malformed cron expression is supplied
|
||||||
|
* By default, on a DST change, it returns the times that would have been skipped when the clock moves forward and returns only once the times that would have been repeated when the clock moves backwards. For customized behavior, see the godoc documentation.
|
||||||
|
|
||||||
Install
|
Install
|
||||||
-------
|
-------
|
||||||
|
@ -131,4 +132,3 @@ License: pick the one which suits you best:
|
||||||
|
|
||||||
- GPL v3 see <https://www.gnu.org/licenses/gpl.html>
|
- GPL v3 see <https://www.gnu.org/licenses/gpl.html>
|
||||||
- APL v2 see <http://www.apache.org/licenses/LICENSE-2.0>
|
- APL v2 see <http://www.apache.org/licenses/LICENSE-2.0>
|
||||||
|
|
||||||
|
|
35
cronexpr.go
35
cronexpr.go
|
@ -27,6 +27,7 @@ import (
|
||||||
// <https://github.com/gorhill/cronexpr#implementation>
|
// <https://github.com/gorhill/cronexpr#implementation>
|
||||||
type Expression struct {
|
type Expression struct {
|
||||||
expression string
|
expression string
|
||||||
|
options Options
|
||||||
secondList []int
|
secondList []int
|
||||||
minuteList []int
|
minuteList []int
|
||||||
hourList []int
|
hourList []int
|
||||||
|
@ -42,6 +43,30 @@ type Expression struct {
|
||||||
lastWeekDaysOfWeek map[int]bool
|
lastWeekDaysOfWeek map[int]bool
|
||||||
daysOfWeekRestricted bool
|
daysOfWeekRestricted bool
|
||||||
yearList []int
|
yearList []int
|
||||||
|
|
||||||
|
// indicates an instance of a expression used internally
|
||||||
|
// for rounding a time
|
||||||
|
rounding bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type DSTFlags uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DSTLeapUnskip indicates the parser to not skip times that would have been
|
||||||
|
// been skipped when the clock moves forward on a DST change.
|
||||||
|
DSTLeapUnskip = 1 << iota
|
||||||
|
|
||||||
|
// DSTFallFireEarly indicates the parser to return the earliest time
|
||||||
|
// when a time is repeated due to a DST fall.
|
||||||
|
DSTFallFireEarly
|
||||||
|
|
||||||
|
// DSTFallFireLate indicates the parser to return the latest time
|
||||||
|
// when a time is repeated due to a DST fall.
|
||||||
|
DSTFallFireLate
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
DSTFlags DSTFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -67,6 +92,14 @@ func MustParse(cronLine string) *Expression {
|
||||||
// about what is a well-formed cron expression from this library's point of
|
// about what is a well-formed cron expression from this library's point of
|
||||||
// view.
|
// view.
|
||||||
func Parse(cronLine string) (*Expression, error) {
|
func Parse(cronLine string) (*Expression, error) {
|
||||||
|
return ParseWithOptions(cronLine, Options{DSTFlags: DSTLeapUnskip | DSTFallFireEarly})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseWithOptions is used to build a Expression pointer with custom options.
|
||||||
|
func ParseWithOptions(cronLine string, options Options) (*Expression, error) {
|
||||||
|
if options.DSTFlags == 0 {
|
||||||
|
return nil, fmt.Errorf("missing DST flags")
|
||||||
|
}
|
||||||
|
|
||||||
// Maybe one of the built-in aliases is being used
|
// Maybe one of the built-in aliases is being used
|
||||||
cron := cronNormalizer.Replace(cronLine)
|
cron := cronNormalizer.Replace(cronLine)
|
||||||
|
@ -81,7 +114,7 @@ func Parse(cronLine string) (*Expression, error) {
|
||||||
fieldCount = 7
|
fieldCount = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
var expr = Expression{}
|
var expr = Expression{options: options}
|
||||||
var field = 0
|
var field = 0
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
214
cronexpr_next.go
214
cronexpr_next.go
|
@ -53,7 +53,8 @@ func (expr *Expression) nextYear(t time.Time) time.Time {
|
||||||
0,
|
0,
|
||||||
t.Location()))
|
t.Location()))
|
||||||
}
|
}
|
||||||
return time.Date(
|
|
||||||
|
next := time.Date(
|
||||||
expr.yearList[i],
|
expr.yearList[i],
|
||||||
time.Month(expr.monthList[0]),
|
time.Month(expr.monthList[0]),
|
||||||
expr.actualDaysOfMonthList[0],
|
expr.actualDaysOfMonthList[0],
|
||||||
|
@ -61,7 +62,9 @@ func (expr *Expression) nextYear(t time.Time) time.Time {
|
||||||
expr.minuteList[0],
|
expr.minuteList[0],
|
||||||
expr.secondList[0],
|
expr.secondList[0],
|
||||||
0,
|
0,
|
||||||
t.Location())
|
time.UTC)
|
||||||
|
|
||||||
|
return expr.nextTime(t, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -87,7 +90,7 @@ func (expr *Expression) nextMonth(t time.Time) time.Time {
|
||||||
t.Location()))
|
t.Location()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Date(
|
next := time.Date(
|
||||||
t.Year(),
|
t.Year(),
|
||||||
time.Month(expr.monthList[i]),
|
time.Month(expr.monthList[i]),
|
||||||
expr.actualDaysOfMonthList[0],
|
expr.actualDaysOfMonthList[0],
|
||||||
|
@ -95,7 +98,9 @@ func (expr *Expression) nextMonth(t time.Time) time.Time {
|
||||||
expr.minuteList[0],
|
expr.minuteList[0],
|
||||||
expr.secondList[0],
|
expr.secondList[0],
|
||||||
0,
|
0,
|
||||||
t.Location())
|
time.UTC)
|
||||||
|
|
||||||
|
return expr.nextTime(t, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -108,7 +113,7 @@ func (expr *Expression) nextDayOfMonth(t time.Time) time.Time {
|
||||||
return expr.nextMonth(t)
|
return expr.nextMonth(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Date(
|
next := time.Date(
|
||||||
t.Year(),
|
t.Year(),
|
||||||
t.Month(),
|
t.Month(),
|
||||||
expr.actualDaysOfMonthList[i],
|
expr.actualDaysOfMonthList[i],
|
||||||
|
@ -116,7 +121,9 @@ func (expr *Expression) nextDayOfMonth(t time.Time) time.Time {
|
||||||
expr.minuteList[0],
|
expr.minuteList[0],
|
||||||
expr.secondList[0],
|
expr.secondList[0],
|
||||||
0,
|
0,
|
||||||
t.Location())
|
time.UTC)
|
||||||
|
|
||||||
|
return expr.nextTime(t, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -129,7 +136,7 @@ func (expr *Expression) nextHour(t time.Time) time.Time {
|
||||||
return expr.nextDayOfMonth(t)
|
return expr.nextDayOfMonth(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Date(
|
next := time.Date(
|
||||||
t.Year(),
|
t.Year(),
|
||||||
t.Month(),
|
t.Month(),
|
||||||
t.Day(),
|
t.Day(),
|
||||||
|
@ -137,7 +144,9 @@ func (expr *Expression) nextHour(t time.Time) time.Time {
|
||||||
expr.minuteList[0],
|
expr.minuteList[0],
|
||||||
expr.secondList[0],
|
expr.secondList[0],
|
||||||
0,
|
0,
|
||||||
t.Location())
|
time.UTC)
|
||||||
|
|
||||||
|
return expr.nextTime(t, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -150,7 +159,7 @@ func (expr *Expression) nextMinute(t time.Time) time.Time {
|
||||||
return expr.nextHour(t)
|
return expr.nextHour(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Date(
|
next := time.Date(
|
||||||
t.Year(),
|
t.Year(),
|
||||||
t.Month(),
|
t.Month(),
|
||||||
t.Day(),
|
t.Day(),
|
||||||
|
@ -158,7 +167,9 @@ func (expr *Expression) nextMinute(t time.Time) time.Time {
|
||||||
expr.minuteList[i],
|
expr.minuteList[i],
|
||||||
expr.secondList[0],
|
expr.secondList[0],
|
||||||
0,
|
0,
|
||||||
t.Location())
|
time.UTC)
|
||||||
|
|
||||||
|
return expr.nextTime(t, next)
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -174,7 +185,7 @@ func (expr *Expression) nextSecond(t time.Time) time.Time {
|
||||||
return expr.nextMinute(t)
|
return expr.nextMinute(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return time.Date(
|
next := time.Date(
|
||||||
t.Year(),
|
t.Year(),
|
||||||
t.Month(),
|
t.Month(),
|
||||||
t.Day(),
|
t.Day(),
|
||||||
|
@ -182,7 +193,106 @@ func (expr *Expression) nextSecond(t time.Time) time.Time {
|
||||||
t.Minute(),
|
t.Minute(),
|
||||||
expr.secondList[i],
|
expr.secondList[i],
|
||||||
0,
|
0,
|
||||||
t.Location())
|
time.UTC)
|
||||||
|
|
||||||
|
return expr.nextTime(t, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *Expression) roundTime(t time.Time) time.Time {
|
||||||
|
roundingExpr := new(Expression)
|
||||||
|
*roundingExpr = *expr
|
||||||
|
roundingExpr.rounding = true
|
||||||
|
|
||||||
|
i := sort.SearchInts(expr.hourList, t.Hour())
|
||||||
|
if i == len(expr.hourList) || expr.hourList[i] != t.Hour() {
|
||||||
|
return roundingExpr.nextHour(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
i = sort.SearchInts(expr.minuteList, t.Minute())
|
||||||
|
if i == len(expr.minuteList) || expr.minuteList[i] != t.Minute() {
|
||||||
|
return roundingExpr.nextMinute(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
i = sort.SearchInts(expr.secondList, t.Second())
|
||||||
|
if i == len(expr.secondList) || expr.secondList[i] != t.Second() {
|
||||||
|
return roundingExpr.nextSecond(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *Expression) isRounded(t time.Time) bool {
|
||||||
|
i := sort.SearchInts(expr.hourList, t.Hour())
|
||||||
|
if i == len(expr.hourList) || expr.hourList[i] != t.Hour() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
i = sort.SearchInts(expr.minuteList, t.Minute())
|
||||||
|
if i == len(expr.minuteList) || expr.minuteList[i] != t.Minute() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
i = sort.SearchInts(expr.secondList, t.Second())
|
||||||
|
if i == len(expr.secondList) || expr.secondList[i] != t.Second() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (expr *Expression) nextTime(prev, next time.Time) time.Time {
|
||||||
|
dstFlags := expr.options.DSTFlags
|
||||||
|
t := prev.Add(noTZDiff(prev, next))
|
||||||
|
offsetDiff := utcOffset(t) - utcOffset(prev)
|
||||||
|
|
||||||
|
// a dst leap occurred
|
||||||
|
if offsetDiff > 0 {
|
||||||
|
if dstFlags&DSTLeapUnskip != 0 {
|
||||||
|
return findTimeOfDSTChange(prev, t).Add(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr.roundTime(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// a dst fall occurred
|
||||||
|
if offsetDiff < 0 {
|
||||||
|
twinT := findTwinTime(prev)
|
||||||
|
|
||||||
|
if !twinT.IsZero() {
|
||||||
|
if dstFlags&DSTFallFireLate != 0 {
|
||||||
|
return twinT
|
||||||
|
}
|
||||||
|
if dstFlags&DSTFallFireEarly != 0 {
|
||||||
|
// skip the twin time
|
||||||
|
return expr.Next(expr.roundTime(twinT))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dstFlags&DSTFallFireEarly != 0 {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr.roundTime(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
twinT := findTwinTime(t)
|
||||||
|
if !twinT.IsZero() {
|
||||||
|
if dstFlags&DSTFallFireEarly != 0 {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
if dstFlags&DSTFallFireLate != 0 {
|
||||||
|
return twinT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dstFlags&DSTFallFireLate == 0 && !expr.rounding {
|
||||||
|
twinT = findTwinTime(prev)
|
||||||
|
if !twinT.IsZero() && twinT.Before(prev) && !expr.isRounded(prev) {
|
||||||
|
return expr.Next(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -290,3 +400,83 @@ func workdayOfMonth(targetDom, lastDom time.Time) int {
|
||||||
}
|
}
|
||||||
return dom
|
return dom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func utcOffset(t time.Time) int {
|
||||||
|
_, offset := t.Zone()
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func noTZ(t time.Time) time.Time {
|
||||||
|
return t.UTC().Add(time.Duration(utcOffset(t)) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func noTZDiff(t1, t2 time.Time) time.Duration {
|
||||||
|
t1 = noTZ(t1)
|
||||||
|
t2 = noTZ(t2)
|
||||||
|
return t2.Sub(t1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findTimeOfDSTChange returns the time a second before a DST change occurs,
|
||||||
|
// and returns zero time in case there's no DST change.
|
||||||
|
func findTimeOfDSTChange(t1, t2 time.Time) time.Time {
|
||||||
|
if t1.Location() != t2.Location() || utcOffset(t1) == utcOffset(t2) || t1.Location() == time.UTC {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure t2 > t1
|
||||||
|
if t2.Before(t1) {
|
||||||
|
t := t2
|
||||||
|
t1 = t2
|
||||||
|
t2 = t
|
||||||
|
}
|
||||||
|
|
||||||
|
// do a binary search to find the time one second before the dst change
|
||||||
|
len := t2.Unix() - t1.Unix()
|
||||||
|
var a int64
|
||||||
|
b := len
|
||||||
|
for len > 1 {
|
||||||
|
len = (b - a + 1) / 2
|
||||||
|
if utcOffset(t1.Add(time.Duration(a+len)*time.Second)) != utcOffset(t1) {
|
||||||
|
b = a + len
|
||||||
|
} else {
|
||||||
|
a = a + len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return t1.Add(time.Duration(a) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a DST fall accurs, a certain interval of time is repeated. Once
|
||||||
|
// in DST time and once in standard time.
|
||||||
|
// findTwinTime tries to find the repated "twin" time if one exists.
|
||||||
|
func findTwinTime(t time.Time) time.Time {
|
||||||
|
offsetDiff := utcOffset(t.Add(12*time.Hour)) - utcOffset(t)
|
||||||
|
// a fall occurs within the next 12 hours
|
||||||
|
if offsetDiff < 0 {
|
||||||
|
border := findTimeOfDSTChange(t, t.Add(12*time.Hour))
|
||||||
|
t0 := border.Add(time.Duration(offsetDiff) * time.Second)
|
||||||
|
|
||||||
|
if t0.After(t) {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
dur := t.Sub(t0)
|
||||||
|
return border.Add(dur)
|
||||||
|
}
|
||||||
|
|
||||||
|
offsetDiff = utcOffset(t) - utcOffset(t.Add(-12*time.Second))
|
||||||
|
// a fall occurred in the past 12 hours
|
||||||
|
if offsetDiff < 0 {
|
||||||
|
border := findTimeOfDSTChange(t.Add(-12*time.Hour), t)
|
||||||
|
t0 := border.Add(time.Duration(offsetDiff) * time.Second)
|
||||||
|
|
||||||
|
if t0.Add(time.Duration(-2*offsetDiff) * time.Second).Before(t) {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
dur := t.Sub(border)
|
||||||
|
return t0.Add(dur)
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
490
cronexpr_test.go
490
cronexpr_test.go
|
@ -15,6 +15,7 @@ package cronexpr_test
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -284,6 +285,495 @@ func TestNextN_every5min(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDST(t *testing.T) {
|
||||||
|
var locs [3]*time.Location
|
||||||
|
|
||||||
|
// 1 hour DST, negative UTC offset
|
||||||
|
// time.Date(2014, 3, 9, 2, 0, 0, 0, locs[0]) Leap PST -> PDT
|
||||||
|
// time.Date(2014, 11, 2, 1, 59, 59, 0, locs[0]) Fall PDT -> PST
|
||||||
|
locs[0], _ = time.LoadLocation("America/Los_Angeles")
|
||||||
|
|
||||||
|
// biggest tz leap ever (3 hours), occurred from YAKT to MAGST
|
||||||
|
// at time.Date(1981, 4, 1, 3, 0, 0, 0, locs[1]),
|
||||||
|
locs[1], _ = time.LoadLocation("Asia/Ust-Nera")
|
||||||
|
|
||||||
|
// 30 mins DST, positive UTC offset
|
||||||
|
// time.Date(2014, 10, 5, 2, 30, 0, 0, locs[2]) Leap LHST -> LHDT
|
||||||
|
// time.Date(2015, 4, 5, 1, 30, 0, 0, locs[2]) Fall LHDT -> LHST
|
||||||
|
locs[2], _ = time.LoadLocation("Australia/LHI")
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
expr string
|
||||||
|
opts cronexpr.Options
|
||||||
|
from time.Time
|
||||||
|
expected []time.Time
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily leap skip", locs[0]),
|
||||||
|
"0 0 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 3, 9, 1, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 3, 10, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 11, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 12, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 13, 2, 0, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily leap unskip", locs[0]),
|
||||||
|
"0 0 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 3, 9, 1, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 3, 9, 3, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 10, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 11, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 12, 2, 0, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly leap skip", locs[0]),
|
||||||
|
"0 0 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 3, 9, 1, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 3, 9, 3, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 9, 4, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 9, 5, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 9, 6, 0, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly leap unskip", locs[0]),
|
||||||
|
"0 0 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 3, 9, 1, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 3, 9, 3, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 9, 4, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 9, 5, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 9, 6, 0, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily quarter-hourly leap skip", locs[0]),
|
||||||
|
"0 */15 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 3, 9, 1, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 3, 10, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 10, 2, 15, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 10, 2, 30, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 10, 2, 45, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily quarter-hourly leap unskip", locs[0]),
|
||||||
|
"0 */15 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 3, 9, 1, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 3, 9, 3, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 9, 3, 15, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 9, 3, 30, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 3, 9, 3, 45, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily fall fire early", locs[0]),
|
||||||
|
"0 0 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 11, 1, 2, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 11, 2, 1, 0, 0, 0, locs[0]).Add(1 * time.Hour),
|
||||||
|
time.Date(2014, 11, 3, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 4, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 5, 2, 0, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily fall fire late", locs[0]),
|
||||||
|
"0 0 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireLate},
|
||||||
|
time.Date(2014, 11, 1, 2, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 11, 2, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 3, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 4, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 5, 2, 0, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily fall fire both", locs[0]),
|
||||||
|
"0 0 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly | cronexpr.DSTFallFireLate},
|
||||||
|
time.Date(2014, 11, 1, 2, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 11, 2, 1, 0, 0, 0, locs[0]).Add(1 * time.Hour),
|
||||||
|
time.Date(2014, 11, 2, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 3, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 4, 2, 0, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly fall fire early", locs[0]),
|
||||||
|
"0 0 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 11, 2, 0, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 11, 2, 1, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 2, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 2, 3, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 2, 4, 0, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly fall fire late", locs[0]),
|
||||||
|
"0 0 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireLate},
|
||||||
|
time.Date(2014, 11, 2, 0, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 11, 2, 1, 0, 0, 0, locs[0]).Add(1 * time.Hour),
|
||||||
|
time.Date(2014, 11, 2, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 2, 3, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 2, 4, 0, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly fall fire twice", locs[0]),
|
||||||
|
"0 0 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly | cronexpr.DSTFallFireLate},
|
||||||
|
time.Date(2014, 11, 2, 0, 0, 0, 0, locs[0]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 11, 2, 1, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 2, 1, 0, 0, 0, locs[0]).Add(1 * time.Hour),
|
||||||
|
time.Date(2014, 11, 2, 2, 0, 0, 0, locs[0]),
|
||||||
|
time.Date(2014, 11, 2, 3, 0, 0, 0, locs[0]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily leap skip", locs[1]),
|
||||||
|
"0 0 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(1981, 3, 31, 2, 0, 0, 0, locs[1]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(1981, 4, 2, 2, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 3, 2, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 4, 2, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 5, 2, 0, 0, 0, locs[1]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily leap unskip", locs[1]),
|
||||||
|
"0 0 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(1981, 3, 31, 2, 0, 0, 0, locs[1]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(1981, 4, 1, 3, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 2, 2, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 3, 2, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 4, 2, 0, 0, 0, locs[1]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly leap skip", locs[1]),
|
||||||
|
"0 0 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(1981, 3, 31, 23, 0, 0, 0, locs[1]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(1981, 4, 1, 3, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 4, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 5, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 6, 0, 0, 0, locs[1]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly leap unskip", locs[1]),
|
||||||
|
"0 0 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(1981, 3, 31, 23, 0, 0, 0, locs[1]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(1981, 4, 1, 3, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 4, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 5, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 6, 0, 0, 0, locs[1]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s quarter-hourly leap skip", locs[1]),
|
||||||
|
"0 */15 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(1981, 3, 31, 23, 15, 0, 0, locs[1]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(1981, 3, 31, 23, 30, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 3, 31, 23, 45, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 3, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 3, 15, 0, 0, locs[1]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s quarter-hourly leap unskip", locs[1]),
|
||||||
|
"0 */15 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(1981, 3, 31, 23, 15, 0, 0, locs[1]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(1981, 3, 31, 23, 30, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 3, 31, 23, 45, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 3, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 3, 15, 0, 0, locs[1]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily third-hourly leap skip", locs[1]),
|
||||||
|
"0 */20 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(1981, 3, 31, 2, 40, 0, 0, locs[1]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(1981, 4, 2, 2, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 2, 2, 20, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 2, 2, 40, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 3, 2, 0, 0, 0, locs[1]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily third-hourly leap unskip", locs[1]),
|
||||||
|
"0 */20 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(1981, 3, 31, 2, 40, 0, 0, locs[1]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(1981, 4, 1, 3, 0, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 3, 20, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 1, 3, 40, 0, 0, locs[1]),
|
||||||
|
time.Date(1981, 4, 2, 2, 0, 0, 0, locs[1]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily leap skip", locs[2]),
|
||||||
|
"0 0 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 10, 4, 2, 0, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 10, 6, 2, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 7, 2, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 8, 2, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 9, 2, 0, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily leap unskip", locs[2]),
|
||||||
|
"0 0 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 10, 4, 2, 0, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 10, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 6, 2, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 7, 2, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 8, 2, 0, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly leap skip", locs[2]),
|
||||||
|
"0 0 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 10, 5, 1, 0, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 10, 5, 3, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 4, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 5, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 6, 0, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly leap unskip", locs[2]),
|
||||||
|
"0 0 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 10, 5, 1, 0, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 10, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 3, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 4, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 5, 0, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s quarter-hourly leap skip", locs[2]),
|
||||||
|
"0 */15 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 10, 5, 1, 15, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 10, 5, 1, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 1, 45, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 2, 45, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s quarter-hourly leap unskip", locs[2]),
|
||||||
|
"0 */15 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 10, 5, 1, 15, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 10, 5, 1, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 1, 45, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 2, 45, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s third-hourly leap skip", locs[2]),
|
||||||
|
"0 */20 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 10, 4, 2, 40, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 10, 5, 2, 40, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 6, 2, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 6, 2, 20, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 6, 2, 40, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s third-hourly leap unskip", locs[2]),
|
||||||
|
"0 */20 2 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2014, 10, 4, 2, 40, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2014, 10, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 5, 2, 40, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 6, 2, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2014, 10, 6, 2, 20, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily fall fire early", locs[2]),
|
||||||
|
"0 45 1 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2015, 4, 5, 1, 0, 0, 0, locs[2]).Add(45 * time.Minute),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2015, 4, 6, 1, 45, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 7, 1, 45, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 8, 1, 45, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 9, 1, 45, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily fall fire late", locs[2]),
|
||||||
|
"0 45 1 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireLate},
|
||||||
|
time.Date(2015, 4, 5, 1, 45, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2015, 4, 6, 1, 45, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 7, 1, 45, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 8, 1, 45, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 9, 1, 45, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s daily fall fire twice", locs[2]),
|
||||||
|
"0 45 1 * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly | cronexpr.DSTFallFireLate},
|
||||||
|
time.Date(2015, 4, 5, 1, 0, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2015, 4, 5, 1, 0, 0, 0, locs[2]).Add(45 * time.Minute),
|
||||||
|
time.Date(2015, 4, 5, 1, 45, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 6, 1, 45, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 7, 1, 45, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly fall fire early", locs[2]),
|
||||||
|
"0 30 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2015, 4, 5, 0, 30, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2015, 4, 5, 0, 30, 0, 0, locs[2]).Add(1 * time.Hour),
|
||||||
|
time.Date(2015, 4, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 3, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 4, 30, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly fall fire late", locs[2]),
|
||||||
|
"0 30 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireLate},
|
||||||
|
time.Date(2015, 4, 5, 0, 30, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2015, 4, 5, 1, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 3, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 4, 30, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s hourly fall fire twice", locs[2]),
|
||||||
|
"0 30 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly | cronexpr.DSTFallFireLate},
|
||||||
|
time.Date(2015, 4, 5, 0, 30, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2015, 4, 5, 0, 30, 0, 0, locs[2]).Add(1 * time.Hour),
|
||||||
|
time.Date(2015, 4, 5, 1, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 3, 30, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s half-hourly fall fire early", locs[2]),
|
||||||
|
"0 */30 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly},
|
||||||
|
time.Date(2015, 4, 5, 1, 0, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2015, 4, 5, 1, 0, 0, 0, locs[2]).Add(30 * time.Minute),
|
||||||
|
time.Date(2015, 4, 5, 2, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 3, 0, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s half-hourly fall fire late", locs[2]),
|
||||||
|
"0 */30 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireLate},
|
||||||
|
time.Date(2015, 4, 5, 1, 0, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2015, 4, 5, 1, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 2, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 3, 0, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fmt.Sprintf("%s half-hourly fall fire twice", locs[2]),
|
||||||
|
"0 */30 * * * * *",
|
||||||
|
cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly | cronexpr.DSTFallFireLate},
|
||||||
|
time.Date(2015, 4, 5, 1, 0, 0, 0, locs[2]),
|
||||||
|
[]time.Time{
|
||||||
|
time.Date(2015, 4, 5, 1, 0, 0, 0, locs[2]).Add(30 * time.Minute),
|
||||||
|
time.Date(2015, 4, 5, 1, 30, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 2, 0, 0, 0, locs[2]),
|
||||||
|
time.Date(2015, 4, 5, 2, 30, 0, 0, locs[2]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
s, err := cronexpr.ParseWithOptions(tc.expr, tc.opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parser error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
runs := s.NextN(tc.from, 4)
|
||||||
|
if len(runs) != 4 {
|
||||||
|
t.Errorf("Case %s: Expected 4 runs, got %d", tc.name, len(runs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(runs); i++ {
|
||||||
|
if !runs[i].Equal(tc.expected[i]) {
|
||||||
|
t.Errorf("Case %s: Expected %v, got %v", tc.name, tc.expected[i], runs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var benchmarkExpressions = []string{
|
var benchmarkExpressions = []string{
|
||||||
|
|
|
@ -35,3 +35,81 @@ func ExampleMustParse() {
|
||||||
// Sun, 29 Feb 2032 00:00:00 UTC
|
// Sun, 29 Feb 2032 00:00:00 UTC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure the parser to skip times in DST leaps and
|
||||||
|
// repeat times in DST falls
|
||||||
|
func ExampleParseWithOptions_dst1() {
|
||||||
|
loc, _ := time.LoadLocation("America/Los_Angeles")
|
||||||
|
t := time.Date(2014, 3, 8, 1, 0, 0, 0, loc)
|
||||||
|
expr, _ := cronexpr.ParseWithOptions("0 0 2 * * * *", cronexpr.Options{
|
||||||
|
DSTFlags: cronexpr.DSTFallFireEarly | cronexpr.DSTFallFireLate,
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println("DST leap times:")
|
||||||
|
nextTimes := expr.NextN(t, 4)
|
||||||
|
for i := range nextTimes {
|
||||||
|
fmt.Println(nextTimes[i].Format(time.RFC1123))
|
||||||
|
}
|
||||||
|
|
||||||
|
t = time.Date(2014, 10, 31, 1, 0, 0, 0, loc)
|
||||||
|
expr, _ = cronexpr.ParseWithOptions("0 0 1 * * * *", cronexpr.Options{
|
||||||
|
DSTFlags: cronexpr.DSTFallFireEarly | cronexpr.DSTFallFireLate,
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println("DST fall times:")
|
||||||
|
nextTimes = expr.NextN(t, 4)
|
||||||
|
for i := range nextTimes {
|
||||||
|
fmt.Println(nextTimes[i].Format(time.RFC1123))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// DST leap times:
|
||||||
|
// Sat, 08 Mar 2014 02:00:00 PST
|
||||||
|
// Mon, 10 Mar 2014 02:00:00 PDT
|
||||||
|
// Tue, 11 Mar 2014 02:00:00 PDT
|
||||||
|
// Wed, 12 Mar 2014 02:00:00 PDT
|
||||||
|
// DST fall times:
|
||||||
|
// Sat, 01 Nov 2014 01:00:00 PDT
|
||||||
|
// Sun, 02 Nov 2014 01:00:00 PDT
|
||||||
|
// Sun, 02 Nov 2014 01:00:00 PST
|
||||||
|
// Mon, 03 Nov 2014 01:00:00 PST
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the parser to unskip times in DST leaps and
|
||||||
|
// fire late in DST falls
|
||||||
|
func ExampleParseWithOptions_dst2() {
|
||||||
|
loc, _ := time.LoadLocation("America/Los_Angeles")
|
||||||
|
t := time.Date(2014, 3, 8, 1, 0, 0, 0, loc)
|
||||||
|
expr, _ := cronexpr.ParseWithOptions("0 0 2 * * * *", cronexpr.Options{
|
||||||
|
DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireLate,
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println("DST leap times:")
|
||||||
|
nextTimes := expr.NextN(t, 4)
|
||||||
|
for i := range nextTimes {
|
||||||
|
fmt.Println(nextTimes[i].Format(time.RFC1123))
|
||||||
|
}
|
||||||
|
|
||||||
|
t = time.Date(2014, 10, 31, 1, 0, 0, 0, loc)
|
||||||
|
expr, _ = cronexpr.ParseWithOptions("0 0 1 * * * *", cronexpr.Options{
|
||||||
|
DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireLate,
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Println("DST fall times:")
|
||||||
|
nextTimes = expr.NextN(t, 4)
|
||||||
|
for i := range nextTimes {
|
||||||
|
fmt.Println(nextTimes[i].Format(time.RFC1123))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// DST leap times:
|
||||||
|
// Sat, 08 Mar 2014 02:00:00 PST
|
||||||
|
// Sun, 09 Mar 2014 03:00:00 PDT
|
||||||
|
// Mon, 10 Mar 2014 02:00:00 PDT
|
||||||
|
// Tue, 11 Mar 2014 02:00:00 PDT
|
||||||
|
// DST fall times:
|
||||||
|
// Sat, 01 Nov 2014 01:00:00 PDT
|
||||||
|
// Sun, 02 Nov 2014 01:00:00 PST
|
||||||
|
// Mon, 03 Nov 2014 01:00:00 PST
|
||||||
|
// Tue, 04 Nov 2014 01:00:00 PST
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue