Checking if current time is in a given interval, GOLANG
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
Francesco
Always loved programming. I believe languages are just tools, programming is the core logic to master.
Updated on June 09, 2022Comments
-
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 about 5 yearsThe last line seems wrong -
inTimeSpan
returnstrue
ifcheck
is beforestart
(and therefore not betweenstart
andend
). -
Kamil Dziedzic about 5 years20:37 is not between 23:00-05:00, should be false (as long as I understand OP question).
-
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 about 5 yearsBut to be honest you can achieve the same with just string comparision instead of time package.
-
Francesco about 5 yearsIt 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