cronexpr/cronexpr.go

265 lines
7.0 KiB
Go
Raw Normal View History

2013-08-29 13:03:10 +00:00
/*!
* Copyright 2013 Raymond Hill
*
2013-08-31 13:00:12 +00:00
* Project: github.com/gorhill/cronexpr
* File: cronexpr.go
2013-08-29 13:03:10 +00:00
* Version: 1.0
* License: GPL v3 see <https://www.gnu.org/licenses/gpl.html>
*
*/
2013-08-31 13:00:12 +00:00
// Package cronexpr parses cron time expressions.
package cronexpr
2013-08-29 13:03:10 +00:00
/******************************************************************************/
import (
2013-09-07 18:02:17 +00:00
"fmt"
2013-08-30 15:09:36 +00:00
"sort"
"time"
2013-08-29 13:03:10 +00:00
)
/******************************************************************************/
2013-08-31 13:07:23 +00:00
// A Expression represents a specific cron time expression as defined at
// <https://github.com/gorhill/cronexpr#implementation>
type Expression struct {
2013-08-30 15:40:02 +00:00
expression string
2013-08-30 15:09:36 +00:00
secondList []int
minuteList []int
hourList []int
daysOfMonth map[int]bool
workdaysOfMonth map[int]bool
lastDayOfMonth bool
2013-09-07 18:02:17 +00:00
lastWorkdayOfMonth bool
2013-08-30 15:09:36 +00:00
daysOfMonthRestricted bool
actualDaysOfMonthList []int
monthList []int
daysOfWeek map[int]bool
2013-09-07 18:02:17 +00:00
specificWeekDaysOfWeek map[int]bool
lastWeekDaysOfWeek map[int]bool
2013-08-30 15:09:36 +00:00
daysOfWeekRestricted bool
yearList []int
2013-08-29 13:03:10 +00:00
}
/******************************************************************************/
2013-09-02 13:25:49 +00:00
// MustParse returns a new Expression pointer. It expects a well-formed cron
// expression. If a malformed cron expression is supplied, it will `panic`.
// See <https://github.com/gorhill/cronexpr#implementation> for documentation
// about what is a well-formed cron expression from this library's point of
// view.
func MustParse(cronLine string) *Expression {
2013-09-07 18:02:17 +00:00
expr, err := Parse(cronLine)
if err != nil {
panic(err)
}
return expr
}
/******************************************************************************/
// Parse returns a new Expression pointer. An error is returned if a malformed
// cron expression is supplied.
// See <https://github.com/gorhill/cronexpr#implementation> for documentation
// about what is a well-formed cron expression from this library's point of
// view.
func Parse(cronLine string) (*Expression, error) {
2013-08-30 15:09:36 +00:00
2013-09-07 18:02:17 +00:00
// Maybe one of the built-in aliases is being used
cron := cronNormalizer.Replace(cronLine)
indices := fieldFinder.FindAllStringIndex(cron, -1)
2013-09-08 14:37:12 +00:00
fieldCount := len(indices)
if fieldCount < 5 {
2013-09-07 19:45:21 +00:00
return nil, fmt.Errorf("missing field(s)")
2013-09-07 18:02:17 +00:00
}
2013-09-08 14:37:12 +00:00
// ignore fields beyond 7th
if fieldCount > 7 {
fieldCount = 7
}
2013-09-07 18:02:17 +00:00
2013-09-08 14:37:12 +00:00
var expr = Expression{}
var field = 0
var err error
2013-09-07 18:02:17 +00:00
// second field (optional)
2013-09-08 14:37:12 +00:00
if fieldCount == 7 {
err = expr.secondFieldHandler(cron[indices[field][0]:indices[field][1]])
2013-09-07 18:02:17 +00:00
if err != nil {
return nil, err
}
field += 1
} else {
expr.secondList = []int{0}
}
// minute field
2013-09-08 14:37:12 +00:00
err = expr.minuteFieldHandler(cron[indices[field][0]:indices[field][1]])
2013-09-07 18:02:17 +00:00
if err != nil {
return nil, err
}
field += 1
2013-08-30 15:09:36 +00:00
2013-09-07 18:02:17 +00:00
// hour field
err = expr.hourFieldHandler(cron[indices[field][0]:indices[field][1]])
if err != nil {
return nil, err
2013-08-30 15:09:36 +00:00
}
2013-09-07 18:02:17 +00:00
field += 1
// day of month field
err = expr.domFieldHandler(cron[indices[field][0]:indices[field][1]])
if err != nil {
return nil, err
2013-08-30 15:09:36 +00:00
}
2013-09-07 18:02:17 +00:00
field += 1
// month field
err = expr.monthFieldHandler(cron[indices[field][0]:indices[field][1]])
if err != nil {
return nil, err
2013-08-30 15:09:36 +00:00
}
2013-09-07 18:02:17 +00:00
field += 1
2013-08-30 15:09:36 +00:00
2013-09-07 18:02:17 +00:00
// day of week field
err = expr.dowFieldHandler(cron[indices[field][0]:indices[field][1]])
if err != nil {
return nil, err
2013-08-30 15:09:36 +00:00
}
2013-09-07 18:02:17 +00:00
field += 1
2013-08-30 15:09:36 +00:00
2013-09-07 18:02:17 +00:00
// year field
2013-09-08 14:37:12 +00:00
if field < fieldCount {
2013-09-07 18:02:17 +00:00
err = expr.yearFieldHandler(cron[indices[field][0]:indices[field][1]])
if err != nil {
return nil, err
}
} else {
expr.yearList = yearDescriptor.defaultList
}
2013-08-30 15:09:36 +00:00
2013-09-07 18:02:17 +00:00
return &expr, nil
2013-08-29 13:03:10 +00:00
}
/******************************************************************************/
// Next returns the closest time instant immediately following `fromTime` which
// matches the cron expression `expr`.
2013-08-31 13:00:12 +00:00
//
// 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) Next(fromTime time.Time) time.Time {
2013-08-30 15:09:36 +00:00
// Special case
if fromTime.IsZero() {
2013-08-30 15:09:36 +00:00
return fromTime
}
// Since expr.nextSecond()-expr.nextMonth() expects that the
2013-08-30 15:09:36 +00:00
// supplied time stamp is a perfect match to the underlying cron
// expression, and since this function is an entry point where `fromTime`
// does not necessarily matches the underlying cron expression,
// we first need to ensure supplied time stamp matches
// the cron expression. If not, this means the supplied time
// stamp falls in between matching time stamps, thus we move
// to closest future matching immediately upon encountering a mismatching
// time stamp.
// year
v := fromTime.Year()
i := sort.SearchInts(expr.yearList, v)
if i == len(expr.yearList) {
return time.Time{}
2013-08-30 15:09:36 +00:00
}
if v != expr.yearList[i] {
return expr.nextYear(fromTime)
2013-08-30 15:09:36 +00:00
}
// month
v = int(fromTime.Month())
i = sort.SearchInts(expr.monthList, v)
if i == len(expr.monthList) {
return expr.nextYear(fromTime)
2013-08-30 15:09:36 +00:00
}
if v != expr.monthList[i] {
return expr.nextMonth(fromTime)
2013-08-30 15:09:36 +00:00
}
expr.actualDaysOfMonthList = expr.calculateActualDaysOfMonth(fromTime.Year(), int(fromTime.Month()))
if len(expr.actualDaysOfMonthList) == 0 {
return expr.nextMonth(fromTime)
2013-08-30 15:09:36 +00:00
}
// day of month
v = fromTime.Day()
i = sort.SearchInts(expr.actualDaysOfMonthList, v)
if i == len(expr.actualDaysOfMonthList) {
return expr.nextMonth(fromTime)
2013-08-30 15:09:36 +00:00
}
if v != expr.actualDaysOfMonthList[i] {
return expr.nextDayOfMonth(fromTime)
2013-08-30 15:09:36 +00:00
}
// hour
v = fromTime.Hour()
i = sort.SearchInts(expr.hourList, v)
if i == len(expr.hourList) {
return expr.nextDayOfMonth(fromTime)
2013-08-30 15:09:36 +00:00
}
if v != expr.hourList[i] {
return expr.nextHour(fromTime)
2013-08-30 15:09:36 +00:00
}
// minute
v = fromTime.Minute()
i = sort.SearchInts(expr.minuteList, v)
if i == len(expr.minuteList) {
return expr.nextHour(fromTime)
2013-08-30 15:09:36 +00:00
}
if v != expr.minuteList[i] {
return expr.nextMinute(fromTime)
2013-08-30 15:09:36 +00:00
}
// second
v = fromTime.Second()
i = sort.SearchInts(expr.secondList, v)
if i == len(expr.secondList) {
return expr.nextMinute(fromTime)
2013-08-30 15:09:36 +00:00
}
// If we reach this point, there is nothing better to do
// than to move to the next second
return expr.nextSecond(fromTime)
2013-08-29 13:03:10 +00:00
}
/******************************************************************************/
// NextN returns a slice of `n` closest time instants immediately following
// `fromTime` which match the cron expression `expr`.
2013-08-31 13:00:12 +00:00
//
// The time instants in the returned slice are in chronological ascending order.
// The `time.Location` of the returned time instants is the same as that of
// `fromTime`.
//
// A slice with len between [0-`n`] is returned, that is, if not enough existing
// matching time instants exist, the number of returned entries will be less
// than `n`.
func (expr *Expression) NextN(fromTime time.Time, n uint) []time.Time {
2013-09-07 18:02:17 +00:00
nextTimes := make([]time.Time, 0, n)
if n > 0 {
fromTime = expr.Next(fromTime)
for {
if fromTime.IsZero() {
break
}
nextTimes = append(nextTimes, fromTime)
n -= 1
if n == 0 {
break
}
fromTime = expr.nextSecond(fromTime)
2013-08-30 15:09:36 +00:00
}
}
return nextTimes
2013-08-29 13:03:10 +00:00
}