Determine Next Event Using DateTime and TimeSpan

J.C. SolvoTerra 1 Tallied Votes 682 Views Share

I'm currently working on a collection of sub-projects, one of which was a backup manager. I needed to be able to allow the user to specify a backup time and a repeat interval. After thinking about the solution, decided it would be best for the user to provide an Initial DateTime of the first occurance and how frequent they would like to repeat the event. This class can be used to help determaine Alarms, Reminders or anything that requires an event to be triggered at a certain time.

Fun and Good for Beginners

It was good fun writing this wee class and thought it would be a great example for junior developers on using DateTime and TimeSpan to determine things like DateTime manipulation, and the difference between two Dates and Times.

Note: “Event” throughout this snippet is referring to something like an alarm, a calendar reminder or any other action to be performed at a certain date and time.

A Quick Introduction

User Input Required

The Date of the first occurrence
The Time of the First Occurrence
The Repeat Interval

0 = No Repeat
1 = Daily
2 = Weekly
3 = Monthly
4 = Yearly

The “NextEvent” method of the EventManager class then calculates the next occurrence of the event based on today’s date and time.

Returns

the next event date, time and a duration until the next event.

What's happening

Let’s say todays date is: Saturday 4th October 2014 at about 10:30
The user created an event whose first occurrence was on the Thursday 25th Sept 2014 21:00

6c63500f6a372b31f9c7906c4a261e5f

Depending on the users repeat interval their repeat options would be (Excluding no repeat)

Daily = every day at 21:00
Weekly = every Thursday at 21:00
Monthly = 25th of every month at 21:00
Yearly = every 25th of September at 21:00

picb

The repeat events would be as follows

Daily = Today at 21:00
Weekly = Thursday 9th October at 21:00
Monthly = Saturday 25th October at 21:00
Yearly = Friday 25th September 2015 at 21:00

The method also calculates the time of day to make sure if the date of the next event is today, the event time is still in the future, otherwise the next event will occur another interval step in the future (Day, Week, Month, Year).

As you can see from the next image, the user set the first event day time to Saturday 27th September at 21:00 and the repeat is weekly. Because in this example Today is Saturday, the time is referenced. As its only 10:30 and the event doesn’t occur until 21:00 the next event will be today. In a few hours.

a2aa890d9c2e29b8e2a6f79c68ecab8a

Let’s change the event time to a day which matches the current day (Saturday) but move the time to a position before the current time.

7f06645a670cdaedaa1df7ace619e901

As you’d expect, because the time has passed, the next event is now just under a week away on the next repeat interval: Saturday 11th October at 10:15

Using The EventManager Class

Note: EventManager's methods are shared so you don't need to create a new instance before using them.

A new DateTime object is created and populated with the users chosen date and time for their event.

Dim stpSelected As DateTime = New DateTime(START_YEAR, _
                                            START_MONTH, _
                                            START_DAY, _
                                            START_HOUR, _
                                            MINUTE, 0)

The final argument of New DateTime is hard-coded to 0. This is the seconds value, which I do not calculate (This is personal choice, not essential)

The start date and time is then passed is passed to the NextEvent method, along with the repeat value. The repeat value can be set by integer 0 to 5 or by using the enum EventManager.RepeatValues

Dim TV As EventManager.TimeValues = EventManager.NextEvent(stpSelected, REPEAT_VALUE)

A new TimeValues object is created and is populated by NextEvents result.

RepeatValues

    Public Enum RepeatValues
        NoRepeat = 0
        Day = 1 'Repeat Every Day
        Week = 2 'Repeat each week on the selescted DayOfWeek
        Month = 3 'Repeat each month on the selected day
        Year = 4 'repeat each year on the selected date
    End Enum

TimeValues

    Public Class TimeValues
        Public Sub New(NextDate As DateTime, Duration As TimeSpan)

            _NextDate = NextDate
            _Duration = Duration

        End Sub

        Private _NextDate As DateTime
        Public ReadOnly Property NextDate As DateTime
            Get
                Return _NextDate
            End Get
        End Property

        Private _Duration As TimeSpan
        Public ReadOnly Property Duration As TimeSpan
            Get
                Return _Duration
            End Get
        End Property

    End Class

