Is there any options in doing date comparison in Go? I have to sort data based on date and time - independently. So I might allow an object that occurs within a range of dates so long as it also occurs within a range of times. In this model, I could not simply just select the oldest date, youngest time/latest date, latest time and Unix() seconds compare them. I'd really appreciate any suggestions.
Ultimately, I wrote a time parsing string compare module to check if a time is within a range. However, this is not faring to well; I've got some gaping issues. I'll post that here just for fun, but I'm hoping there's a better way to time compare.
package main
import (
"strconv"
"strings"
)
func tryIndex(arr []string, index int, def string) string {
if index <= len(arr)-1 {
return arr[index]
}
return def
}
/*
* Takes two strings of format "hh:mm:ss" and compares them.
* Takes a function to compare individual sections (split by ":").
* Note: strings can actually be formatted like "h", "hh", "hh:m",
* "hh:mm", etc. Any missing parts will be added lazily.
*/
func timeCompare(a, b string, compare func(int, int) (bool, bool)) bool {
aArr := strings.Split(a, ":")
bArr := strings.Split(b, ":")
// Catches margins.
if (b == a) {
return true
}
for i := range aArr {
aI, _ := strconv.Atoi(tryIndex(aArr, i, "00"))
bI, _ := strconv.Atoi(tryIndex(bArr, i, "00"))
res, flag := compare(aI, bI)
if res {
return true
} else if flag { // Needed to catch case where a > b and a is the lower limit
return false
}
}
return false
}
func timeGreaterEqual(a, b int) (bool, bool) {return a > b, a < b}
func timeLesserEqual(a, b int) (bool, bool) {return a < b, a > b}
/*
* Returns true for two strings formmated "hh:mm:ss".
* Note: strings can actually be formatted like "h", "hh", "hh:m",
* "hh:mm", etc. Any missing parts will be added lazily.
*/
func withinTime(timeRange, time string) bool {
rArr := strings.Split(timeRange, "-")
if timeCompare(rArr[0], rArr[1], timeLesserEqual) {
afterStart := timeCompare(rArr[0], time, timeLesserEqual)
beforeEnd := timeCompare(rArr[1], time, timeGreaterEqual)
return afterStart && beforeEnd
}
// Catch things like `timeRange := "22:00:00-04:59:59"` which will happen
// with UTC conversions from local time.
// THIS IS THE BROKEN PART I BELIEVE
afterStart := timeCompare(rArr[0], time, timeLesserEqual)
beforeEnd := timeCompare(rArr[1], time, timeGreaterEqual)
return afterStart || beforeEnd
}
So TLDR, I wrote a withinTimeRange(range, time) function but it's not working totally correctly. (In fact, mostly just the second case, where a time range crosses over days is broken. The original part worked, I just realized I'd need to account for that when making conversions to UTC from local.)
If there's a better (preferably built in) way, I'd love to hear about it!
NOTE:
Just as an example, I solved this issue in Javascript with this function:
function withinTime(start, end, time) {
var s = Date.parse("01/01/2011 "+start);
var e = Date.parse("01/0"+(end=="24:00:00"?"2":"1")+"/2011 "+(end=="24:00:00"?"00:00:00":end));
var t = Date.parse("01/01/2011 "+time);
return s <= t && e >= t;
}
However I really want to do this filter server-side.
Use the time package to work with time information in Go.
Time instants can be compared using the Before, After, and Equal
methods. The Sub method subtracts two instants, producing a Duration.
The Add method adds a Time and a Duration, producing a Time.
Play example:
package main
import (
"fmt"
"time"
)
func inTimeSpan(start, end, check time.Time) bool {
return check.After(start) && check.Before(end)
}
func main() {
start, _ := time.Parse(time.RFC822, "01 Jan 15 10:00 UTC")
end, _ := time.Parse(time.RFC822, "01 Jan 16 10:00 UTC")
in, _ := time.Parse(time.RFC822, "01 Jan 15 20:00 UTC")
out, _ := time.Parse(time.RFC822, "01 Jan 17 10:00 UTC")
if inTimeSpan(start, end, in) {
fmt.Println(in, "is between", start, "and", end, ".")
}
if !inTimeSpan(start, end, out) {
fmt.Println(out, "is not between", start, "and", end, ".")
}
}
For comparison between two times use time.Sub()
// utc life
loc, _ := time.LoadLocation("UTC")
// setup a start and end time
createdAt := time.Now().In(loc).Add(1 * time.Hour)
expiresAt := time.Now().In(loc).Add(4 * time.Hour)
// get the diff
diff := expiresAt.Sub(createdAt)
fmt.Printf("Lifespan is %+v", diff)
The program outputs:
Lifespan is 3h0m0s
http://play.golang.org/p/bbxeTtd4L6
For case when your interval's end date doesn't contains hours like
"from 2017-01-01 to whole day of 2017-01-16" it's better to adjust interval's end to midnight of the next day to include all milliseconds like this:
if now.After(start) && now.Before(end.Add(24 * time.Hour).Truncate(24 * time.Hour)) {
...
}
It's possible to compare date using int64 of Unix epoch with seconds granularity. If you need more exact comparison like milisecons or microseconds etc. I guess that
#Oleg Neumyvakin's answer is perfect.
if expirationDate.Unix() > time.Now().Unix() {
...
}
If you're interested in comparing whether a time is close to another for test purposes, you can use testify assert.WithinDuration for this. For example:
expectedTime := time.Now()
actualTime := expectedTime.Add(100*time.Millisecond)
assert.WithinDuration(t, expectedTime, actualTime, 1*time.Second) // pass
assert.WithinDuration(t, expectedTime, actualTime, 1*time.Millisecond) // fail
Otherwise the implementation of assert.WithinDuration can be re-used in your code to determine how close two times are (subtracting one date from the other gives the time difference):
func WithinDuration(expected, actual time.Time, delta time.Duration) bool {
dt := expected.Sub(actual)
return dt >= -delta && dt <= delta
}
Recent protocols prefer usage of RFC3339 per golang time package documentation.
In general RFC1123Z should be used instead of RFC1123 for servers that insist on that format, and RFC3339 should be preferred for new protocols. RFC822, RFC822Z, RFC1123, and RFC1123Z are useful for formatting; when used with time.Parse they do not accept all the time formats permitted by the RFCs.
cutOffTime, _ := time.Parse(time.RFC3339, "2017-08-30T13:35:00Z")
// POSTDATE is a date time field in DB (datastore)
query := datastore.NewQuery("db").Filter("POSTDATE >=", cutOffTime).
As explained in the theread we could use github.com/google/go-cmp/cmp package for dates comparison in tests.
func TestDates(t *testing.T) {
date, _ := time.Parse(time.RFC3339, "2021-11-05T12:00:00+02:00")
dateEqual, _ := time.Parse(time.RFC3339, "2021-11-05T11:00:00+01:00")
dateNotEqual, _ := time.Parse(time.RFC3339, "2021-11-05T12:00:01+02:00")
assertDates(t, date, dateEqual) //pass
assertDates(t, date, dateNotEqual) //fail
}
func assertDates(t *testing.T, expected, actual time.Time) {
t.Helper()
if diff := cmp.Diff(expected, actual); diff != "" {
t.Errorf("mismatch (-expected +actual):\n%s", diff)
}
}
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Hello World")
maxRep := 5
repPeroid := 6
expiry := maxRep * repPeroid
fmt.Println("Expiry: ", expiry)
fmt.Println(time.Now())
CorrIdtime := time.Now().Add(time.Second * time.Duration(expiry)).Format(time.RFC3339)
Notifytime := time.Now().Add(2 * time.Second * time.Duration(expiry)).Format(time.RFC3339)
fmt.Println(CorrIdtime)
fmt.Println(Notifytime)
if CorrIdtime < Notifytime {
fmt.Println("Discarded")
} else {
fmt.Println("Accepted")
}
}
Per proposal time: add Time.Compare and related commit, time.Compare will be added in the new release (Go 1.20)
// Compare compares the time instant t with u. If t is before u, it returns -1;
// if t is after u, it returns +1; if they're the same, it returns 0.
func (t Time) Compare(u Time) int {
Sample
var t1, t2 Time
result := t1.Compare(t2)
Related
I have an API endpoint which will gather some structured data and one of the fields is a time stamp with this format:
"2022-08-30 09:05:27.567995"
My requirement is to caculate the number of days since this timestamp style.
I have this code which is working but I am looking for improvements goplayground:
package main
import (
"fmt"
"math"
"time"
)
func main() {
s1 := "2023-01-20"
date1, _ := time.Parse("2006-01-02", s1)
t1 := time.Now().Round(0)
days := int(math.Ceil(t1.Sub(date1).Hours() / 24))
fmt.Println("days: ", days)
}
I have two questions:
I was not able to find anything in time.Time that recogizes that time format, so I have done string parsing instead, to get just the date (parsing at first white space), which I will insert into the s1 as a variable. That is fine (code not listed here) but I would prefer if time.Time could parse just the date, from that format ("2022-08-30 09:05:27.567995").
Is there a better way to calculate the days since the timestamp, perhaps without having to import the math package? I was somewhat suprised at how difficult this seemed to be because I thought time.Since() would be able to do this, but I was not successful with that, so I came up with this code.
I tried the following with time.Since() that works, as I think, as expected:
package main
import (
"fmt"
"time"
)
func main() {
// this is the input from JSON
// used this as an example for checking calculation in Go playground
// which starts time at 2009-11-10 23:00:00 UTC
timeStr := "2009-11-11 23:00:00.000000"
parseLayout := "2006-01-02 15:04:05.000000"
t, err := time.Parse(parseLayout, timeStr)
if err != nil {
fmt.Printf("Error parsing datetime value %v: %w", timeStr, err)
}
durationDays := int(time.Since(t).Abs().Hours() / 24)
fmt.Println(durationDays)
}
When you use the format string posted by Matteo your time string should be parsed correctly. See https://pkg.go.dev/time#pkg-constants for format string details.
JSON doesn't have date\datetime data type and you would parse from string.
json := "2022-08-30 09:05:27.567995"
t := time.Parse("2006-01-02 15:04:05.999999", json)
You don't need the Math package:
package main
import (
"fmt"
"time"
)
func main() {
s1 := "2023-01-20" // or "2022-08-30 09:05:27.567995"
date1, _ := time.Parse("2006-01-02", s1)
t1 := time.Now()
// Unfortunately there isn't a direct truncation to Date only
y, m, d := t1.Date()
t1 = time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
// now is truncated to Date
days := t1.Sub(date1).Hours() / 24
fmt.Println("days: ", days)
}
EDIT: Extending to your JSON case, you would need truncation to date a lot. You could do something like this:
package main
import (
"fmt"
"time"
)
type myTime time.Time
func main() {
s1 := "2023-01-20 09:05:27.567995" // or "2022-08-30 09:05:27.567995"
date1, _ := time.Parse("2006-01-02 15:04:05.999999", s1)
date1 = myTime(date1).DateOnly()
t1 := myTime(time.Now()).DateOnly()
days := t1.Sub(date1).Hours() / 24
fmt.Println("days: ", days)
}
func (t myTime) DateOnly() time.Time {
y, m, d := time.Time(t).Date()
return time.Date(y, m, d, 0, 0, 0, 0, time.UTC)
}
For the first point of your question, you could parse using the pattern 2006-01-02 15:04:05.999999 like:
package main
import (
"fmt"
"time"
)
func main() {
x := "2022-08-30 09:05:27.567995"
fmt.Println(time.Parse("2006-01-02 15:04:05.999999", x))
}
See https://go.dev/play/p/v4TSXJyNOxg
Here is the code:
type field struct {
name string
}
func print(p *field) {
fmt.Println(p.name)
}
func fix1() {
data := []*field{{name: "one"}, {name: "two"}, {name: "three"}}
for _, v := range data {
go print(v)
}
time.Sleep(time.Millisecond * 200)
}
func wrong1() {
data := []*field{{name: "one"}, {name: "two"}, {name: "three"}}
for _, v := range data {
go func() {
print(v)
}()
}
time.Sleep(time.Millisecond * 200)
}
func main() {
wrong1()
}
As far as I understand, all goroutines in function wrong1 share the same local variable v. At the moment of a goroutine execution, the value of v may be equal to any value in data, therefore the function prints random data three times.
However, I am failing to understand why function fix1 behaves differently (it prints each value in data exactly once).
wrong1(): go func() { print(v) }()
Go: Frequently Asked Questions (FAQ)
What happens with closures running as goroutines?
Some confusion may arise when using closures with concurrency.
Consider the following program:
func main() {
done := make(chan bool)
values := []string{"a", "b", "c"}
for _, v := range values {
go func() {
fmt.Println(v)
done <- true
}()
}
// wait for all goroutines to complete before exiting
for _ = range values {
<-done
}
}
One might mistakenly expect to see a, b, c as the output. What you'll
probably see instead is c, c, c. This is because each iteration of the
loop uses the same instance of the variable v, so each closure shares
that single variable. When the closure runs, it prints the value of v
at the time fmt.Println is executed, but v may have been modified
since the goroutine was launched.
To bind the current value of v to each closure as it is launched, one
must modify the inner loop to create a new variable each iteration.
One way is to pass the variable as an argument to the closure:
for _, v := range values {
go func(u string) {
fmt.Println(u)
done <- true
}(v)
}
In this example, the value of v is passed as an argument to the
anonymous function. That value is then accessible inside the function
as the variable u.
Even easier is just to create a new variable, using a declaration
style that may seem odd but works fine in Go:
for _, v := range values {
v := v // create a new 'v'.
go func() {
fmt.Println(v)
done <- true
}()
}
Your wrong1 example,
for _, v := range data {
go func() {
print(v)
}()
}
Playground: https://play.golang.org/p/0w86nvVMt1g
Output:
three
three
three
Your wrong1 example, creating a new variable,
for _, v := range data {
v := v
go func() {
print(v)
}()
}
Playground: https://play.golang.org/p/z5RCI0ZZU8Z
Output:
one
two
three
Your wrong1 example, passing the variable as an argument,
for _, v := range data {
go func(v *field) {
print(v)
}(v)
}
Playground: https://play.golang.org/p/1JVI7XYSqvv
Output:
one
two
three
fix1(): go print(v)
The Go Programming Language Specification
Calls
Given an expression f of function type F,
f(a1, a2, … an)
calls f with arguments a1, a2, … an. Except for one special case,
arguments must be single-valued expressions assignable to the
parameter types of F and are evaluated before the function is called.
Go statements
The function value and parameters are evaluated as usual in the
calling goroutine.
Your fix1 example, evaluating the value of v before the function is called,
for _, v := range data {
go print(v)
}
Playground: https://play.golang.org/p/rN3UNaGi-ge
Output:
one
two
three
I've got a problem that I couldn't resolve. I'm using https://github.com/kmanley/golang-tuple to create tuples.
I've got a list of minutes:
minutes := int{0, 30} // Minutes are 0 and 30
And a four parameters: start, startBreak, stop, stopBreak:
start := tuple.NewTupleFromItems(9, 30) // It represents "9:30"
startBreak := tuple.NewTupleFromItems(12, 0) // It represents "12:00"
stop := tuple.NewTupleFromItems(21, 0) // It represents "21:00"
stopBreak := tuple.NewTupleFromItems(14, 30) // It represents "14:30"
I want to get a slice of tuples (hour, minutes) using all the minutes in the minutes slice and they must not be included in the range startBreak-stopBreak (it can be equal to startBreak or stopBreak, so the range will become 12:30, 13:00, 13:30, 14:00) and stop-start (it can be equal to start and stop, so the range will become 21:30, 22:00, 22:30, ..., 8:30, 9:00).
For example, using those four parameters, the final result will be:
9:30, 10:00, 10:30, 11:00, 11:30, 12:00, 14:30, 15:00, 15:30, 16:00, 16:30, 17:00, 17:30, 18:00, 18:30, 19:00, 19:30, 20:00, 20:30, 21:00
Here is a minimal code that demonstrates this, I did not put any data validation.
func periods(minutes, start, startBreak, stopBreak, stop *tuple.Tuple) (out []tuple.Tuple) {
// next() moves current to the next minute interval
i := 0
curr := tuple.NewTupleFromItems(start.Get(0), minutes.Get(0))
next := func() {
i = (i + 1) % minutes.Len()
curr.Set(1, minutes.Get(i))
if i == 0 {
curr.Set(0, curr.Get(0).(int)+1)
}
}
for ; curr.Le(stop); next() {
if (curr.Ge(start) && curr.Le(startBreak)) || (curr.Ge(stopBreak) && curr.Le(stop)) {
out = append(out, *curr.Copy())
}
}
return out
}
Playground
Why is the map printing out of order, and how do I get it in to order?
package main
import (
"fmt"
)
type monthsType struct {
no int
text string
}
var months = map[int]string{
1:"January", 2:"Fabruary", 3:"March", 4:"April", 5:"May", 6:"June",
7:"July", 8:"August", 9:"September", 10:"October", 11:"Novenber", 12:"December",
}
func main(){
for no, month := range months {
fmt.Print(no)
fmt.Println("-" + month)
}
}
Prints out:
10-October
7-July
1-January
9-September
4-April
5-May
2-Fabruary
12-December
11-Novenber
6-June
8-August
3-March
Code:
func DemoSortMap() (int, error) {
fmt.Println("use an array to access items by number:")
am := [2]string{"jan", "feb"}
for i, n := range am {
fmt.Printf("%2d: %s\n", i, n)
}
fmt.Println("maps are non-sorted:")
mm := map[int]string{2: "feb", 1: "jan"}
for i, n := range mm {
fmt.Printf("%2d: %s\n", i, n)
}
fmt.Println("access items via sorted list of keys::")
si := make([]int, 0, len(mm))
for i := range mm {
si = append(si, i)
}
sort.Ints(si)
for _, i := range si {
fmt.Printf("%2d: %s\n", i, mm[i])
}
return 0, nil
}
(most of it stolen from M. Summerfield's book)
output:
use an array to access items by number:
0: jan
1: feb
maps are non-sorted:
2: feb
1: jan
access items via sorted list of keys::
1: jan
2: feb
Maps are not sorted so you may use a slice to sort your map. Mark Summerfield's book "Programming in Go" explains this on page 204 and is highly recommended.
This is very late answer, but from what I have read maps are unsorted by design, and are random as one should not rely on the order.
Besides using the sort package together with a second map, one can also use the fmt.Prinln(theMap), which will print the map sorted.
fmt: print maps in key-sorted order
This will print the map typically as follows:
map[key:value
key:value
key:value
]
But this might not be what you want...
By using the fmt.Sprint one can then manipulate the string if needed.
i.e.
s := fmt.Sprint(theMap)
s1 := s[4 : len(s)-1] // remove map[ and ]
fmt.Println(s1)
I'm investigating an issue and ran across some suspicious code involving comparison of Date instances using comparison operators. e.g.
def stamp = ... //Date
def offset = ... //Integer
def d = new Date(stamp.time + offset)
if (d < new Date()) {
...
}
This resource indicates the above is equivalent to the following
def stamp = ... //Date
def offset = ... //Integer
def d = new Date(stamp.time + offset)
if (d.compareTo(new Date()) < 0) {
...
}
However, the GDK documentation on Dates only has examples comparing dates using compareTo, before, and after and I seem to recall specifically avoiding using the comparison operators on Dates due to an experience with unexpected results. Are the above two code examples indeed equivalent (that is, can I safely use comparison operators on Dates in Groovy, or should I only use compareTo, before, and after)?
Thanks!
Well if you plug them into the handy GroovyConsole they have the same result.
If I understand the question correctly:
def stamp = Date.parse("MM/dd/yyyy","02/02/2010")
def offset = 1213123123
def d = new Date(stamp.time+offset)
if(d < new Date() ) {
println "before"
}
if(d.compareTo(new Date()) < 0) {
println "before"
}
Prints "before" twice
If I switched the stamp date to 2011 lets say it would not print.