From 1a49152fbcceaecb60016c8bd407b1f72007e1f6 Mon Sep 17 00:00:00 2001 From: Augusto Becciu Date: Wed, 11 Jan 2017 22:07:19 -0800 Subject: [PATCH] Fixed dst leap adjustment. --- cronexpr_next.go | 30 ++++++++++++++------ cronexpr_test.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/cronexpr_next.go b/cronexpr_next.go index e9d6ec7..9aad6bd 100644 --- a/cronexpr_next.go +++ b/cronexpr_next.go @@ -247,11 +247,23 @@ func (expr *Expression) nextTime(prev, next time.Time) time.Time { // a dst leap occurred if offsetDiff > 0 { - if dstFlags&DSTLeapUnskip != 0 { - return findTimeOfDSTChange(prev, t).Add(1 * time.Second) + dstChangeTime := findTimeOfDSTChange(prev, t) + + // since a dst leap occured, t is offsetDiff seconds ahead + t = t.Add(-1 * offsetDiff) + + // check if t is within the skipped interval (offsetDiff) + if noTZDiff(dstChangeTime, t) < offsetDiff { + if dstFlags&DSTLeapUnskip != 0 { + // return the earliest time right after the leap + return dstChangeTime.Add(1 * time.Second) + } + + // return the next scheduled time right after the leap + return expr.roundTime(dstChangeTime.Add(1 * time.Second)) } - return expr.roundTime(t) + return t } // a dst fall occurred @@ -401,13 +413,13 @@ func workdayOfMonth(targetDom, lastDom time.Time) int { return dom } -func utcOffset(t time.Time) int { +func utcOffset(t time.Time) time.Duration { _, offset := t.Zone() - return offset + return time.Duration(offset) * time.Second } func noTZ(t time.Time) time.Time { - return t.UTC().Add(time.Duration(utcOffset(t)) * time.Second) + return t.UTC().Add(utcOffset(t)) } func noTZDiff(t1, t2 time.Time) time.Duration { @@ -454,7 +466,7 @@ func findTwinTime(t time.Time) time.Time { // 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) + t0 := border.Add(offsetDiff) if t0.After(t) { return t @@ -468,9 +480,9 @@ func findTwinTime(t time.Time) time.Time { // 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) + t0 := border.Add(offsetDiff) - if t0.Add(time.Duration(-2*offsetDiff) * time.Second).Before(t) { + if t0.Add(-2 * offsetDiff).Before(t) { return t } diff --git a/cronexpr_test.go b/cronexpr_test.go index a18ffad..b3a4fb5 100644 --- a/cronexpr_test.go +++ b/cronexpr_test.go @@ -333,6 +333,30 @@ func TestDST(t *testing.T) { time.Date(2014, 3, 12, 2, 0, 0, 0, locs[0]), }, }, + { + fmt.Sprintf("%s time after daily leap skip", locs[0]), + "0 5 14 * * * *", + cronexpr.Options{DSTFlags: cronexpr.DSTFallFireLate}, + time.Date(2016, 3, 12, 14, 6, 0, 0, locs[0]), + []time.Time{ + time.Date(2016, 3, 13, 14, 5, 0, 0, locs[0]), + time.Date(2016, 3, 14, 14, 5, 0, 0, locs[0]), + time.Date(2016, 3, 15, 14, 5, 0, 0, locs[0]), + time.Date(2016, 3, 16, 14, 5, 0, 0, locs[0]), + }, + }, + { + fmt.Sprintf("%s time after daily leap unskip", locs[0]), + "0 5 14 * * * *", + cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireLate}, + time.Date(2016, 3, 12, 14, 6, 0, 0, locs[0]), + []time.Time{ + time.Date(2016, 3, 13, 14, 5, 0, 0, locs[0]), + time.Date(2016, 3, 14, 14, 5, 0, 0, locs[0]), + time.Date(2016, 3, 15, 14, 5, 0, 0, locs[0]), + time.Date(2016, 3, 16, 14, 5, 0, 0, locs[0]), + }, + }, { fmt.Sprintf("%s hourly leap skip", locs[0]), "0 0 * * * * *", @@ -477,6 +501,30 @@ func TestDST(t *testing.T) { time.Date(1981, 4, 4, 2, 0, 0, 0, locs[1]), }, }, + { + fmt.Sprintf("%s time after daily leap skip", locs[1]), + "0 5 14 * * * *", + cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly}, + time.Date(1981, 3, 31, 15, 0, 0, 0, locs[1]), + []time.Time{ + time.Date(1981, 4, 1, 14, 5, 0, 0, locs[1]), + time.Date(1981, 4, 2, 14, 5, 0, 0, locs[1]), + time.Date(1981, 4, 3, 14, 5, 0, 0, locs[1]), + time.Date(1981, 4, 4, 14, 5, 0, 0, locs[1]), + }, + }, + { + fmt.Sprintf("%s time after daily leap unskip", locs[1]), + "0 5 14 * * * *", + cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly}, + time.Date(1981, 3, 31, 15, 0, 0, 0, locs[1]), + []time.Time{ + time.Date(1981, 4, 1, 14, 5, 0, 0, locs[1]), + time.Date(1981, 4, 2, 14, 5, 0, 0, locs[1]), + time.Date(1981, 4, 3, 14, 5, 0, 0, locs[1]), + time.Date(1981, 4, 4, 14, 5, 0, 0, locs[1]), + }, + }, { fmt.Sprintf("%s hourly leap skip", locs[1]), "0 0 * * * * *", @@ -573,6 +621,30 @@ func TestDST(t *testing.T) { time.Date(2014, 10, 8, 2, 0, 0, 0, locs[2]), }, }, + { + fmt.Sprintf("%s time after daily leap skip", locs[2]), + "0 5 14 * * * *", + cronexpr.Options{DSTFlags: cronexpr.DSTFallFireEarly}, + time.Date(2014, 10, 4, 15, 0, 0, 0, locs[2]), + []time.Time{ + time.Date(2014, 10, 5, 14, 5, 0, 0, locs[2]), + time.Date(2014, 10, 6, 14, 5, 0, 0, locs[2]), + time.Date(2014, 10, 7, 14, 5, 0, 0, locs[2]), + time.Date(2014, 10, 8, 14, 5, 0, 0, locs[2]), + }, + }, + { + fmt.Sprintf("%s time after daily leap unskip", locs[2]), + "0 5 14 * * * *", + cronexpr.Options{DSTFlags: cronexpr.DSTLeapUnskip | cronexpr.DSTFallFireEarly}, + time.Date(2014, 10, 4, 15, 0, 0, 0, locs[2]), + []time.Time{ + time.Date(2014, 10, 5, 14, 5, 0, 0, locs[2]), + time.Date(2014, 10, 6, 14, 5, 0, 0, locs[2]), + time.Date(2014, 10, 7, 14, 5, 0, 0, locs[2]), + time.Date(2014, 10, 8, 14, 5, 0, 0, locs[2]), + }, + }, { fmt.Sprintf("%s hourly leap skip", locs[2]), "0 0 * * * * *",