TimeValues is a very simple class which does nothing more than hold a Next Event DateTime and Duration TimeSpan.

Note: The Values are calculated from your current system date and time.

Using The Returned Results

Here is one way to use the returned results:

If Not (IsNothing(TV)) Then

    lblNext.Text = EventManager.VeryLongDate(TV.NextDate) 
    lblDuration.Text = TV.Duration.Days & " days, " & TV.Duration.Hours & "hrs, " & TV.Duration.Minutes & "mins"

Else

    lblNext.Text = "This Event Has Passed"
    lblDuration.Text = "This Event Does Not Repeat"

End If

Because NextEvent returns nothing if the event's StartDate is in the past and the RepeatValue is set to 0 (NoRepeat), we provide the If Not (IsNothing(TV)) Then clause

You may have noticed this method EventManager.VeryLongDate(TV.NextDate). This is a simple method created to return a long-long DateTime value as you can see in the images e.g. "Saturday 4th of October 2014 at 21:00"

EventManager Methods

VeryLongDate

The VeryLongDate method has three arguments, two of which are optional.

  1. DateValue: The DateTime object to be evaluated
  2. IncludeTime: Include the time in the returned string
  3. Time Deliminator: A string that seperates the date string and stime string (can be empty)

    Public Shared Function VeryLongDate(DateValue As DateTime, _
        Optional IncludeTime As Boolean = True, Optional TimeDeliminator As String = " at ") As String
    

For ease of use I get the long values of elements such as DayOfWeek and Month

        Dim _Day As String = DateTime.Parse(DateValue).DayOfWeek.ToString
        Dim _Month As String = MonthName(DateValue.Month)

Using another method SuffixDay I suffix the day value to eg 1 becomes 1st, 22 becomes 22nd etc.

        Dim _Date As String = SuffixDay(DateValue)

And preoduce a nice time string ensuring single digits are preceeded by 0 e.g. 1:9 becomes "01:09"

        Dim _Time As String = Format(DateValue.Hour, "00") & ":" & Format(DateValue.Minute, "00")

Using String.Format we pin our values together. I find String.Format particularly useful as I can change the string format with the greatest of ease.

        If IncludeTime = True Then
            Return String.Format("{0} {1} of {2} {3} {4} {5}", _Day, _Date, _Month, DateValue.Year, TimeDeliminator, _Time)
        Else
            Return String.Format("{0} {1} of {2} {3}", _Day, _Date, _Month, DateValue.Year)
        End If

SuffixDay

    Public Shared Function SuffixDay(DateValue As DateTime) As String

        Dim sDay As String = DateValue.Day.ToString()

        Select Case Strings.Right(sDay, 1)
            Case "1" : sDay += "st"
            Case "2" : sDay += "nd"
            Case "3" : sDay += "rd"
            Case "4" To "9", "0" : sDay += "th"
        End Select

        Return sDay

    End Function

another, nice, simple function whic simply checks the last digit of a number and adds the apropriate suffix eg 1 becomes 1st, 21 becomes 21st and 30 becomes 30th

NextEvent

This is the main method providing the user with the next event DateTime and a TimeSpan until the next event from the current system date and time.

    Public Shared Function NextEvent(StartDateTime As DateTime, Repeat As RepeatValues) As TimeValues

I start by storing the current DateTime and the DateTime of the first events occurance

        'Get current DateTime
        Dim CurrentTime As DateTime = Now

        'Get DateTime of first event occurance
        Dim NextTime As DateTime = StartDateTime

And get the difference between the Current and Next values as a TimeSpan.

        'Get the differance between start and next as a TimeSpan
        Dim ElapsedTime As TimeSpan = NextTime.Subtract(CurrentTime)

        Dim Days As Integer = 1
        Dim Dayn As Integer = 1

TimeSpan will hold an absolute value if the Next event is in the future and a negative value if it's in the past. If the NExt event is in the future, we don't need to process anything, we simply return the next DateTime and the TimeSpan (Duration) until the next event.

        'If the TimeSpan < 0 then the next event would be in the past so...
        If ElapsedTime.Milliseconds < 0 Then

Depending on the users Repeat interval we have to calculate when the next event will happen

            Select Case Repeat

