Checking if current time is in a given interval, GOLANG

11,328

Solution 1

Here is a clean solution which is immune from time zone issues. In my context, I run a function in a concurrent timer thread and it calls this function every 5 minutes to check the time against the given range.

Note that you should use whole minutes only for this to work. While I haven't tested it, I think the time.Kitchen layout will strip seconds during the time conversions. There is no need to specify seconds as the selector will take every time in the range except the exact end time.

There is no accounting for midnight but I didn't need that functionality. If you wanted that you could test

if end.Before(start) {

if timeNow.After(start) {
   return false 
}

if timeNow.After(end) {
   return true
}
return false
} else {

....
}

Here is my function as it is.

func stringToTime(str string) time.Time {
    tm, err := time.Parse(time.Kitchen, str)
    if err != nil {
        fmt.Println("Failed to decode time:", err)
    }
    fmt.Println("Time decoded:", tm)
    return tm
}

func isInTimeRange() bool {

    startTimeString := "06:00AM"  // "01:00PM"

    endTimeString := "06:05AM"

    t := time.Now()

    zone, offset := t.Zone()

    fmt.Println(t.Format(time.Kitchen), "Zone:", zone, "Offset UTC:", offset)

    timeNowString := t.Format(time.Kitchen)

    fmt.Println("String Time Now: ", timeNowString)

    timeNow := stringToTime(timeNowString)

    start := stringToTime(startTimeString)

    end := stringToTime(endTimeString)

    fmt.Println("Local Time Now: ", timeNow)

    if timeNow.Before(start) {
        return false
    }

    if timeNow.Before(end) {
        return true
    }

    return false
}

Output

What's the time, Mr Wolf?

10:58AM Zone: ACST Offset UTC: 34200

String Time Now: 10:58AM

Time decoded: 0000-01-01 10:58:00 +0000 UTC

Time decoded: 0000-01-01 11:00:00 +0000 UTC

Time decoded: 0000-01-01 11:05:00 +0000 UTC

Local Time Now: 0000-01-01 10:58:00 +0000 UTC

What's the time, Mr Wolf?

11:03AM Zone: ACST Offset UTC: 34200

String Time Now: 11:03AM

Time decoded: 0000-01-01 11:03:00 +0000 UTC

Time decoded: 0000-01-01 11:00:00 +0000 UTC

Time decoded: 0000-01-01 11:05:00 +0000 UTC

Local Time Now: 0000-01-01 11:03:00 +0000 UTC

Time to eat you!

Solution 2

EDIT: It was wrong initially, sorry about that. Fixed version, and also the range now is inclusive. Check also Mickael V. answer as adding time make sense too.

func inTimeSpan(start, end, check time.Time) bool {
    if start.Before(end) {
        return !check.Before(start) && !check.After(end)
    }
    if start.Equal(end) {
        return check.Equal(start)
    }
    return !start.After(check) || !end.Before(check)
}

Check it on playground https://play.golang.org/p/CGWqY2AQ-Th

package main

import (
    "fmt"
    "time"
)

func inTimeSpan(start, end, check time.Time) bool {
    if start.Before(end) {
        return !check.Before(start) && !check.After(end)
    }
    if start.Equal(end) {
        return check.Equal(start)
    }
    return !start.After(check) || !end.Before(check)
}

func main() {
    test := []struct {
        start string
        end   string
        check string
    }{
        {"23:00", "05:00", "04:00"},
        {"23:00", "05:00", "23:30"},
        {"23:00", "05:00", "20:00"},
        {"10:00", "21:00", "11:00"},
        {"10:00", "21:00", "22:00"},
        {"10:00", "21:00", "03:00"},
        // Additional checks.
        {"22:00", "02:00", "00:00"},
        {"10:00", "21:00", "10:00"},
        {"10:00", "21:00", "21:00"},
        {"23:00", "05:00", "06:00"},
        {"23:00", "05:00", "23:00"},
        {"23:00", "05:00", "05:00"},
        {"10:00", "21:00", "10:00"},
        {"10:00", "21:00", "21:00"},
        {"10:00", "10:00", "09:00"},
        {"10:00", "10:00", "11:00"},
        {"10:00", "10:00", "10:00"},
    }
    newLayout := "15:04"
    for _, t := range test {
        check, _ := time.Parse(newLayout, t.check)
        start, _ := time.Parse(newLayout, t.start)
        end, _ := time.Parse(newLayout, t.end)
        fmt.Println(t.start+"-"+t.end, t.check, inTimeSpan(start, end, check))
    }
}

Result:

23:00-05:00 04:00 true
23:00-05:00 23:30 true
23:00-05:00 20:00 false
10:00-21:00 11:00 true
10:00-21:00 22:00 false
10:00-21:00 03:00 false
22:00-02:00 00:00 true
10:00-21:00 10:00 true
10:00-21:00 21:00 true
23:00-05:00 06:00 false
23:00-05:00 23:00 true
23:00-05:00 05:00 true
10:00-21:00 10:00 true
10:00-21:00 21:00 true
10:00-10:00 09:00 false
10:00-10:00 11:00 false
10:00-10:00 10:00 true

Solution 3

For example,

package main

import (
    "fmt"
    "strconv"
    "time"
)

func inTimeSpan(start, end, check time.Time) bool {
    start, end = start.UTC(), end.UTC()
    if start.After(end) {
        start, end = end, start
    }
    check = check.UTC()
    return !check.Before(start) && !check.After(end)
}

