Skip to content

Commit

Permalink
use Last() implementation when calculating tournament deadlines (#1084)
Browse files Browse the repository at this point in the history
  • Loading branch information
Luke Gehorsam authored Sep 19, 2023
1 parent 068c8ad commit 81b4c7f
Show file tree
Hide file tree
Showing 7 changed files with 671 additions and 53 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr
### Fixed
- Fixed multiple issues found by linter.

### Fixed
- Fixes calculation of leaderboard and tournament times for rare types of CRON expressions that don't execute at a fixed interval.
- Improved how start and end times are calculated for tournaments occuring in the future.

### [3.17.1] - 2023-08-23
### Added
- Add Satori `recompute` optional input parameter to relevant operations.
Expand Down
82 changes: 82 additions & 0 deletions internal/cronexpr/cronexpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Expression struct {
lastDayOfMonth bool
lastWorkdayOfMonth bool
daysOfMonthRestricted bool
actualDaysOfMonthList []int
monthList []int
daysOfWeek map[int]bool
specificWeekDaysOfWeek map[int]bool
Expand Down Expand Up @@ -233,6 +234,87 @@ func (expr *Expression) Next(fromTime time.Time) time.Time {
return expr.nextSecond(fromTime, actualDaysOfMonthList)
}

// Last returns the closest time instant immediately before `fromTime` which
// matches the cron expression `expr`.
//
// The `time.Location` of the returned time instant is the same as that of
// `fromTime`.
//
// The zero value of time.Time is returned if no matching time instant exists
// or if a `fromTime` is itself a zero value.
func (expr *Expression) Last(fromTime time.Time) time.Time {
// Special case
if fromTime.IsZero() {
return fromTime
}

// year
v := fromTime.Year()
i := sort.SearchInts(expr.yearList, v)
if i == 0 && v != expr.yearList[i] {
return time.Time{}
}
if i == len(expr.yearList) || v != expr.yearList[i] {
return expr.lastYear(fromTime, false)
}
// month
v = int(fromTime.Month())
i = sort.SearchInts(expr.monthList, v)
if i == 0 && v != expr.monthList[i] {
return expr.lastYear(fromTime, true)
}
if i == len(expr.monthList) || v != expr.monthList[i] {
return expr.lastMonth(fromTime, false)
}

expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(fromTime.Year(), int(fromTime.Month()))
if len(expr.actualDaysOfMonthList) == 0 {
return expr.lastMonth(fromTime, true)
}

// day of month
v = fromTime.Day()
i = sort.SearchInts(expr.actualDaysOfMonthList, v)
if i == 0 && v != expr.actualDaysOfMonthList[i] {
return expr.lastMonth(fromTime, true)
}
if i == len(expr.actualDaysOfMonthList) || v != expr.actualDaysOfMonthList[i] {
return expr.lastActualDayOfMonth(fromTime, false)
}
// hour
v = fromTime.Hour()
i = sort.SearchInts(expr.hourList, v)
if i == 0 && v != expr.hourList[i] {
return expr.lastActualDayOfMonth(fromTime, true)
}
if i == len(expr.hourList) || v != expr.hourList[i] {
return expr.lastHour(fromTime, false)
}

// minute
v = fromTime.Minute()
i = sort.SearchInts(expr.minuteList, v)
if i == 0 && v != expr.minuteList[i] {
return expr.lastHour(fromTime, true)
}
if i == len(expr.minuteList) || v != expr.minuteList[i] {
return expr.lastMinute(fromTime, false)
}
// second
v = fromTime.Second()
i = sort.SearchInts(expr.secondList, v)
if i == len(expr.secondList) {
return expr.lastMinute(fromTime, true)
}

// If we reach this point, there is nothing better to do
// than to move to the next second

return expr.lastSecond(fromTime)
}

/******************************************************************************/

/******************************************************************************/

// NextN returns a slice of `n` closest time instants immediately following
Expand Down
202 changes: 199 additions & 3 deletions internal/cronexpr/cronexpr_next.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ func (expr *Expression) nextYear(t time.Time) time.Time {
return time.Time{}
}
// Year changed, need to recalculate actual days of month
actualDaysOfMonthList := expr.calculateActualDaysOfMonth(expr.yearList[i], expr.monthList[0])
if len(actualDaysOfMonthList) == 0 {
expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(expr.yearList[i], expr.monthList[0])
if len(expr.actualDaysOfMonthList) == 0 {
return expr.nextMonth(time.Date(
expr.yearList[i],
time.Month(expr.monthList[0]),
Expand All @@ -56,14 +56,57 @@ func (expr *Expression) nextYear(t time.Time) time.Time {
return time.Date(
expr.yearList[i],
time.Month(expr.monthList[0]),
actualDaysOfMonthList[0],
expr.actualDaysOfMonthList[0],
expr.hourList[0],
expr.minuteList[0],
expr.secondList[0],
0,
t.Location())
}

func (expr *Expression) lastYear(t time.Time, acc bool) time.Time {
// candidate year
v := t.Year()
if acc {
v--
}
i := sort.SearchInts(expr.yearList, v)
var year int
if i < len(expr.yearList) && v == expr.yearList[i] {
year = expr.yearList[i]
} else if i == 0 {
return time.Time{}
} else {
year = expr.yearList[i-1]
}
// Year changed, need to recalculate actual days of month
expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(
year,
expr.monthList[len(expr.monthList)-1])

if len(expr.actualDaysOfMonthList) == 0 {
return expr.lastMonth(time.Date(
year,
time.Month(expr.monthList[len(expr.monthList)-1]),
1,
expr.hourList[len(expr.hourList)-1],
expr.minuteList[len(expr.minuteList)-1],
expr.secondList[len(expr.secondList)-1],
0,
t.Location()), true)
}
return time.Date(
year,
time.Month(expr.monthList[len(expr.monthList)-1]),
expr.actualDaysOfMonthList[len(expr.actualDaysOfMonthList)-1],
expr.hourList[len(expr.hourList)-1],
expr.minuteList[len(expr.minuteList)-1],
expr.secondList[len(expr.secondList)-1],
0,
t.Location())
}

/******************************************************************************/
/******************************************************************************/

func (expr *Expression) nextMonth(t time.Time) time.Time {
Expand Down Expand Up @@ -98,6 +141,48 @@ func (expr *Expression) nextMonth(t time.Time) time.Time {
t.Location())
}

func (expr *Expression) lastMonth(t time.Time, acc bool) time.Time {
// candidate month
v := int(t.Month())
if acc {
v--
}
i := sort.SearchInts(expr.monthList, v)

var month int
if i < len(expr.monthList) && v == expr.monthList[i] {
month = expr.monthList[i]
} else if i == 0 {
return expr.lastYear(t, true)
} else {
month = expr.monthList[i-1]
}

// Month changed, need to recalculate actual days of month
expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(t.Year(), month)
if len(expr.actualDaysOfMonthList) == 0 {
return expr.lastMonth(time.Date(
t.Year(),
time.Month(month),
1,
expr.hourList[len(expr.hourList)-1],
expr.minuteList[len(expr.minuteList)-1],
expr.secondList[len(expr.secondList)-1],
0,
t.Location()), true)
}

return time.Date(
t.Year(),
time.Month(month),
expr.actualDaysOfMonthList[len(expr.actualDaysOfMonthList)-1],
expr.hourList[len(expr.hourList)-1],
expr.minuteList[len(expr.minuteList)-1],
expr.secondList[len(expr.secondList)-1],
0,
t.Location())
}

/******************************************************************************/

func (expr *Expression) nextDayOfMonth(t time.Time, actualDaysOfMonthList []int) time.Time {
Expand All @@ -119,6 +204,34 @@ func (expr *Expression) nextDayOfMonth(t time.Time, actualDaysOfMonthList []int)
t.Location())
}

func (expr *Expression) lastActualDayOfMonth(t time.Time, acc bool) time.Time {
// candidate day of month
v := t.Day()
if acc {
v--
}
i := sort.SearchInts(expr.actualDaysOfMonthList, v)

var day int
if i < len(expr.actualDaysOfMonthList) && v == expr.actualDaysOfMonthList[i] {
day = expr.actualDaysOfMonthList[i]
} else if i == 0 {
return expr.lastMonth(t, true)
} else {
day = expr.actualDaysOfMonthList[i-1]
}

return time.Date(
t.Year(),
t.Month(),
day,
expr.hourList[len(expr.hourList)-1],
expr.minuteList[len(expr.minuteList)-1],
expr.secondList[len(expr.secondList)-1],
0,
t.Location())
}

/******************************************************************************/

func (expr *Expression) nextHour(t time.Time, actualDaysOfMonthList []int) time.Time {
Expand All @@ -142,6 +255,38 @@ func (expr *Expression) nextHour(t time.Time, actualDaysOfMonthList []int) time.

/******************************************************************************/

func (expr *Expression) lastHour(t time.Time, acc bool) time.Time {
// candidate hour
v := t.Hour()
if acc {
v--
}
i := sort.SearchInts(expr.hourList, v)

var hour int
if i < len(expr.hourList) && v == expr.hourList[i] {
hour = expr.hourList[i]
} else if i == 0 {
return expr.lastActualDayOfMonth(t, true)
} else {
hour = expr.hourList[i-1]
}

return time.Date(
t.Year(),
t.Month(),
t.Day(),
hour,
expr.minuteList[len(expr.minuteList)-1],
expr.secondList[len(expr.secondList)-1],
0,
t.Location())
}

/******************************************************************************/

/******************************************************************************/

func (expr *Expression) nextMinute(t time.Time, actualDaysOfMonthList []int) time.Time {
// Find index at which item in list is greater or equal to
// candidate minute
Expand All @@ -163,6 +308,35 @@ func (expr *Expression) nextMinute(t time.Time, actualDaysOfMonthList []int) tim

/******************************************************************************/

func (expr *Expression) lastMinute(t time.Time, acc bool) time.Time {
// candidate minute
v := t.Minute()
if !acc {
v--
}
i := sort.SearchInts(expr.minuteList, v)
var min int
if i < len(expr.minuteList) && v == expr.minuteList[i] {
min = expr.minuteList[i]
} else if i == 0 {
return expr.lastHour(t, true)
} else {
min = expr.minuteList[i-1]
}

return time.Date(
t.Year(),
t.Month(),
t.Day(),
t.Hour(),
min,
expr.secondList[len(expr.secondList)-1],
0,
t.Location())
}

/******************************************************************************/

func (expr *Expression) nextSecond(t time.Time, actualDaysOfMonthList []int) time.Time {
// nextSecond() assumes all other fields are exactly matched
// to the cron expression
Expand All @@ -185,6 +359,28 @@ func (expr *Expression) nextSecond(t time.Time, actualDaysOfMonthList []int) tim
t.Location())
}

/******************************************************************************/
// lastSecond() assumes all other fields are exactly matched
// to the cron expression
func (expr *Expression) lastSecond(t time.Time) time.Time {
// candidate second
v := t.Second() - 1
i := sort.SearchInts(expr.secondList, v)
if i == len(expr.secondList) || expr.secondList[i] != v {
return expr.lastMinute(t, false)
}

return time.Date(
t.Year(),
t.Month(),
t.Day(),
t.Hour(),
t.Minute(),
expr.secondList[i],
0,
t.Location())
}

/******************************************************************************/

func (expr *Expression) calculateActualDaysOfMonth(year, month int) []int {
Expand Down
Loading

0 comments on commit 81b4c7f

Please sign in to comment.