If the user doesn't want this event to repeat, we simply return nothing

                Case 0 'Never

                    'This user doesn't want to repeat this event so let's
                    'return nothing for now.
                    Return Nothing

Daily is pretty easy too, If it's in the past, we'll just set the next event to tomorrow.

                Case 1 'Daily

                    'Add one day on to the next event DateTime (Tomorrow)
                    NextTime = New DateTime(CurrentTime.Year, CurrentTime.Month, _
                                    CurrentTime.AddDays(1).Day, NextTime.Hour, NextTime.Minute, 0)

Weekly is a little more complicated. The repeat may be every Friday, but today is Sunday. In our heads, this is quite simple, so how do we determine this in code.

                Case 2 'Weekly

I record todays DayOfWeek value (Days) and NextTimes DayOfWeek value (Dayn)

                    'Add one week on to the next event DateTime (Next Week)
                    Dayn = NextTime.DayOfWeek
                    Days = CurrentTime.DayOfWeek

The integer values for DayOfWeek Start from 0 (Sunday) to 6 (Saturday). I want Sunday to be the last day of the week, so I switch the value of Sunday (0) to 7. This now let's me work with Monday(1) to Sunday (7

                    'Switch from 0 (Sunday) -> 6 (Saturday) to 1 (Monday) -> 7 (Sunday)
                    If Days = 0 Then Days = 7
                    If Dayn = 0 Then Dayn = 7

DayDiff holds the value differencr between Days and Dayn

                    Dim DayDiff As Integer = 0

If NextDay value is higher then TodaysDay value then the Day Differance is simple, we simply subtract the current day value from the next day value. e.g Today is Wednesday (3) Next is Friday (5)... The difference between Now and Next is 2 (5 - 3)

                    If Math.Abs(0 - Dayn) > Math.Abs(0 - Days) Then
                        DayDiff = Math.Abs((Math.Abs(0 - Days)) - Math.Abs(0 - Dayn))
                    Else

If NextDay value is before todays value I simply get the number of days between today and sunday, then add on the value of the next days value e.g Today is Friday (5) Next is Wed(3). Sunday (7) - Friday (5) = 2. 2 + Next (Wed (3)). So the DayDiff between Friday (5) and Wednesday(3) is 5.

                        DayDiff = (7 - Math.Abs(0 - Days)) + Math.Abs(0 - Dayn)
                    End If

                    'If the event day is today, compare the current
                    'time to the event time

Ok, so If the DayDiff is 7. It means today is the same value of next eg Friday (5). We need to reference the time to see if it's in the past.

                    If DayDiff = 7 Then
                        If NextTime.TimeOfDay > Now.TimeOfDay Then

If the Next Time is still in the future, let's set DayDiff to 0, as the next event will occur today.

                            DayDiff = 0

Otherwise leave it at 7, the enext event will be next week Else

                            DayDiff = 7
                        End If
                    End If

Let's build a new NextTime object, incrementing the number of days as required. Preserve Hour, Minute

                    'Increment number of days until next event
                    NextTime = New DateTime(CurrentTime.Year, CurrentTime.Month, _
                                    CurrentTime.AddDays(DayDiff).Day, NextTime.Hour, NextTime.Minute, 0)

The Monthly repeat requires a little work too.

                Case 3 'Monthly

                    'TODO:Add handler which makes sure if the selected month has more
                    'days than the next event month, the day is switched
                    'to the last day of the month eg 31st = 29th

If the Now day is the same as next day eg 0-31

                    'If the event date is today, check the time
                    If NextTime.Day = CurrentTime.Day Then

We need to check the time.

If the NextTime time is still in the future there's no need to change NextTime

                        If NextTime.TimeOfDay >= Now.TimeOfDay Then
                            'The next event is today
                            NextTime = New DateTime(CurrentTime.Year, CurrentTime.Month, _
                                    NextTime.Day, NextTime.Hour, NextTime.Minute, 0)

If the time has passed we need to set NextTime month foward by one. Preserve Day, Hour, Minute

                        Else
                            'The next event is next month
                            NextTime = New DateTime(CurrentTime.Year, CurrentTime.AddMonths(1).Month, _
                                    NextTime.Day, NextTime.Hour, NextTime.Minute, 0)
                        End If

