Imports System.Text.RegularExpressions
Public Class CronJob
'the following instance variables are arrays that contain the appropriate values for when this
'particular cronjob can be run. If a "*" was found with no modifier, a single entry of Cron.ANY value
'will be the contents of the array, this indicates that the cronjob can run every minute/hour/day/month/weekday
'as the case may be.
Private _minutes As Byte()
Private _hours As Byte()
Private _days As Byte()
Private _months As Byte()
Private _weekdays As Byte()
'the command to be executed (i.e "php")
Private _command As String
'the command-line arguments to be passed when executing the above command.
Private _args As String()
'original string given when parsing.
Private _cronstring As String
'value that indicates the field is valid at any value. As no proper cron values can be > 59, this
'is a safe value to use. Also, the cron pattern doesn't match more than 2 digits in any single match.
Public Const ANY As Byte = Byte.MaxValue - 1
'pattern that matches a singular part of the cron pattern. At this stage it will match the following formats.
'NB: Assume the dummy w,x,y,z values are integer values between 1 and 2 digits long.
'Any: *
'Any + Modifier: */x
'Lower + Upper: x-y
'Lower + Upper + Modifier: x-y/z
'Explicit Values: w,x,y,z
Public Const CRON_PATTERN As String = "^(?:(?:(?:(?<any>\*)|(?<lower>\d{1,2})-(?<upper>\d{1,2}))(?:/(?<mod>\d{1,2}))?)|(?<explicit>\d{1,2},(?:\d{1,2},)*\d{1,2})|(?<single>\d{1,2}))$"
Private Structure Boundary
Public lower As Byte
Public upper As Byte
Public modifier As Byte
End Structure
Public Shared Function TryParse(ByVal cString As String, ByRef result As CronJob) As Boolean
Dim parsed As CronJob = CronJob.Parse(cString)
If parsed Is Nothing Then
Return False
Else
result = parsed
Return True
End If
End Function
Public Shared Function Parse(ByVal cstring As String) As CronJob
Dim mCollect As MatchCollection = Regex.Matches(cstring, "[^""^\s]+(?=\s*)|""[^""]+""(?=\s*)", RegexOptions.Compiled)
Dim parts As String() = mCollect.Cast(Of Match)().Select(Function(m As Match) m.Value).ToArray()
Dim cronjob As CronJob = Nothing
If parts.Length > 5 Then
cronjob = New CronJob()
cronjob._cronstring = cstring
cronjob._minutes = cronjob.ParseTime(parts(0), 0, 59)
cronjob._hours = cronjob.ParseTime(parts(1), 0, 23)
cronjob._days = cronjob.ParseTime(parts(2), 1, 31)
cronjob._months = cronjob.ParseTime(parts(3), 1, 12)
cronjob._weekdays = cronjob.ParseTime(parts(4), 0, 7)
cronjob._command = parts(5)
If parts.Length > 6 Then
ReDim cronjob._args(parts.Length - 7)
Array.Copy(parts, 6, cronjob.Args, 0, parts.Length - 6)
Else
cronjob._args = New String() {}
End If
If cronjob.NullFields(cronjob) Then cronjob = Nothing
End If
Return cronjob
End Function
Public Function IsRunnable(ByVal time As Date) As Boolean
If CronJob.NullFields(Me) Then
Return False
Else
Return (Me.Minutes(0) = CronJob.ANY OrElse Me.Minutes.Contains(time.Minute)) _
AndAlso (Me.Hours(0) = CronJob.ANY OrElse Me.Hours.Contains(time.Hour)) _
AndAlso (Me.Days(0) = CronJob.ANY OrElse Me.Days.Contains(time.Day)) _
AndAlso (Me.Months(0) = CronJob.ANY OrElse Me.Months.Contains(time.Month)) _
AndAlso (Me.Weekdays(0) = CronJob.ANY OrElse Me.Weekdays.Contains(time.DayOfWeek))
End If
End Function
Private Shared Function GetBoundaries(ByVal data As Match, ByVal lower As Byte, ByVal upper As Byte) As Boundary
Dim bounds As New Boundary() With {.lower = lower, .upper = upper, .modifier = 1}
If data.Groups("mod").Success Then bounds.modifier = Byte.Parse(data.Groups("mod").Value)
If data.Groups("any").Success AndAlso bounds.modifier = 1 Then
bounds.lower = CronJob.ANY
bounds.upper = CronJob.ANY
ElseIf data.Groups("any").Success AndAlso bounds.modifier <> 1 Then
bounds.lower = lower
bounds.upper = upper
ElseIf data.Groups("single").Success Then
bounds.upper = Byte.Parse(data.Groups("single").Value)
bounds.lower = bounds.upper
ElseIf data.Groups("lower").Success AndAlso data.Groups("upper").Success Then
bounds.lower = Byte.Parse(data.Groups("lower").Value)
bounds.upper = Byte.Parse(data.Groups("upper").Value)
Else
bounds.modifier = 0
End If
Return bounds
End Function
Private Shared Function ParseTime(ByVal time As String, ByVal lower As Byte, ByVal upper As Byte) As Byte()
Dim cronRegex As New Regex(CRON_PATTERN, RegexOptions.Compiled)
Dim m As Match = cronRegex.Match(time.Trim())
Dim retval As Byte() = Nothing
If m.Success Then
If m.Groups("explicit").Success Then
Dim parts As String() = m.Groups("explicit").Value.Split(New Char() {","c})
Dim values As Byte() = Array.ConvertAll(Of String, Byte)(parts, Function(s As String) Byte.Parse(s))
If values.All(Function(b As Byte) Not (b > upper OrElse b < lower)) Then
retval = values
End If
Else
Dim bounds As Boundary = CronJob.GetBoundaries(m, lower, upper)
If Not ((bounds.modifier = 0) OrElse (bounds.upper < bounds.lower) OrElse (bounds.lower < lower) OrElse (bounds.upper > upper AndAlso bounds.upper <> CronJob.ANY)) Then
Dim result((bounds.upper - bounds.lower) \ bounds.modifier) As Byte
For i As Integer = bounds.lower To bounds.upper Step bounds.modifier
result((i - bounds.lower) \ bounds.modifier) = i
Next i
retval = result
End If
End If
End If
Return retval
End Function
Private Shared Function NullFields(ByVal cronjob As CronJob) As Boolean
Dim fields As Object() = New Object() {cronjob._args, cronjob._command, _
cronjob._days, cronjob._hours, _
cronjob._minutes, cronjob._months, _
cronjob._weekdays}
Return Not fields.All(Function(o) o IsNot Nothing)
End Function
End Class
Partial Public Class CronJob
Public ReadOnly Property Minutes() As Byte()
Get
Return Me._minutes
End Get
End Property
Public ReadOnly Property Hours() As Byte()
Get
Return Me._hours
End Get
End Property
Public ReadOnly Property Days() As Byte()
Get
Return Me._days
End Get
End Property
Public ReadOnly Property Months() As Byte()
Get
Return Me._months
End Get
End Property
Public ReadOnly Property Weekdays() As Byte()
Get
Return Me._weekdays
End Get
End Property
Public ReadOnly Property Command() As String
Get
Return Me._command
End Get
End Property
Public ReadOnly Property Args() As String()
Get
Return Me._args
End Get
End Property
Public ReadOnly Property CronString As String
Get
Return Me._cronstring
End Get
End Property
End Class