func main() {
    now := time.Now()
    newLayout := "15:04"
    ns, _ := time.Parse(newLayout, strconv.Itoa(now.Hour())+":"+strconv.Itoa(now.Minute()))
    srt, _ := time.Parse(newLayout, "23:00")
    end, _ := time.Parse(newLayout, "05:00")

    fmt.Println(srt, end)
    fmt.Println(ns)
    fmt.Println("1 : ", inTimeSpan(srt, end, ns))
}

Output:

0000-01-01 23:00:00 +0000 UTC 0000-01-01 05:00:00 +0000 UTC
0000-01-01 20:37:00 +0000 UTC
1 :  true

Solution 4

Building on Kamil's answer, which is ultimately wrong as its own tests show, here is a solution that makes use only of Go's time interface. The trick, knowing that Go will make Before and After comparison using time AND date, is to just push the end time to the next day if its time component is "smaller" than the start one. Then the time to check needs to be done the same if it's before the start time.

I took the same test cases and added some significant ones like midnight.

package main

import (
    "fmt"
    "time"
)

func inTimeSpan(start, end, check time.Time) bool {
    _end := end
    _check := check
    if end.Before(start) {
        _end = end.Add(24 * time.Hour)
        if check.Before(start) {
            _check = check.Add(24 * time.Hour)
        }
    }
    return _check.After(start) && _check.Before(_end)
}

func main() {
    test := []struct {
        start string
        end   string
        check string
    }{
        {"23:00", "05:00", "04:00"},
        {"23:00", "05:00", "23:30"},
        {"23:00", "05:00", "20:00"},
        {"10:00", "21:00", "11:00"},
        {"10:00", "21:00", "22:00"},
        {"10:00", "21:00", "03:00"},
        {"22:00", "02:00", "00:00"},
        {"10:00", "21:00", "10:00"},
        {"10:00", "21:00", "21:00"},
    }
    newLayout := "15:04"
    for _, t := range test {
        check, _ := time.Parse(newLayout, t.check)
        start, _ := time.Parse(newLayout, t.start)
        end, _ := time.Parse(newLayout, t.end)
        fmt.Println(t.start+"-"+t.end, t.check, inTimeSpan(start, end, check))
    }
}

Output :

23:00-05:00 04:00 true
23:00-05:00 23:30 true
23:00-05:00 20:00 false
10:00-21:00 11:00 true
10:00-21:00 22:00 false
10:00-21:00 03:00 false
22:00-02:00 00:00 true
10:00-21:00 10:00 false
10:00-21:00 21:00 false

You will notice on the two last cases that the check is not inclusive for the start time, nor the end time. If you need to change that behavior, you can remediate this by changing the function a little :

func inTimeSpanEx(start, end, check time.Time, includeStart, includeEnd bool) bool {
    _start := start
    _end := end
    _check := check
    if end.Before(start) {
        _end = end.Add(24 * time.Hour)
        if check.Before(start) {
            _check = check.Add(24 * time.Hour)
        }
    }

    if includeStart {
    _start = _start.Add(-1 * time.Nanosecond)
    }
    if includeEnd {
    _end = _end.Add(1 * time.Nanosecond)
    }

    return _check.After(_start) && _check.Before(_end)
}

Extract of the output when calling with true for inclusion of start and end :

10:00-21:00 10:00 true
10:00-21:00 21:00 true

Complete playground here : https://play.golang.org/p/8ig34gmHl71

Share:
11,328
Francesco
Author by

Francesco

Always loved programming. I believe languages are just tools, programming is the core logic to master.

Updated on June 09, 2022

Comments

  • Francesco
    Francesco almost 2 years

    I am trying to find a way to check if the current time is in a given interval, where start and end are given (eventually) by a user.

    I have been trying by using the After and Before from the Time package after making sure all times are in UTC, but clearly I am doing something wrong.

    The code looks similar to this example:

    func inTimeSpan(start, end, check time.Time) bool {
        return check.After(start) && check.Before(end)
    }
    
    func main() {
    
        now := time.Now()
        newLayout := "15:04"
        ns, _ := time.Parse(newLayout, strconv.Itoa(now.Hour())+":"+strconv.Itoa(now.Minute()))
        srt, _ := time.Parse(newLayout, "23:00")
        end, _ := time.Parse(newLayout, "05:00")
    
        fmt.Println("1 : ", inTimeSpan(srt, end, ns))
    }
    

    Any help is appreciated.

  • Adrian
    Adrian about 5 years
    The last line seems wrong - inTimeSpan returns true if check is before start (and therefore not between start and end).
  • Kamil Dziedzic
    Kamil Dziedzic about 5 years
    20:37 is not between 23:00-05:00, should be false (as long as I understand OP question).
  • Kamil Dziedzic
    Kamil Dziedzic about 5 years
    @Adrian No, that's correct and intentional (unless I misunderstood OP). You have range from 23:00 to 05:00 but there is no date involved. That's the original issue with the code. OP assume 04:00 is between 23:00-05:00 which is correct but time package understands 23:00 as today, and 05:00 as today (instead of next day). If time.Parse would use date you would be correct but then there wouldn't be a problem with comparing times in the first place.
  • Kamil Dziedzic
    Kamil Dziedzic about 5 years
    But to be honest you can achieve the same with just string comparision instead of time package.
  • Francesco
    Francesco about 5 years
    It is actually not correct, I did realized but forgot to write it. The examples results should have been true, true, false, true, false, false. Anyhow your solution was accepted because it basically helped me solving my problem. Thanks a lot. I also see my original thanks reply is not there …. sorry