If the NextTime day is in the past let's set the NextTime month value ahead by one. Preserve Day, Hour, Minute

                    ElseIf NextTime.Day < CurrentTime.Day Then
                        'The next event is next month
                        NextTime = New DateTime(CurrentTime.Year, CurrentTime.AddMonths(1).Month, _
                                    NextTime.Day, NextTime.Hour, NextTime.Minute, 0)

The NextTime month value is in the future so let's leave NextTime it as it is

                    Else
                        'The next event is this month
                        NextTime = New DateTime(CurrentTime.Year, CurrentTime.Month, _
                                    NextTime.Day, NextTime.Hour, NextTime.Minute, 0)
                    End If

Yearly is just as simple as daily, we'll just set the next event to next year. Preserve Month, Day, Hour Minute

                Case 4 'Yearly

                    'Increment event date by 1 year
                    NextTime = New DateTime(CurrentTime.AddYears(1).Year, _
                                    NextTime.Month, NextTime.Day, NextTime.Hour, NextTime.Minute, 0)

            End Select

We now set the elapsed time TimeSpan to calculate the time span between now and the new NextTime DateValue.

            'Re-evaluate the TimeSpan between now and the next occorance of this event
            ElapsedTime = NextTime.Subtract(CurrentTime)

        End If

And return the results to the user.

        Dim TV As TimeValues = New TimeValues(Now + ElapsedTime, ElapsedTime)

        Return TV

    End Function

That's it, all comments stripped away it's a pretty short and simple piece of code.

Note:I'm not much of a mathmatition. The method I used to calculate the DayDiff values may not be the simplest way to get the required result. It does work 100% but you may see a simple equation which does the same.

That's it then, I hope this gives you a wee insite to TimeSpan and DateTime

