Loading CHANGELOG.md +4 −0 Original line number Diff line number Diff line Loading @@ -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. Loading internal/cronexpr/cronexpr.go +82 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading internal/cronexpr/cronexpr_next.go +199 −3 Original line number Diff line number Diff line Loading @@ -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]), Loading @@ -56,7 +56,7 @@ 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], Loading @@ -64,6 +64,49 @@ func (expr *Expression) nextYear(t time.Time) time.Time { 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 { Loading Loading @@ -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 { Loading @@ -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 { Loading @@ -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 Loading @@ -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 Loading @@ -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 { Loading internal/cronexpr/cronexpr_test.go +257 −1 Original line number Diff line number Diff line Loading @@ -193,8 +193,264 @@ var crontests = []crontest{ {"2014-08-15 00:00:00", "Fri 2014-08-29 00:00"}, }, }, } var cronbackwardtests = []crontest{ // Seconds { "* * * * * * *", "2006-01-02 15:04:05", []crontimes{ {"2013-01-01 00:00:01", "2013-01-01 00:00:00"}, {"2013-01-01 00:01:00", "2013-01-01 00:00:59"}, {"2013-01-01 01:00:00", "2013-01-01 00:59:59"}, {"2013-01-02 00:00:00", "2013-01-01 23:59:59"}, {"2013-03-01 00:00:00", "2013-02-28 23:59:59"}, {"2016-02-29 00:00:00", "2016-02-28 23:59:59"}, {"2013-01-01 00:00:00", "2012-12-31 23:59:59"}, }, }, // every 5 Second { "*/5 * * * * * *", "2006-01-02 15:04:05", []crontimes{ {"2013-01-01 00:00:06", "2013-01-01 00:00:05"}, {"2013-01-01 00:01:00", "2013-01-01 00:00:55"}, {"2013-01-01 01:00:00", "2013-01-01 00:59:55"}, {"2013-01-02 00:00:00", "2013-01-01 23:59:55"}, {"2013-03-01 00:00:00", "2013-02-28 23:59:55"}, {"2016-02-29 00:00:00", "2016-02-28 23:59:55"}, {"2013-01-01 00:00:00", "2012-12-31 23:59:55"}, }, }, // Minutes { "* * * * *", "2006-01-02 15:04:05", []crontimes{ {"2013-01-01 00:00:58", "2013-01-01 00:00:00"}, {"2013-01-01 00:01:00", "2013-01-01 00:00:00"}, {"2013-01-01 01:00:00", "2013-01-01 00:59:00"}, {"2013-01-02 00:00:00", "2013-01-01 23:59:00"}, {"2013-03-01 00:00:00", "2013-02-28 23:59:00"}, {"2016-02-29 00:00:00", "2016-02-28 23:59:00"}, {"2013-01-01 00:00:00", "2012-12-31 23:59:00"}, }, }, // // Minutes with interval { "17-43/5 * * * *", "2006-01-02 15:04:05", []crontimes{ {"2013-01-01 00:17:01", "2013-01-01 00:17:00"}, {"2013-01-01 00:33:00", "2013-01-01 00:32:00"}, {"2013-01-01 01:00:00", "2013-01-01 00:42:00"}, {"2013-01-02 00:01:00", "2013-01-01 23:42:00"}, {"2013-03-01 00:01:00", "2013-02-28 23:42:00"}, {"2016-02-29 00:01:00", "2016-02-28 23:42:00"}, {"2013-01-01 00:01:00", "2012-12-31 23:42:00"}, }, }, // Minutes interval, list { "15-30/4,55 * * * *", "2006-01-02 15:04:05", []crontimes{ {"2013-01-01 00:16:00", "2013-01-01 00:15:00"}, {"2013-01-01 00:18:59", "2013-01-01 00:15:00"}, {"2013-01-01 00:19:00", "2013-01-01 00:15:00"}, {"2013-01-01 00:56:00", "2013-01-01 00:55:00"}, {"2013-01-01 01:15:00", "2013-01-01 00:55:00"}, {"2013-01-02 00:15:00", "2013-01-01 23:55:00"}, {"2013-03-01 00:15:00", "2013-02-28 23:55:00"}, {"2016-02-29 00:15:00", "2016-02-28 23:55:00"}, {"2012-12-31 23:54:00", "2012-12-31 23:27:00"}, {"2013-01-01 00:15:00", "2012-12-31 23:55:00"}, }, }, // Hour interval { "* 9-19/3 * * *", "2006-01-02 15:04:05", []crontimes{ {"2017-01-01 00:10:00", "2016-12-31 18:59:00"}, {"2017-02-01 00:10:00", "2017-01-31 18:59:00"}, {"2017-02-12 00:10:00", "2017-02-11 18:59:00"}, {"2017-02-12 19:10:00", "2017-02-12 18:59:00"}, {"2017-02-12 12:15:00", "2017-02-12 12:14:00"}, {"2017-02-12 13:00:00", "2017-02-12 12:59:00"}, {"2017-02-12 11:00:00", "2017-02-12 09:59:00"}, }, }, // Hour interval, list { "5 12-21/3,23 * * *", "2006-01-02 15:04:05", []crontimes{ {"2017-01-01 00:10:00", "2016-12-31 23:05:00"}, {"2017-02-01 00:10:00", "2017-01-31 23:05:00"}, {"2017-02-12 00:10:00", "2017-02-11 23:05:00"}, {"2017-02-12 19:10:00", "2017-02-12 18:05:00"}, {"2017-02-12 12:15:00", "2017-02-12 12:05:00"}, {"2017-02-12 22:00:00", "2017-02-12 21:05:00"}, }, }, // Day interval { "5 10-17 12-25/4 * *", "2006-01-02 15:04:05", []crontimes{ {"2017-01-01 00:10:00", "2016-12-24 17:05:00"}, {"2017-02-01 10:10:00", "2017-01-24 17:05:00"}, {"2017-02-27 13:10:00", "2017-02-24 17:05:00"}, {"2017-02-23 13:10:00", "2017-02-20 17:05:00"}, {"2017-02-11 13:10:00", "2017-01-24 17:05:00"}, }, }, // Day interval, list { "* * 12-15,20-22 * *", "2006-01-02 15:04:05", []crontimes{ {"2017-01-01 00:20:00", "2016-12-22 23:59:00"}, {"2017-02-01 10:30:00", "2017-01-22 23:59:00"}, {"2017-02-27 13:40:00", "2017-02-22 23:59:00"}, {"2017-02-17 16:10:00", "2017-02-15 23:59:00"}, {"2017-02-11 13:10:00", "2017-01-22 23:59:00"}, }, }, // Month { "5 10 1 4-6 *", "2006-01-02 15:04:05", []crontimes{ {"2017-01-01 00:10:00", "2016-06-01 10:05:00"}, {"2017-07-01 10:01:00", "2017-06-01 10:05:00"}, {"2017-06-03 00:10:00", "2017-06-01 10:05:00"}, }, }, // TODO: more tests // Month { "0 0 0 12 * * 2017-2020", "2006-01-02 15:04:05", []crontimes{ {"2017-12-11 00:10:00", "2017-11-12 00:00:00"}, {"2023-01-11 00:10:00", "2020-12-12 00:00:00"}, {"2021-01-11 00:10:00", "2020-12-12 00:00:00"}, }, }, // Days of week { "0 0 * * MON", "Mon 2006-01-02 15:04", []crontimes{ {"2013-01-10 00:00:00", "Mon 2013-01-07 00:00"}, {"2017-08-07 00:00:00", "Mon 2017-07-31 00:00"}, {"2017-01-01 00:30:00", "Mon 2016-12-26 00:00"}, }, }, { "0 0 * * friday", "Mon 2006-01-02 15:04", []crontimes{ {"2017-08-14 00:00:00", "Fri 2017-08-11 00:00"}, {"2017-08-02 00:00:00", "Fri 2017-07-28 00:00"}, {"2018-01-02 00:30:00", "Fri 2017-12-29 00:00"}, }, }, { "0 0 * * 6,7", "Mon 2006-01-02 15:04", []crontimes{ {"2017-09-04 00:00:00", "Sun 2017-09-03 00:00"}, {"2017-08-02 00:00:00", "Sun 2017-07-30 00:00"}, {"2018-01-03 00:30:00", "Sun 2017-12-31 00:00"}, }, }, // // Specific days of week { "0 0 * * 6#5", "Mon 2006-01-02 15:04", []crontimes{ {"2017-03-03 00:00:00", "Sat 2016-12-31 00:00"}, }, }, // // Work day of month { "0 0 18W * *", "Mon 2006-01-02 15:04", []crontimes{ {"2017-12-02 00:00:00", "Fri 2017-11-17 00:00"}, {"2017-10-12 00:00:00", "Mon 2017-09-18 00:00"}, {"2017-08-30 00:00:00", "Fri 2017-08-18 00:00"}, {"2017-06-21 00:00:00", "Mon 2017-06-19 00:00"}, }, }, // // Work day of month -- end of month { "0 0 30W * *", "Mon 2006-01-02 15:04", []crontimes{ {"2017-03-02 00:00:00", "Mon 2017-01-30 00:00"}, {"2017-06-02 00:00:00", "Tue 2017-05-30 00:00"}, {"2017-08-02 00:00:00", "Mon 2017-07-31 00:00"}, {"2017-11-02 00:00:00", "Mon 2017-10-30 00:00"}, }, }, // // Last day of month { "0 0 L * *", "Mon 2006-01-02 15:04", []crontimes{ {"2017-01-02 00:00:00", "Sat 2016-12-31 00:00"}, {"2017-02-01 00:00:00", "Tue 2017-01-31 00:00"}, {"2017-03-01 00:00:00", "Tue 2017-02-28 00:00"}, {"2016-03-15 00:00:00", "Mon 2016-02-29 00:00"}, }, }, // // Last work day of month { "0 0 LW * *", "Mon 2006-01-02 15:04", []crontimes{ {"2016-03-02 00:00:00", "Mon 2016-02-29 00:00"}, {"2017-11-02 00:00:00", "Tue 2017-10-31 00:00"}, {"2017-08-15 00:00:00", "Mon 2017-07-31 00:00"}, }, }, } func TestBackwardExpressions(t *testing.T) { for _, test := range cronbackwardtests { for _, times := range test.times { from, _ := time.Parse("2006-01-02 15:04:05", times.from) expr, err := Parse(test.expr) if err != nil { t.Errorf(`cronexpr.Parse("%s") returned "%s"`, test.expr, err.Error()) } last := expr.Last(from) laststr := last.Format(test.layout) if laststr != times.next { t.Errorf(`("%s").Last("%s") = "%s", got "%s"`, test.expr, times.from, times.next, laststr) } } } } func TestExpressions(t *testing.T) { Loading server/core_leaderboard.go +1 −31 Original line number Diff line number Diff line Loading @@ -735,37 +735,7 @@ func calculatePrevReset(currentTime time.Time, startTime int64, resetSchedule *c return 0 } nextResets := resetSchedule.NextN(currentTime, 2) t1 := nextResets[0] t2 := nextResets[1] resetPeriod := t2.Sub(t1) sTime := t1.Add(resetPeriod * -2) // start from twice the period between the next resets back in time nextReset := resetSchedule.Next(currentTime) if nextReset.IsZero() { return 0 } var prevReset time.Time nextResets = resetSchedule.NextN(sTime, 2) for i, r := range nextResets { if r.Equal(nextReset) { if i == 0 { // No prev reset exists, next reset is the first to occur. return 0 } // Prev reset was found. prevReset = nextResets[i-1] break } } if prevReset.IsZero() { return 0 } return prevReset.Unix() return resetSchedule.Last(currentTime).Unix() } func getLeaderboardRecordsHaystack(ctx context.Context, logger *zap.Logger, db *sql.DB, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, ownerID uuid.UUID, limit int, leaderboardId, cursor string, sortOrder int, expiryTime time.Time) (*api.LeaderboardRecordList, error) { Loading Loading
CHANGELOG.md +4 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
internal/cronexpr/cronexpr.go +82 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading
internal/cronexpr/cronexpr_next.go +199 −3 Original line number Diff line number Diff line Loading @@ -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]), Loading @@ -56,7 +56,7 @@ 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], Loading @@ -64,6 +64,49 @@ func (expr *Expression) nextYear(t time.Time) time.Time { 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 { Loading Loading @@ -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 { Loading @@ -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 { Loading @@ -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 Loading @@ -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 Loading @@ -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 { Loading
internal/cronexpr/cronexpr_test.go +257 −1 Original line number Diff line number Diff line Loading @@ -193,8 +193,264 @@ var crontests = []crontest{ {"2014-08-15 00:00:00", "Fri 2014-08-29 00:00"}, }, }, } var cronbackwardtests = []crontest{ // Seconds { "* * * * * * *", "2006-01-02 15:04:05", []crontimes{ {"2013-01-01 00:00:01", "2013-01-01 00:00:00"}, {"2013-01-01 00:01:00", "2013-01-01 00:00:59"}, {"2013-01-01 01:00:00", "2013-01-01 00:59:59"}, {"2013-01-02 00:00:00", "2013-01-01 23:59:59"}, {"2013-03-01 00:00:00", "2013-02-28 23:59:59"}, {"2016-02-29 00:00:00", "2016-02-28 23:59:59"}, {"2013-01-01 00:00:00", "2012-12-31 23:59:59"}, }, }, // every 5 Second { "*/5 * * * * * *", "2006-01-02 15:04:05", []crontimes{ {"2013-01-01 00:00:06", "2013-01-01 00:00:05"}, {"2013-01-01 00:01:00", "2013-01-01 00:00:55"}, {"2013-01-01 01:00:00", "2013-01-01 00:59:55"}, {"2013-01-02 00:00:00", "2013-01-01 23:59:55"}, {"2013-03-01 00:00:00", "2013-02-28 23:59:55"}, {"2016-02-29 00:00:00", "2016-02-28 23:59:55"}, {"2013-01-01 00:00:00", "2012-12-31 23:59:55"}, }, }, // Minutes { "* * * * *", "2006-01-02 15:04:05", []crontimes{ {"2013-01-01 00:00:58", "2013-01-01 00:00:00"}, {"2013-01-01 00:01:00", "2013-01-01 00:00:00"}, {"2013-01-01 01:00:00", "2013-01-01 00:59:00"}, {"2013-01-02 00:00:00", "2013-01-01 23:59:00"}, {"2013-03-01 00:00:00", "2013-02-28 23:59:00"}, {"2016-02-29 00:00:00", "2016-02-28 23:59:00"}, {"2013-01-01 00:00:00", "2012-12-31 23:59:00"}, }, }, // // Minutes with interval { "17-43/5 * * * *", "2006-01-02 15:04:05", []crontimes{ {"2013-01-01 00:17:01", "2013-01-01 00:17:00"}, {"2013-01-01 00:33:00", "2013-01-01 00:32:00"}, {"2013-01-01 01:00:00", "2013-01-01 00:42:00"}, {"2013-01-02 00:01:00", "2013-01-01 23:42:00"}, {"2013-03-01 00:01:00", "2013-02-28 23:42:00"}, {"2016-02-29 00:01:00", "2016-02-28 23:42:00"}, {"2013-01-01 00:01:00", "2012-12-31 23:42:00"}, }, }, // Minutes interval, list { "15-30/4,55 * * * *", "2006-01-02 15:04:05", []crontimes{ {"2013-01-01 00:16:00", "2013-01-01 00:15:00"}, {"2013-01-01 00:18:59", "2013-01-01 00:15:00"}, {"2013-01-01 00:19:00", "2013-01-01 00:15:00"}, {"2013-01-01 00:56:00", "2013-01-01 00:55:00"}, {"2013-01-01 01:15:00", "2013-01-01 00:55:00"}, {"2013-01-02 00:15:00", "2013-01-01 23:55:00"}, {"2013-03-01 00:15:00", "2013-02-28 23:55:00"}, {"2016-02-29 00:15:00", "2016-02-28 23:55:00"}, {"2012-12-31 23:54:00", "2012-12-31 23:27:00"}, {"2013-01-01 00:15:00", "2012-12-31 23:55:00"}, }, }, // Hour interval { "* 9-19/3 * * *", "2006-01-02 15:04:05", []crontimes{ {"2017-01-01 00:10:00", "2016-12-31 18:59:00"}, {"2017-02-01 00:10:00", "2017-01-31 18:59:00"}, {"2017-02-12 00:10:00", "2017-02-11 18:59:00"}, {"2017-02-12 19:10:00", "2017-02-12 18:59:00"}, {"2017-02-12 12:15:00", "2017-02-12 12:14:00"}, {"2017-02-12 13:00:00", "2017-02-12 12:59:00"}, {"2017-02-12 11:00:00", "2017-02-12 09:59:00"}, }, }, // Hour interval, list { "5 12-21/3,23 * * *", "2006-01-02 15:04:05", []crontimes{ {"2017-01-01 00:10:00", "2016-12-31 23:05:00"}, {"2017-02-01 00:10:00", "2017-01-31 23:05:00"}, {"2017-02-12 00:10:00", "2017-02-11 23:05:00"}, {"2017-02-12 19:10:00", "2017-02-12 18:05:00"}, {"2017-02-12 12:15:00", "2017-02-12 12:05:00"}, {"2017-02-12 22:00:00", "2017-02-12 21:05:00"}, }, }, // Day interval { "5 10-17 12-25/4 * *", "2006-01-02 15:04:05", []crontimes{ {"2017-01-01 00:10:00", "2016-12-24 17:05:00"}, {"2017-02-01 10:10:00", "2017-01-24 17:05:00"}, {"2017-02-27 13:10:00", "2017-02-24 17:05:00"}, {"2017-02-23 13:10:00", "2017-02-20 17:05:00"}, {"2017-02-11 13:10:00", "2017-01-24 17:05:00"}, }, }, // Day interval, list { "* * 12-15,20-22 * *", "2006-01-02 15:04:05", []crontimes{ {"2017-01-01 00:20:00", "2016-12-22 23:59:00"}, {"2017-02-01 10:30:00", "2017-01-22 23:59:00"}, {"2017-02-27 13:40:00", "2017-02-22 23:59:00"}, {"2017-02-17 16:10:00", "2017-02-15 23:59:00"}, {"2017-02-11 13:10:00", "2017-01-22 23:59:00"}, }, }, // Month { "5 10 1 4-6 *", "2006-01-02 15:04:05", []crontimes{ {"2017-01-01 00:10:00", "2016-06-01 10:05:00"}, {"2017-07-01 10:01:00", "2017-06-01 10:05:00"}, {"2017-06-03 00:10:00", "2017-06-01 10:05:00"}, }, }, // TODO: more tests // Month { "0 0 0 12 * * 2017-2020", "2006-01-02 15:04:05", []crontimes{ {"2017-12-11 00:10:00", "2017-11-12 00:00:00"}, {"2023-01-11 00:10:00", "2020-12-12 00:00:00"}, {"2021-01-11 00:10:00", "2020-12-12 00:00:00"}, }, }, // Days of week { "0 0 * * MON", "Mon 2006-01-02 15:04", []crontimes{ {"2013-01-10 00:00:00", "Mon 2013-01-07 00:00"}, {"2017-08-07 00:00:00", "Mon 2017-07-31 00:00"}, {"2017-01-01 00:30:00", "Mon 2016-12-26 00:00"}, }, }, { "0 0 * * friday", "Mon 2006-01-02 15:04", []crontimes{ {"2017-08-14 00:00:00", "Fri 2017-08-11 00:00"}, {"2017-08-02 00:00:00", "Fri 2017-07-28 00:00"}, {"2018-01-02 00:30:00", "Fri 2017-12-29 00:00"}, }, }, { "0 0 * * 6,7", "Mon 2006-01-02 15:04", []crontimes{ {"2017-09-04 00:00:00", "Sun 2017-09-03 00:00"}, {"2017-08-02 00:00:00", "Sun 2017-07-30 00:00"}, {"2018-01-03 00:30:00", "Sun 2017-12-31 00:00"}, }, }, // // Specific days of week { "0 0 * * 6#5", "Mon 2006-01-02 15:04", []crontimes{ {"2017-03-03 00:00:00", "Sat 2016-12-31 00:00"}, }, }, // // Work day of month { "0 0 18W * *", "Mon 2006-01-02 15:04", []crontimes{ {"2017-12-02 00:00:00", "Fri 2017-11-17 00:00"}, {"2017-10-12 00:00:00", "Mon 2017-09-18 00:00"}, {"2017-08-30 00:00:00", "Fri 2017-08-18 00:00"}, {"2017-06-21 00:00:00", "Mon 2017-06-19 00:00"}, }, }, // // Work day of month -- end of month { "0 0 30W * *", "Mon 2006-01-02 15:04", []crontimes{ {"2017-03-02 00:00:00", "Mon 2017-01-30 00:00"}, {"2017-06-02 00:00:00", "Tue 2017-05-30 00:00"}, {"2017-08-02 00:00:00", "Mon 2017-07-31 00:00"}, {"2017-11-02 00:00:00", "Mon 2017-10-30 00:00"}, }, }, // // Last day of month { "0 0 L * *", "Mon 2006-01-02 15:04", []crontimes{ {"2017-01-02 00:00:00", "Sat 2016-12-31 00:00"}, {"2017-02-01 00:00:00", "Tue 2017-01-31 00:00"}, {"2017-03-01 00:00:00", "Tue 2017-02-28 00:00"}, {"2016-03-15 00:00:00", "Mon 2016-02-29 00:00"}, }, }, // // Last work day of month { "0 0 LW * *", "Mon 2006-01-02 15:04", []crontimes{ {"2016-03-02 00:00:00", "Mon 2016-02-29 00:00"}, {"2017-11-02 00:00:00", "Tue 2017-10-31 00:00"}, {"2017-08-15 00:00:00", "Mon 2017-07-31 00:00"}, }, }, } func TestBackwardExpressions(t *testing.T) { for _, test := range cronbackwardtests { for _, times := range test.times { from, _ := time.Parse("2006-01-02 15:04:05", times.from) expr, err := Parse(test.expr) if err != nil { t.Errorf(`cronexpr.Parse("%s") returned "%s"`, test.expr, err.Error()) } last := expr.Last(from) laststr := last.Format(test.layout) if laststr != times.next { t.Errorf(`("%s").Last("%s") = "%s", got "%s"`, test.expr, times.from, times.next, laststr) } } } } func TestExpressions(t *testing.T) { Loading
server/core_leaderboard.go +1 −31 Original line number Diff line number Diff line Loading @@ -735,37 +735,7 @@ func calculatePrevReset(currentTime time.Time, startTime int64, resetSchedule *c return 0 } nextResets := resetSchedule.NextN(currentTime, 2) t1 := nextResets[0] t2 := nextResets[1] resetPeriod := t2.Sub(t1) sTime := t1.Add(resetPeriod * -2) // start from twice the period between the next resets back in time nextReset := resetSchedule.Next(currentTime) if nextReset.IsZero() { return 0 } var prevReset time.Time nextResets = resetSchedule.NextN(sTime, 2) for i, r := range nextResets { if r.Equal(nextReset) { if i == 0 { // No prev reset exists, next reset is the first to occur. return 0 } // Prev reset was found. prevReset = nextResets[i-1] break } } if prevReset.IsZero() { return 0 } return prevReset.Unix() return resetSchedule.Last(currentTime).Unix() } func getLeaderboardRecordsHaystack(ctx context.Context, logger *zap.Logger, db *sql.DB, leaderboardCache LeaderboardCache, rankCache LeaderboardRankCache, ownerID uuid.UUID, limit int, leaderboardId, cursor string, sortOrder int, expiryTime time.Time) (*api.LeaderboardRecordList, error) { Loading