Skip to content

Commit 71a9be7

Browse files
committed
fix(next,gronx): day of week / day of month can be intersection or union
1 parent cfa9f62 commit 71a9be7

File tree

4 files changed

+101
-11
lines changed

4 files changed

+101
-11
lines changed

checker.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ func (c *SegmentChecker) SetRef(ref time.Time) {
3434
func (c *SegmentChecker) CheckDue(segment string, pos int) (due bool, err error) {
3535
ref, last := c.GetRef(), -1
3636
val, loc := valueByPos(ref, pos), ref.Location()
37-
isMonth, isWeekDay := pos == 3, pos == 5
37+
isMonthDay, isWeekDay := pos == 3, pos == 5
3838

3939
for _, offset := range strings.Split(segment, ",") {
40-
mod := (isMonth || isWeekDay) && strings.ContainsAny(offset, "LW#")
40+
mod := (isMonthDay || isWeekDay) && strings.ContainsAny(offset, "LW#")
4141
if due, err = c.isOffsetDue(offset, val, pos); due || (!mod && err != nil) {
4242
return
4343
}
@@ -47,7 +47,7 @@ func (c *SegmentChecker) CheckDue(segment string, pos int) (due bool, err error)
4747
if last == -1 {
4848
last = time.Date(ref.Year(), ref.Month(), 1, 0, 0, 0, 0, loc).AddDate(0, 1, 0).Add(-time.Second).Day()
4949
}
50-
if isMonth {
50+
if isMonthDay {
5151
due, err = isValidMonthDay(offset, last, ref)
5252
} else if isWeekDay {
5353
due, err = isValidWeekDay(offset, last, ref)

gronx.go

+34-1
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,44 @@ func Segments(expr string) ([]string, error) {
9696
// SegmentsDue checks if all cron parts are due.
9797
// It returns bool. You should use IsDue(expr) instead.
9898
func (g *Gronx) SegmentsDue(segs []string) (bool, error) {
99-
for pos, seg := range segs {
99+
skipMonthDayCheck := false
100+
for i := 0; i < len(segs); i++ {
101+
pos := len(segs) - 1 - i
102+
seg := segs[pos]
103+
isMonthDay, isWeekday := pos == 3, pos == 5
104+
100105
if seg == "*" || seg == "?" {
101106
continue
102107
}
103108

109+
if isMonthDay && skipMonthDayCheck {
110+
continue
111+
}
112+
113+
if isWeekday {
114+
segIsIntersecting := strings.Index(seg, "*/") == 0
115+
monthDaySeg := segs[3]
116+
monthDaySegIsIntersecting := strings.Index(monthDaySeg, "*") == 0 || monthDaySeg == "?"
117+
intersectCase := segIsIntersecting || monthDaySegIsIntersecting
118+
119+
if !intersectCase {
120+
due, err := g.C.CheckDue(seg, pos)
121+
if err != nil {
122+
return false, err
123+
}
124+
125+
monthDayDue, err := g.C.CheckDue(monthDaySeg, 3)
126+
if due || monthDayDue {
127+
skipMonthDayCheck = true
128+
continue
129+
}
130+
131+
if err != nil {
132+
return false, err
133+
}
134+
}
135+
}
136+
104137
if due, err := g.C.CheckDue(seg, pos); !due {
105138
return due, err
106139
}

gronx_test.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func testcases() []Case {
188188
{"0 0 * * 0,2-6", "2011-06-20 23:09:00", false, "2011-06-21 00:00:00"},
189189
{"0 0 1 1 0", "2011-06-15 23:09:00", false, "2012-01-01 00:00:00"},
190190
{"0 0 1 JAN 0", "2011-06-15 23:09:00", false, "2012-01-01 00:00:00"},
191-
{"0 0 1 * 0", "2011-06-15 23:09:00", false, "2012-01-01 00:00:00"},
191+
{"0 0 1 * 0", "2011-06-15 23:09:00", false, "2011-06-19 00:00:00"},
192192
{"0 0 L * *", "2011-07-15 00:00:00", false, "2011-07-31 00:00:00"},
193193
{"0 0 2W * *", "2011-07-01 00:00:00", true, "2011-08-02 00:00:00"},
194194
{"0 0 1W * *", "2011-05-01 00:00:00", false, "2011-05-02 00:00:00"},
@@ -214,12 +214,12 @@ func testcases() []Case {
214214
{"5/20 * * * *", "2018-08-13 00:24:00", false, "2018-08-13 00:25:00"},
215215
{"5/20 * * * *", "2018-08-13 00:45:00", true, "2018-08-13 01:05:00"},
216216
{"5-11/4 * * * *", "2018-08-13 00:03:00", false, "2018-08-13 00:05:00"},
217-
{"0 0 L * 0", "2011-06-15 23:09:00", false, "2011-07-31 00:00:00"},
218-
{"3-59/15 6-12 */15 1 2-5", "2017-01-08 00:00:00", false, "2018-01-30 06:03:00"},
217+
{"0 0 L * 0", "2011-06-15 23:09:00", false, "2011-06-19 00:00:00"},
218+
{"3-59/15 6-12 */15 1 2-5", "2017-01-08 00:00:00", false, "2017-01-31 06:03:00"},
219219
{"* * * * MON-FRI", "2017-01-08 00:00:00", false, "2017-01-09 00:00:00"},
220220
{"* * * * TUE", "2017-01-08 00:00:00", false, "2017-01-10 00:00:00"},
221-
{"0 1 15 JUL mon,Wed,FRi", "2019-11-14 00:00:00", false, "2020-07-15 01:00:00"},
222-
{"0 1 15 jul mon,Wed,FRi", "2019-11-14 00:00:00", false, "2020-07-15 01:00:00"},
221+
{"0 1 15 JUL mon,Wed,FRi", "2019-11-14 00:00:00", false, "2020-07-01 01:00:00"},
222+
{"0 1 15 jul mon,Wed,FRi", "2019-11-14 00:00:00", false, "2020-07-01 01:00:00"},
223223
{"@weekly", "2019-11-14 00:00:00", false, "2019-11-17 00:00:00"},
224224
{"@weekly", "2019-11-14 00:00:00", false, "2019-11-17 00:00:00"},
225225
{"@weekly", "2019-11-14 00:00:00", false, "2019-11-17 00:00:00"},
@@ -229,6 +229,8 @@ func testcases() []Case {
229229
{"* * ? * * * */2", "2021-08-20 00:00:00", false, "2022-01-01 00:00:00"},
230230
{"* * * * * * *", "2021-08-20 00:00:00", true, "2021-08-20 00:00:01"},
231231
{"* * * * * * 2023-2099", "2021-08-20 00:00:00", false, "2023-01-01 00:00:00"},
232+
{"30 9 L */3 *", "2023-04-23 09:30:00", false, "2023-04-30 09:30:00"},
233+
{"30 9 L */3 *", "2023-05-01 09:30:00", false, "2023-07-31 09:30:00"},
232234
}
233235
}
234236

next.go

+57-2
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,71 @@ func loop(gron Gronx, segments []string, start time.Time, incl bool, reverse boo
4646
over:
4747
for iter > 0 {
4848
iter--
49+
skipMonthDayForIter := false
4950
for i := 0; i < len(segments); i++ {
5051
pos := len(segments) - 1 - i
51-
seg := segments[len(segments)-1-i]
52+
seg := segments[pos]
53+
isMonthDay, isWeekday := pos == 3, pos == 5
54+
5255
if seg == "*" || seg == "?" {
5356
continue
5457
}
55-
if next, bumped, err = bumpUntilDue(gron.C, seg, pos, next, reverse); bumped {
58+
59+
if !isWeekday {
60+
if isMonthDay && skipMonthDayForIter {
61+
continue
62+
}
63+
if next, bumped, err = bumpUntilDue(gron.C, seg, pos, next, reverse); bumped {
64+
goto over
65+
}
66+
continue
67+
}
68+
// From here we process the weekday segment in case it is neither * nor ?
69+
70+
segIsIntersecting := strings.Index(seg, "*/") == 0
71+
monthDaySeg := segments[3]
72+
monthDaySegIsIntersecting := strings.Index(monthDaySeg, "*") == 0 || monthDaySeg == "?"
73+
74+
intersectCase := segIsIntersecting || monthDaySegIsIntersecting
75+
76+
nextForWeekDay := next
77+
nextForWeekDay, bumped, err = bumpUntilDue(gron.C, seg, pos, nextForWeekDay, reverse)
78+
if !bumped {
79+
// Weekday seg is specific and next is already at right weekday, so no need to process month day if union case
80+
next = nextForWeekDay
81+
if !intersectCase {
82+
skipMonthDayForIter = true
83+
}
84+
continue
85+
}
86+
// Weekday was bumped, so we need to check for month day
87+
88+
if intersectCase {
89+
// We need intersection so we keep bumped weekday and go over
90+
next = nextForWeekDay
5691
goto over
5792
}
93+
// Month day seg is specific and a number/list/range, so we need to check and keep the closest to next
94+
95+
nextForMonthDay := next
96+
nextForMonthDay, bumped, err = bumpUntilDue(gron.C, monthDaySeg, 3, nextForMonthDay, reverse)
97+
98+
monthDayIsClosestToNextThanWeekDay := reverse && nextForMonthDay.After(nextForWeekDay) ||
99+
!reverse && nextForMonthDay.Before(nextForWeekDay)
100+
101+
if monthDayIsClosestToNextThanWeekDay {
102+
next = nextForMonthDay
103+
if !bumped {
104+
// Month day seg is specific and next is already at right month day, we can continue
105+
skipMonthDayForIter = true
106+
continue
107+
}
108+
} else {
109+
next = nextForWeekDay
110+
}
111+
goto over
58112
}
113+
59114
if !incl && next.Format(FullDateFormat) == start.Format(FullDateFormat) {
60115
delta := time.Second
61116
if reverse {

0 commit comments

Comments
 (0)