Reverend Jim commented: Nicely done. +12
Public Class EventManager

    Public Enum RepeatValues
        NoRepeat = 0
        Day = 1 'Repeat Every Day
        Week = 2 'Repeat each week on the selescted DayOfWeek
        Month = 3 'Repeat each month on the selected day
        Year = 4 'repeat each year on the selected date
    End Enum


    Public Class TimeValues
        Public Sub New(NextDate As DateTime, Duration As TimeSpan)

            _NextDate = NextDate
            _Duration = Duration

        End Sub

        Private _NextDate As DateTime
        Public ReadOnly Property NextDate As DateTime
            Get
                Return _NextDate
            End Get
        End Property

        Private _Duration As TimeSpan
        Public ReadOnly Property Duration As TimeSpan
            Get
                Return _Duration
            End Get
        End Property

    End Class

    Public Shared Function VeryLongDate(DateValue As DateTime, Optional IncludeTime As Boolean = True, Optional TimeDeliminator As String = " at ") As String

        Dim _Day As String = DateTime.Parse(DateValue).DayOfWeek.ToString
        Dim _Month As String = MonthName(DateValue.Month)
        Dim _Date As String = SuffixDay(DateValue)

        Dim _Time As String = Format(DateValue.Hour, "00") & ":" & Format(DateValue.Minute, "00")

        If IncludeTime = True Then
            Return String.Format("{0} {1} of {2} {3} {4} {5}", _Day, _Date, _Month, DateValue.Year, TimeDeliminator, _Time)
        Else
            Return String.Format("{0} {1} of {2} {3}", _Day, _Date, _Month, DateValue.Year)
        End If


    End Function

    Public Shared Function SuffixDay(DateValue As DateTime) As String

        Dim sDay As String = DateValue.Day.ToString()

        Select Case Strings.Right(sDay, 1)
            Case "1" : sDay += "st"
            Case "2" : sDay += "nd"
            Case "3" : sDay += "rd"
            Case "4" To "9", "0" : sDay += "th"
        End Select

        Return sDay

    End Function

    Public Shared Function NextEvent(StartDateTime As DateTime, Repeat As RepeatValues) As TimeValues

        'Get current DateTime
        Dim CurrentTime As DateTime = Now
        'Get DateTime of first event occurance
        Dim NextTime As DateTime = StartDateTime
        'Get the differance between start and next as a TimeSpan
        Dim ElapsedTime As TimeSpan = NextTime.Subtract(CurrentTime)

        Dim Days As Integer = 1
        Dim Dayn As Integer = 1

        'If the TimeSpan < 0 then the next event would be in the past so...
        If ElapsedTime.Milliseconds < 0 Then

            'Let's upate our next event so it's in the future based on the
            'users chosen repeat value.
            Select Case Repeat
                Case 0 'Never

                    'This user doesn't want to repeat this event so let's
                    'return nothing for now.
                    Return Nothing

                Case 1 'Daily

                    'Add one day on to the next event DateTime (Tomorrow)
                    NextTime = New DateTime(CurrentTime.Year, CurrentTime.Month, _
                                    CurrentTime.AddDays(1).Day, NextTime.Hour, NextTime.Minute, 0)

                Case 2 'Weekly

                    'Add one week on to the next event DateTime (Next Week)
                    Dayn = NextTime.DayOfWeek
                    Days = CurrentTime.DayOfWeek

                    'Switch from 0 (Sunday) -> 6 (Saturday) to 1 (Monday) -> 7 (Sunday)
                    If Days = 0 Then Days = 7
                    If Dayn = 0 Then Dayn = 7

                    Dim DayDiff As Integer = 0

                    'Get the difference from today to the next event day
                    If Math.Abs(0 - Dayn) > Math.Abs(0 - Days) Then
                        DayDiff = Math.Abs((Math.Abs(0 - Days)) - Math.Abs(0 - Dayn))
                    Else
                        DayDiff = (7 - Math.Abs(0 - Days)) + Math.Abs(0 - Dayn)
                    End If

                    'If the event day is today, compare the current
                    'time to the event time
                    If DayDiff = 7 Then
                        If NextTime.TimeOfDay > Now.TimeOfDay Then
                            DayDiff = 0
                        Else
                            DayDiff = 7
                        End If
                    End If

                    'Increment number of days until next event
                    NextTime = New DateTime(CurrentTime.Year, CurrentTime.Month, _
                                    CurrentTime.AddDays(DayDiff).Day, NextTime.Hour, NextTime.Minute, 0)

                Case 3 'Monthly

                    'TODO:Add handler which makes sure if the selected month has more
                    'days than the next event month, the day is switched
                    'to the last day of the month eg 31st = 29th

                    'If the event date is today, check the time
                    If NextTime.Day = CurrentTime.Day Then

                        'If the Event time is ahead of the current time
                        If NextTime.TimeOfDay >= Now.TimeOfDay Then
                            'The next event is today
                            NextTime = New DateTime(CurrentTime.Year, CurrentTime.Month, _
                                    NextTime.Day, NextTime.Hour, NextTime.Minute, 0)
                        Else
                            'The next event is next month
                            NextTime = New DateTime(CurrentTime.Year, CurrentTime.AddMonths(1).Month, _
                                    NextTime.Day, NextTime.Hour, NextTime.Minute, 0)
                        End If

                        'If todays date is after the event day 
                    ElseIf NextTime.Day < CurrentTime.Day Then
                        'The next event is next month
                        NextTime = New DateTime(CurrentTime.Year, CurrentTime.AddMonths(1).Month, _
                                    NextTime.Day, NextTime.Hour, NextTime.Minute, 0)
                    Else
                        'The next event is this month
                        NextTime = New DateTime(CurrentTime.Year, CurrentTime.Month, _
                                    NextTime.Day, NextTime.Hour, NextTime.Minute, 0)
                    End If

                Case 4 'Yearly

                    'Increment event date by 1 year
                    NextTime = New DateTime(CurrentTime.AddYears(1).Year, _
                                    NextTime.Month, NextTime.Day, NextTime.Hour, NextTime.Minute, 0)

            End Select

            'Re-evaluate the TimeSpan between now and the next occorance of this event
            ElapsedTime = NextTime.Subtract(CurrentTime)

        End If

        Dim TV As TimeValues = New TimeValues(Now + ElapsedTime, ElapsedTime)

        Return TV

    End Function

End Class
Reverend Jim 4,968 Hi, I'm Jim, one of DaniWeb's moderators. Moderator Featured Poster

I'd say this goes way beyond "code snippet" and could easily be a tutorial. Nicely done.

J.C. SolvoTerra 109 Eat, Sleep, Code, Repeat Featured Poster

Thanks Reverend Jim.

Yeah, I got kinda carried away explaining things. Doh!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.