4 people like it.
Like the snippet!
cron scheduling
Parsing cron expression and calculating next launch time from schedule.
v 1.1: bugs fixed for case */n + catch bad parsings
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
|
module CronSchedule
open System
open System.Text.RegularExpressions
let cronRegex =
Regex(@"(?<=^|,)(?<rangeStart>(?:\d+|\*))(?:\-(?<rangeEnd>(?:\d+|\*)))?(?:\/(?<devided>[1-9]\d*))?(?=,|$)")
type CronSchedule = {
minutes: Set<int>
hours: Set<int>
dayOfMonth: Set<int>
months: Set<int>
dayOfWeek: Set<int>
fail: string
original: string
} with
static member Fail(str, original) =
{ minutes = Set.empty; hours = Set.empty; dayOfMonth = Set.empty;
months = Set.empty; dayOfWeek = Set.empty; fail = str; original = original}
member x.IsTime(date: DateTime) =
x.minutes.Contains(date.Minute) &&
x.hours.Contains(date.Hour) &&
x.dayOfMonth.Contains(date.Day) &&
x.months.Contains(date.Month) &&
x.dayOfWeek.Contains(int date.DayOfWeek)
member x.NextTime(startDate: DateTime, ?maxDate: DateTime) =
let maxDate = defaultArg maxDate (startDate.AddYears(1))
let years = [startDate.Year..maxDate.Year]
let startMonthes, monthes =
if Set.isEmpty x.months then
[yield![startDate.Month..12];yield![1..startDate.Month-1]], [1..12]
else x.months |> Set.toList |> fun x -> x,x
let startDaysOfMonth, daysOfMonth =
if Set.isEmpty x.dayOfMonth then
[yield![startDate.Day..31];yield![1..startDate.Day-1]], [1..31]
else x.dayOfMonth |> Set.toList |> fun x -> x,x
let startHours, hours =
if Set.isEmpty x.hours then
[yield![startDate.Hour..23];yield![0..startDate.Hour-1]], [0..23]
else x.hours |> Set.toList |> fun x -> x,x
let startMinutes, minutes =
if Set.isEmpty x.minutes then
[yield![startDate.Minute..59];yield![0..startDate.Minute-1]], [0..59]
else x.minutes |> Set.toList |> fun x -> x,x
let daysOfWeek =
if Set.isEmpty x.dayOfWeek then Set [0..7]
else x.dayOfWeek
let rec search =
function
//no more years to check
| [], _, _, _, _ -> None
//no more monthes to check -> go to next year
| year::avYears, [], _, _, _ ->
search (avYears, monthes, daysOfMonth, hours, minutes)
//no more days of monthes to check -> go to next month
| avYears, month::avMonthes, [], _, _ ->
search (avYears, avMonthes, daysOfMonth, hours, minutes)
// days count in this month year is less than specified day of month --> go to next month
| (year::_ as avYears), month::avMonthes, dayOfMonth::_, _, _
when DateTime.DaysInMonth(year, month) < dayOfMonth ->
search (avYears, avMonthes, daysOfMonth, hours, minutes)
//no more hours -> go to next day
| avYears, avMonthes, dayOfMonth::avDaysOfMonth, [], _ ->
search (avYears, avMonthes, avDaysOfMonth, hours, minutes)
// no more minutes -> go to next hour
| avYears, avMonthes, avDaysOfMonth, hour::avHours, [] ->
search (avYears, avMonthes, avDaysOfMonth, avHours, minutes)
//build date and additional check
| (year::_ as avYears), (month::_ as avMonthes),
(dayOfMonth::otherDaysOfMonth as avDaysOfMonth),
(hour::_ as avHours), minute::avMinutes ->
let date = DateTime(year, month, dayOfMonth, hour, minute, 0)
if date >= startDate && date <= maxDate then
if daysOfWeek.Contains(int date.DayOfWeek) then Some date
else search (avYears, avMonthes, otherDaysOfMonth, hours, minutes)
else search (avYears, avMonthes, avDaysOfMonth, avHours, avMinutes)
search (years, startMonthes, startDaysOfMonth, startHours, startMinutes)
let (|Cron|_|) (rangeStart, rangeEnd) str =
let res = cronRegex.Matches(str)
if res.Count > 0 then
[
for m in res do
let rangeStartGroup = m.Groups.["rangeStart"]
let rangeEndGroup = m.Groups.["rangeEnd"]
let stepGroup = m.Groups.["devided"]
let step =
if stepGroup.Success then
Int32.Parse(stepGroup.Value)
else 1 //defaultStep
match rangeStartGroup.Success, rangeEndGroup.Success with
| true, false when rangeStartGroup.Value = "*" -> ()
| true, false -> yield Int32.Parse(rangeStartGroup.Value)
| true, true when rangeStartGroup.Value = "*" && rangeEndGroup.Value = "*" -> ()
| true, true ->
let rangeStart =
if rangeStartGroup.Value = "*" then rangeStart
else max rangeStart (Int32.Parse(rangeStartGroup.Value))
let rangeEnd =
if rangeEndGroup.Value = "*" then rangeEnd
else min rangeEnd (Int32.Parse(rangeEndGroup.Value))
for i in rangeStart..step..rangeEnd do
yield i
| _, _ -> ()
] |> Set.ofList |> Some
else None
let create(expression: string) =
let parts = expression.Split()
match parts with
| [| Cron (0,59) minutes; Cron (0,23) hours; Cron (1,31) days;
Cron (1,12) months; Cron (0,7) daysOfWeek |] ->
{ minutes = minutes; hours = hours; dayOfMonth = days; months = months;
dayOfWeek = (if Set.contains 7 daysOfWeek then Set.add 0 daysOfWeek else daysOfWeek);
fail = ""; original = expression}
| _ -> CronSchedule.Fail("Wrong expression format. Expression should contain 5 parts", expression)
(*Examples:
let a = CronSchedule.create "40-59/3,15-20,24,28-35/2 11-17 * * 0,3,5"
let b = CronSchedule.create "*/5 * * * *"
let fromTime = DateTime.UtcNow
let nextLaunch = a.NextTime(fromTime)
*)
|
module CronSchedule
namespace System
namespace System.Text
namespace System.Text.RegularExpressions
val cronRegex : Regex
Full name: CronSchedule.cronRegex
Multiple items
type Regex =
new : pattern:string -> Regex + 1 overload
member GetGroupNames : unit -> string[]
member GetGroupNumbers : unit -> int[]
member GroupNameFromNumber : i:int -> string
member GroupNumberFromName : name:string -> int
member IsMatch : input:string -> bool + 1 overload
member Match : input:string -> Match + 2 overloads
member Matches : input:string -> MatchCollection + 1 overload
member Options : RegexOptions
member Replace : input:string * replacement:string -> string + 5 overloads
...
Full name: System.Text.RegularExpressions.Regex
--------------------
Regex(pattern: string) : unit
Regex(pattern: string, options: RegexOptions) : unit
type CronSchedule =
{minutes: Set<int>;
hours: Set<int>;
dayOfMonth: Set<int>;
months: Set<int>;
dayOfWeek: Set<int>;
fail: string;
original: string;}
member IsTime : date:DateTime -> bool
member NextTime : startDate:DateTime * ?maxDate:DateTime -> DateTime option
static member Fail : str:string * original:string -> CronSchedule
Full name: CronSchedule.CronSchedule
CronSchedule.minutes: Set<int>
Multiple items
module Set
from Microsoft.FSharp.Collections
--------------------
type Set<'T (requires comparison)> =
interface IComparable
interface IEnumerable
interface IEnumerable<'T>
interface ICollection<'T>
new : elements:seq<'T> -> Set<'T>
member Add : value:'T -> Set<'T>
member Contains : value:'T -> bool
override Equals : obj -> bool
member IsProperSubsetOf : otherSet:Set<'T> -> bool
member IsProperSupersetOf : otherSet:Set<'T> -> bool
...
Full name: Microsoft.FSharp.Collections.Set<_>
--------------------
new : elements:seq<'T> -> Set<'T>
Multiple items
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
CronSchedule.hours: Set<int>
CronSchedule.dayOfMonth: Set<int>
CronSchedule.months: Set<int>
CronSchedule.dayOfWeek: Set<int>
CronSchedule.fail: string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = String
Full name: Microsoft.FSharp.Core.string
CronSchedule.original: string
static member CronSchedule.Fail : str:string * original:string -> CronSchedule
Full name: CronSchedule.CronSchedule.Fail
val str : string
val original : string
val empty<'T (requires comparison)> : Set<'T> (requires comparison)
Full name: Microsoft.FSharp.Collections.Set.empty
val x : CronSchedule
member CronSchedule.IsTime : date:DateTime -> bool
Full name: CronSchedule.CronSchedule.IsTime
val date : DateTime
Multiple items
type DateTime =
struct
new : ticks:int64 -> DateTime + 10 overloads
member Add : value:TimeSpan -> DateTime
member AddDays : value:float -> DateTime
member AddHours : value:float -> DateTime
member AddMilliseconds : value:float -> DateTime
member AddMinutes : value:float -> DateTime
member AddMonths : months:int -> DateTime
member AddSeconds : value:float -> DateTime
member AddTicks : value:int64 -> DateTime
member AddYears : value:int -> DateTime
...
end
Full name: System.DateTime
--------------------
DateTime()
(+0 other overloads)
DateTime(ticks: int64) : unit
(+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : unit
(+0 other overloads)
member Set.Contains : value:'T -> bool
property DateTime.Minute: int
property DateTime.Hour: int
property DateTime.Day: int
property DateTime.Month: int
property DateTime.DayOfWeek: DayOfWeek
member CronSchedule.NextTime : startDate:DateTime * ?maxDate:DateTime -> DateTime option
Full name: CronSchedule.CronSchedule.NextTime
val startDate : DateTime
val maxDate : DateTime option
val maxDate : DateTime
val defaultArg : arg:'T option -> defaultValue:'T -> 'T
Full name: Microsoft.FSharp.Core.Operators.defaultArg
DateTime.AddYears(value: int) : DateTime
val years : int list
property DateTime.Year: int
val startMonthes : int list
val monthes : int list
val isEmpty : set:Set<'T> -> bool (requires comparison)
Full name: Microsoft.FSharp.Collections.Set.isEmpty
val toList : set:Set<'T> -> 'T list (requires comparison)
Full name: Microsoft.FSharp.Collections.Set.toList
val x : int list
val startDaysOfMonth : int list
val daysOfMonth : int list
val startHours : int list
val hours : int list
val startMinutes : int list
val minutes : int list
val daysOfWeek : Set<int>
val search : (int list * int list * int list * int list * int list -> DateTime option)
union case Option.None: Option<'T>
val year : int
val avYears : int list
val month : int
val avMonthes : int list
val dayOfMonth : int
DateTime.DaysInMonth(year: int, month: int) : int
val avDaysOfMonth : int list
val hour : int
val avHours : int list
val otherDaysOfMonth : int list
val minute : int
val avMinutes : int list
union case Option.Some: Value: 'T -> Option<'T>
val rangeStart : int
val rangeEnd : int
val res : MatchCollection
Regex.Matches(input: string) : MatchCollection
Regex.Matches(input: string, startat: int) : MatchCollection
property MatchCollection.Count: int
val m : Match
val rangeStartGroup : Group
property Match.Groups: GroupCollection
val rangeEndGroup : Group
val stepGroup : Group
val step : int
property Group.Success: bool
type Int32 =
struct
member CompareTo : value:obj -> int + 1 overload
member Equals : obj:obj -> bool + 1 overload
member GetHashCode : unit -> int
member GetTypeCode : unit -> TypeCode
member ToString : unit -> string + 3 overloads
static val MaxValue : int
static val MinValue : int
static member Parse : s:string -> int + 3 overloads
static member TryParse : s:string * result:int -> bool + 1 overload
end
Full name: System.Int32
Int32.Parse(s: string) : int
Int32.Parse(s: string, provider: IFormatProvider) : int
Int32.Parse(s: string, style: Globalization.NumberStyles) : int
Int32.Parse(s: string, style: Globalization.NumberStyles, provider: IFormatProvider) : int
property Capture.Value: string
val max : e1:'T -> e2:'T -> 'T (requires comparison)
Full name: Microsoft.FSharp.Core.Operators.max
val min : e1:'T -> e2:'T -> 'T (requires comparison)
Full name: Microsoft.FSharp.Core.Operators.min
val i : int
val ofList : elements:'T list -> Set<'T> (requires comparison)
Full name: Microsoft.FSharp.Collections.Set.ofList
val create : expression:string -> CronSchedule
Full name: CronSchedule.create
val expression : string
val parts : string []
String.Split([<ParamArray>] separator: char []) : string []
String.Split(separator: string [], options: StringSplitOptions) : string []
String.Split(separator: char [], options: StringSplitOptions) : string []
String.Split(separator: char [], count: int) : string []
String.Split(separator: string [], count: int, options: StringSplitOptions) : string []
String.Split(separator: char [], count: int, options: StringSplitOptions) : string []
active recognizer Cron: int * int -> string -> Set<int> option
Full name: CronSchedule.( |Cron|_| )
val minutes : Set<int>
val hours : Set<int>
val days : Set<int>
val months : Set<int>
val contains : element:'T -> set:Set<'T> -> bool (requires comparison)
Full name: Microsoft.FSharp.Collections.Set.contains
val add : value:'T -> set:Set<'T> -> Set<'T> (requires comparison)
Full name: Microsoft.FSharp.Collections.Set.add
static member CronSchedule.Fail : str:string * original:string -> CronSchedule
More information