How should I format a date to always respect the specified format? [duplicate] - datetime

This question already has answers here:
Go time.Time.UTC() sometimes gives 7 digits, sometimes 9
(1 answer)
prevent json.Marshal time.Time removing trailing zeros
(1 answer)
Closed 2 months ago.
I have a Go API that is supposed to return a timestamp in the following format (Java notation but should be understandable):
yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'
My Go API will build this timestamp as follows:
func ParseToTimestamp(duration string, systemTime time.Time) (string, error) {
parsedDuration, err := time.ParseDuration(duration)
if err != nil {
return "", err
}
validity := systemTime.Add(parsedDuration)
return validity.Format("2006-01-02T15:04:05.999999Z"), nil
}
I've noticed, however, that this formatted string is not always respecting the wished format. For example, if I pass a date which has 0 nano-seconds:
duration := "10m"
systemTime := time.Date(1990, time.January, 24, 0, 0, 0, 0, time.UTC) //no nano-seconds
timestamp, _ := fxcash.ParseToTimestamp(duration, systemTime)
fmt.Println(timestamp)
>> 1990-01-24T00:10:00Z
However, if I pass a date which has 120k nano-seconds:
duration := "10m"
systemTime := time.Date(1990, time.January, 24, 0, 0, 0, 120000, time.UTC) //120k nano-seconds
timestamp, _ := fxcash.ParseToTimestamp(duration, systemTime)
fmt.Println(timestamp)
>> 1990-01-24T00:10:00.00012Z
Generally speaking, this causes an issue because the service that consumes my Go output expects to have exactly 6 nano-seconds, even if they were only zeros .000000Z.
Is there a way to do that without heavy parsing/ugly splitting using standard Go Libraries?
Sorry if this question is basic but I'm very new to Go, I've looked for examples on the web but couldn't find any.

Related

get time in days from random date in Go

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

Is this the best way of getting tomorrow 09:00? [duplicate]

This question already has an answer here:
How to get specific time from next day
(1 answer)
Closed 2 years ago.
I want to get a datetime object for tomorrow morning 09:00 in Go. My current take is this:
now := time.Now()
tomorrowMorning := time.Date(now.Year(), now.Month(), now.Day(), 9, 0, 0, 0, time.UTC).AddDate(0, 0, 1))
It seems oddly verbose though. Isn't there a simpler way of doing this?
Simplify by adding 1 to the day directly.
now := time.Now()
tomorrowMorning := time.Date(now.Year(), now.Month(), now.Day() + 1, 9, 0, 0, 0, time.UTC)
Run it on the playground.
The time.Date function normalizes the day.

Go time comparison

I'm trying to create simple function just to change time zone of a time to another (Lets assume UTC to +0700 WIB). Here is the source code. I have 2 functions, first GenerateWIB which will change just your time zone into +0700 WIB with same datetime. Second is GenerateUTC which will change given time's timezone into UTC. GenerateUTC works perfectly while another is not.
expect := time.Date(2016, 12, 12, 1, 2, 3, 4, wib)
t1 := time.Date(2016, 12, 12, 1, 2, 3, 4, time.UTC)
res := GenerateWIB(t1)
if res != expect {
fmt.Printf("WIB Expect %+v, but get %+v", expect, res)
}
The res != expect always fullfilled with this result.
WIB Expect 2016-12-12 01:02:03.000000004 +0700 WIB, but get 2016-12-12 01:02:03.000000004 +0700 WIB
But it is the same time right? Did i miss something?
There is an .Equal() method to compare dates :
if !res.Equal(expect) {
...
Quoting the doc :
Note that the Go == operator compares not just the time instant but also the Location and the monotonic clock reading. Therefore, Time values should not be used as map or database keys without first guaranteeing that the identical Location has been set for all values, which can be achieved through use of the UTC or Local method, and that the monotonic clock reading has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u) to t == u, since t.Equal uses the most accurate comparison available and correctly handles the case when only one of its arguments has a monotonic clock reading.
If you look at the code for the time.Time(*) struct, you can see that this struct has three private fields :
type Time struct {
...
wall uint64
ext int64
...
loc *Location
}
and the comments about those fields clearly indicate that, depending on how the Time struct was built, two Time describing the same point in time may have different values for these fields.
Running res == expect compares the values of these inner fields,
running res.Equal(expect) tries to do the thing you expect.
(*) time/time.go source code on master branch as of oct 27th, 2020
Dates in golang must be compared with Equal method. Method Date returns Time type.
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time
and Time type have Equal method.
func (t Time) Equal(u Time) bool
Equal reports whether t and u represent the same time instant. Two times can be equal even if they are in different locations. For example, 6:00 +0200 CEST and 4:00 UTC are Equal. See the documentation on the Time type for the pitfalls of using == with Time values; most code should use Equal instead.
Example
package main
import (
"fmt"
"time"
)
func main() {
secondsEastOfUTC := int((8 * time.Hour).Seconds())
beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)
// Unlike the equal operator, Equal is aware that d1 and d2 are the
// same instant but in different time zones.
d1 := time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC)
d2 := time.Date(2000, 2, 1, 20, 30, 0, 0, beijing)
datesEqualUsingEqualOperator := d1 == d2
datesEqualUsingFunction := d1.Equal(d2)
fmt.Printf("datesEqualUsingEqualOperator = %v\n", datesEqualUsingEqualOperator)
fmt.Printf("datesEqualUsingFunction = %v\n", datesEqualUsingFunction)
}
datesEqualUsingEqualOperator = false
datesEqualUsingFunction = true
resources
Time type documentation
Equal method documentation
time.Date

