Learn VisualBasic.NET with Me: is a time within a time span?

This article discusses debugging a function by rewriting the code.

I’m not sure what I did wrong here, but, my first version of this function didn’t work. The function returns true if the current time is withing two time spans. This code was written in a rush, without really thinking about how to do it, because it seemed pretty straightforward. The code, however, was a mess (and embarassing).

    Private Function itIsTimeToWork() As Boolean
        ' Get the two start and end times, and determine if we're within 
        ' the intervals.
        Dim now, start1, start2, end1, end2 As DateTime
        now = DateTime.Now
        start1 = DateTime.Parse(My.Settings.StartWorkAt1)
        end1 = DateTime.Parse(My.Settings.EndWorkAt1)
        If end1 < start1 Then
            end1 = end1.AddDays(1)
        End If
        start2 = DateTime.Parse(My.Settings.StartWorkAt2)
        end2 = DateTime.Parse(My.Settings.EndWorkAt2)
        If (end2 < start2) Then
            end2 = end2.AddDays(1)
        End If
        If (start1 < now) And (now < end1) Then
            itIsTimeToWork = True
            Exit Function
        End If
        If (start2 < now) And (now < end2) Then
            itIsTimeToWork = True
            Exit Function
        End If
        ' If a time interval crosses midnight, and we're on the 
        ' early morning side of the clock, we adjust the interval to go back
        ' in time one day, because we want the ending time to be today, and the
        ' starting time to be yesterday.
        If (start1.AddDays(-1) < now) And (now < end1.AddDays(-1)) Then
            itIsTimeToWork = True
            Exit Function
        End If
        If (start2.AddDays(-1) < now) And (now < end2.AddDays(-1)) Then
            itIsTimeToWork = True
            Exit Function
        End If
        itIsTimeToWork = False
    End Function

Well, that code failed to work when the time went past midnight. I’m not sure why, but the logic was so disjointed that it seemed like a good idea to rewrite it. Here’s the new version, with comments interspersed.

   Private Function itIsTimeToWork() As Boolean
        ' Get the two start and end times, and determine if we're within 
        ' the intervals.
        Dim now As DateTime
        now = DateTime.Now

The function has been refactored into two functions, the second which returns True if the time is within an interval. The logic is now a lot simpler. Not only that, it can be written as a single statement.

        Return (timeIsInInterval(DateTime.Parse(My.Settings.StartWorkAt1), _
                                 DateTime.Parse(My.Settings.EndWorkAt1), now) Or _
                timeIsInInterval(DateTime.Parse(My.Settings.StartWorkAt2), _
                                 DateTime.Parse(My.Settings.EndWorkAt2), now))
    End Function

Surprisingly enough, the logic is pretty simple. At least it is after you’ve thought about it. I had to spend a while playing with the logic to see that it reduced to this. It’s a little bit like doing math — you try different things until you get a nice, tidy statement.

    ' Intended usage is: timeIsInInterval( #8:00 PM#, #2:00 AM#, DateTime.Now )
    ' so that the date is always "today".
    Function timeIsInInterval(ByVal s As DateTime, ByVal e As DateTime, ByVal time As DateTime)

First, isolate the most common, simple situation, and deal with it.

        If (s < e) Then
            If s < time And time < e Then
                Return True
            End If
        Else

Second, deal with the difficult outlier situation. I had to think this out, and originally split it between times between midnight and the starting time before midnight, and the times after midnight, but before the ending time. I coded it that way. Then, I saw that the two situations could be combined with an Or. I also had to verify that the date issues weren’t going to be a problem. After the thinking, I made some test cases and ran the function against the tests.

            ' if the interval is reversed, assume it straddles midnight
            ' and the logic is inverted (the valid ranges are between midnight and the times)
            If s < time Or time < e Then
                Return True
            End If
        End If
        Return False
    End Function

Here’s the complete code. Shorter, better, and factored correctly.

   Private Function itIsTimeToWork() As Boolean
        ' Get the two start and end times, and determine if we're within 
        ' the intervals.
        Dim now As DateTime
        now = DateTime.Now
        Return (timeIsInInterval(DateTime.Parse(My.Settings.StartWorkAt1), _
                                 DateTime.Parse(My.Settings.EndWorkAt1), now) Or _
                timeIsInInterval(DateTime.Parse(My.Settings.StartWorkAt2), _
                                 DateTime.Parse(My.Settings.EndWorkAt2), now))
    End Function
    ' Intended usage is: timeIsInInterval( #8:00 PM#, #2:00 AM#, DateTime.Now )
    ' so that the date is always "today".
    Function timeIsInInterval(ByVal s As DateTime, ByVal e As DateTime, ByVal time As DateTime)
        If (s < e) Then
            If s < time And time < e Then
                Return True
            End If
        Else
            ' if the interval is reversed, assume it straddles midnight
            ' and the logic is inverted (the valid ranges are between midnight and the times)
            If s < time Or time < e Then
                Return True
            End If
        End If
        Return False
    End Function