/*! * Copyright 2013 Raymond Hill * * Project: github.com/gorhill/cronexpr * File: cronexpr_next.go * Version: 1.0 * License: pick the one which suits you : * GPL v3 see * APL v2 see * */ package cronexpr /******************************************************************************/ import ( "sort" "time" ) /******************************************************************************/ var dowNormalizedOffsets = [][]int{ {1, 8, 15, 22, 29}, {2, 9, 16, 23, 30}, {3, 10, 17, 24, 31}, {4, 11, 18, 25}, {5, 12, 19, 26}, {6, 13, 20, 27}, {7, 14, 21, 28}, } /******************************************************************************/ func (expr *Expression) nextYear(t time.Time) time.Time { // Find index at which item in list is greater or equal to // candidate year i := sort.SearchInts(expr.yearList, t.Year()+1) if i == len(expr.yearList) { return time.Time{} } // Year changed, need to recalculate actual days of month 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]), 1, expr.hourList[0], expr.minuteList[0], expr.secondList[0], 0, t.Location())) } return time.Date( expr.yearList[i], time.Month(expr.monthList[0]), expr.actualDaysOfMonthList[0], expr.hourList[0], expr.minuteList[0], expr.secondList[0], 0, t.Location()) } /******************************************************************************/ func (expr *Expression) nextMonth(t time.Time) time.Time { // Find index at which item in list is greater or equal to // candidate month i := sort.SearchInts(expr.monthList, int(t.Month())+1) if i == len(expr.monthList) { return expr.nextYear(t) } // Month changed, need to recalculate actual days of month expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(t.Year(), expr.monthList[i]) if len(expr.actualDaysOfMonthList) == 0 { return expr.nextMonth(time.Date( t.Year(), time.Month(expr.monthList[i]), 1, expr.hourList[0], expr.minuteList[0], expr.secondList[0], 0, t.Location())) } return time.Date( t.Year(), time.Month(expr.monthList[i]), expr.actualDaysOfMonthList[0], expr.hourList[0], expr.minuteList[0], expr.secondList[0], 0, t.Location()) } /******************************************************************************/ func (expr *Expression) nextDayOfMonth(t time.Time) time.Time { // Find index at which item in list is greater or equal to // candidate day of month i := sort.SearchInts(expr.actualDaysOfMonthList, t.Day()+1) if i == len(expr.actualDaysOfMonthList) { return expr.nextMonth(t) } return time.Date( t.Year(), t.Month(), expr.actualDaysOfMonthList[i], expr.hourList[0], expr.minuteList[0], expr.secondList[0], 0, t.Location()) } /******************************************************************************/ func (expr *Expression) nextHour(t time.Time) time.Time { // Find index at which item in list is greater or equal to // candidate hour i := sort.SearchInts(expr.hourList, t.Hour()+1) if i == len(expr.hourList) { return expr.nextDayOfMonth(t) } return time.Date( t.Year(), t.Month(), t.Day(), expr.hourList[i], expr.minuteList[0], expr.secondList[0], 0, t.Location()) } /******************************************************************************/ func (expr *Expression) nextMinute(t time.Time) time.Time { // Find index at which item in list is greater or equal to // candidate minute i := sort.SearchInts(expr.minuteList, t.Minute()+1) if i == len(expr.minuteList) { return expr.nextHour(t) } return time.Date( t.Year(), t.Month(), t.Day(), t.Hour(), expr.minuteList[i], expr.secondList[0], 0, t.Location()) } /******************************************************************************/ func (expr *Expression) nextSecond(t time.Time) time.Time { // nextSecond() assumes all other fields are exactly matched // to the cron expression // Find index at which item in list is greater or equal to // candidate second i := sort.SearchInts(expr.secondList, t.Second()+1) if i == len(expr.secondList) { return expr.nextMinute(t) } 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 { actualDaysOfMonthMap := make(map[int]bool) firstDayOfMonth := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC) lastDayOfMonth := firstDayOfMonth.AddDate(0, 1, -1) // As per crontab man page (http://linux.die.net/man/5/crontab#): // "The day of a command's execution can be specified by two // "fields - day of month, and day of week. If both fields are // "restricted (ie, aren't *), the command will be run when // "either field matches the current time" // If both fields are not restricted, all days of the month are a hit if expr.daysOfMonthRestricted == false && expr.daysOfWeekRestricted == false { return genericDefaultList[1 : lastDayOfMonth.Day()+1] } // day-of-month != `*` if expr.daysOfMonthRestricted { // Last day of month if expr.lastDayOfMonth { actualDaysOfMonthMap[lastDayOfMonth.Day()] = true } // Last work day of month if expr.lastWorkdayOfMonth { actualDaysOfMonthMap[workdayOfMonth(lastDayOfMonth, lastDayOfMonth)] = true } // Days of month for v := range expr.daysOfMonth { // Ignore days beyond end of month if v <= lastDayOfMonth.Day() { actualDaysOfMonthMap[v] = true } } // Work days of month // As per Wikipedia: month boundaries are not crossed. for v := range expr.workdaysOfMonth { // Ignore days beyond end of month if v <= lastDayOfMonth.Day() { actualDaysOfMonthMap[workdayOfMonth(firstDayOfMonth.AddDate(0, 0, v-1), lastDayOfMonth)] = true } } } // day-of-week != `*` if expr.daysOfWeekRestricted { // How far first sunday is from first day of month offset := 7 - int(firstDayOfMonth.Weekday()) // days of week // offset : (7 - day_of_week_of_1st_day_of_month) // target : 1 + (7 * week_of_month) + (offset + day_of_week) % 7 for v := range expr.daysOfWeek { w := dowNormalizedOffsets[(offset+v)%7] actualDaysOfMonthMap[w[0]] = true actualDaysOfMonthMap[w[1]] = true actualDaysOfMonthMap[w[2]] = true actualDaysOfMonthMap[w[3]] = true if len(w) > 4 && w[4] <= lastDayOfMonth.Day() { actualDaysOfMonthMap[w[4]] = true } } // days of week of specific week in the month // offset : (7 - day_of_week_of_1st_day_of_month) // target : 1 + (7 * week_of_month) + (offset + day_of_week) % 7 for v := range expr.specificWeekDaysOfWeek { v = 1 + 7*(v/7) + (offset+v)%7 if v <= lastDayOfMonth.Day() { actualDaysOfMonthMap[v] = true } } // Last days of week of the month lastWeekOrigin := firstDayOfMonth.AddDate(0, 1, -7) offset = 7 - int(lastWeekOrigin.Weekday()) for v := range expr.lastWeekDaysOfWeek { v = lastWeekOrigin.Day() + (offset+v)%7 if v <= lastDayOfMonth.Day() { actualDaysOfMonthMap[v] = true } } } return toList(actualDaysOfMonthMap) } func workdayOfMonth(targetDom, lastDom time.Time) int { // If saturday, then friday // If sunday, then monday dom := targetDom.Day() dow := targetDom.Weekday() if dow == time.Saturday { if dom > 1 { dom -= 1 } else { dom += 2 } } else if dow == time.Sunday { if dom < lastDom.Day() { dom += 1 } else { dom -= 2 } } return dom }