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
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
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.
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.
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.
- DateValue: The DateTime object to be evaluated
- IncludeTime: Include the time in the returned string
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