# VBForums CodeBank > CodeBank - Visual Basic .NET >  CronJob class. Parsing Included.

## J-Deezy

Well I had the need to write my own internal task scheduler, and I've used the Crontab utility on linux multiple times so I decided to write my own Cron parsing methods.

Error reporting on failed "Parse" operations is pretty much non-existant, I just wrote a catchall (NullFields) to check a cronjob for null fields which would indicate a failed parse. If you wish to make some more in-depth error-reporting, just modify the NullFields function to throw some exceptions with varying messages depending on which field references are Nothing.

As far as I know this will parse all valid Crontab strings into a valid CronJob object. If there's some standard I've missed, feel free to let me know.

I used regular expressions for the parsing, but if anyone has any other, more efficent, methods, I'm all ears.

I used byte-fields instead of Integers to save space, it seemed appropriate seeing as the only valid fields are between 0-59 inclusive, which is well within byte range, and as there could _potentially_ be around 135 total items in all the arrays combined (if someone made a cron entry of the equivalent "* * * * *" but with each item explicitly stated), that's a net memory save of 405 bytes per CronJob object (yeah, I know it's not much, but it can add up if you start getting larger lists)

Suggestions or improvements are always welcome. This was written in the VB 2010 Express Edition IDE targetting .NET 3.5 (if you want to replace the LINQ "contains" extension functions in the IsRunnable() function, you can get the framework down to 2.0 instead, but I was lazy). 

EDIT: According to the Linux MAN pages, the DayOfWeek value can be between 0-7 inclusive, and Sunday is both 0 and 7. In my code I ignore this and accept between 0-6 inclusive simply because fixing it to comply with the MAN page would mean a lot of fiddly hardcoding for such a small improvement.

vb Code:
Imports System.Text.RegularExpressionsPublic 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 FunctionEnd 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 PropertyEnd Class

And an example of how you might use it, though this is a very simple usage.

vb Code:
'The following cron job is set to run on weekdays between 9am-5pm every 2nd hour starting from 9am at 15 minutes past the hour.'It will attempt to run "C:\myjob.exe" with the command line arguments {"argument1", "argument2"}Dim cJob As CronJob = CronJob.Parse("15 9-17/2 * * 1-5 ""C:\myjob.exe"" ""argument1"" ""argument2""")If cJob IsNot Nothing Then    Do        If cJob.IsRunnable(Date.Now)             Dim info As New ProcessStartInfo(cJob.Command, String.Join(" ", cJob.Args))             Dim cProcess As New Process() With { .StartInfo = info }             Try                 cProcess.Start()             Catch ex As Exception                 Debug.WriteLine("Unable to run CRON command: " + ex.Message)             End Try        End If        Threading.Thread.Sleep(60 * 950) 'sleep for approximately 1 minute, but allow 50ms for processing time.    LoopEnd If

----------


## J-Deezy

Any comments?  :Frown:

----------


## Christian A. S.

Thanks for this cool and great code!

----------


## bola777

Hello. Thank you for the code. I will like to extend this to get the next occurrence(s) for a cron. If I have a cron like * 10 5 * * and I will like to know the next occurrence of the cron from today.  I will appreciate any assistance on this. Thank you

----------