Difference between two time.Time objects

Very new to the 'Go'. Question might be basic one.
I have two time.Time objects and I want to get the difference between the two in terms of hours/minutes/seconds. Lets say:
t1 = 2016-09-09 19:09:16 +0530 IST
t2 = 2016-09-09 19:09:16 +0530 IST
In above case, since the difference is 0. It should give me 00:00:00. Consider another case:
t1 = 2016-09-14 14:12:48 +0530 IST
t2 = 2016-09-14 14:18:29 +0530 IST
In this case, difference would be 00:05:41. I looked at the https://godoc.org/time but could not make anything out of it.
You may use Time.Sub() to get the difference between the 2 time.Time values, result will be a value of time.Duration.
When printed, a time.Duration formats itself "intelligently":
t1 := time.Now()
t2 := t1.Add(time.Second * 341)
fmt.Println(t1)
fmt.Println(t2)
diff := t2.Sub(t1)
fmt.Println(diff)
Output:
2009-11-10 23:00:00 +0000 UTC
2009-11-10 23:05:41 +0000 UTC
5m41s
If you want the time format HH:mm:ss, you may constuct a time.Time value and use its Time.Format() method like this:
out := time.Time{}.Add(diff)
fmt.Println(out.Format("15:04:05"))
Output:
00:05:41
Try the examples on the Go Playground.
Of course this will only work if the time difference is less than a day. If the difference may be bigger, then it's another story. The result must include days, months and years. Complexity increases significnatly. See this question for details:
golang time.Since() with months and years
The solution presented there solves this issue by showing a function with signature:
func diff(a, b time.Time) (year, month, day, hour, min, sec int)
You may use that even if your times are within 24 hours (in which case year, month and day will be 0).
Actually, the time package's documentation does discuss it:
https://godoc.org/time#Time.Sub
https://godoc.org/time#Duration.Hours
You should produce a Duration object using Sub() and then use one of the Seconds(), Minutes(), Hours().
package main
import (
"fmt"
"time"
)
func main() {
t1 := time.Date(1984, time.November, 3, 13, 0, 0, 0, time.UTC)
t2 := time.Date(1984, time.November, 3, 10, 0, 0, 0, time.UTC)
fmt.Printf("The hours difference is: %f", t1.Sub(t2).Hours())
}
To complement Shmulik Klein's answer:
Another way to calculate disjoint hours/minutes/seconds out of a time.Duration:
https://play.golang.org/p/VRoXG5NxLo
package main
import (
"fmt"
"math"
"time"
)
func main() {
t1 := time.Date(1984, time.November, 3, 13, 0, 0, 0, time.UTC)
t2 := time.Date(1984, time.November, 3, 10, 23, 34, 0, time.UTC)
hs := t1.Sub(t2).Hours()
hs, mf := math.Modf(hs)
ms := mf * 60
ms, sf := math.Modf(ms)
ss := sf * 60
fmt.Println(hs, "hours", ms, "minutes", ss, "seconds")
}
2 hours 36 minutes 25.999999999999375 seconds
note:
slight precision loss due to the use of the float64 type
we ignore leap seconds and assume every minute has 60 seconds
There are 2 common ways:
straight forward one:
startTime := time.Now()
diff := time.Now().Sub(startTime)
shorter one (a bit):
startTime := time.Now()
diff := time.Since(startTime)
DEMO
package main
import (
"fmt"
"math"
"time"
)
func TimeAsString(dt float64) string {
time := dt
hours := math.Floor(time / 3600)
minutes := math.Ceil(math.Mod(time, 3600)/60) - 1
seconds := int(time) % 60
return fmt.Sprintf("%v:%v:%v", hours, minutes, seconds)
}
func main() {
mytime := 0.0
last := time.Now()
tick := time.Tick(33 * time.Millisecond)
for {
select {
case <-tick:
dt := time.Since(last).Seconds()
last = time.Now()
mytime += dt
fmt.Println(TimeAsString(mytime))
}
}
}

How to do date/time comparison

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)

Resources