Is the UTC() call redundant in rand.Seed(time.Now().UTC().UnixNano())? - go

Many examples on the Internet use rand.Seed(time.Now().UTC().UnixNano()) to initialise the pseudo random number generator seed.
I see that if I omit the UTC() call, it still works fine.
Unix (or UnixNano) time is anyway the number of seconds (or milliseconds) since the epoch, that is, 1970-01-01 00:00:00.000000000 UTC. Unix or UnixNano time is anyway timezone-agnostic.
As an example, take the following code:
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
fmt.Println(t.UnixNano())
fmt.Println(t.UTC().UnixNano())
}
So my question is: Is there any purpose to UTC() call or is it safe to omit the UTC() call and just call rand.Seed(time.Now().UnixNano()) instead?

It is safe to say you can omit the UTC() when using UnixNano()
First see the code of UTC() in time.go:1107:
// UTC returns t with the location set to UTC.
func (t Time) UTC() Time {
t.setLoc(&utcLoc)
return t
}
It only sets the Location of the current Time.
Now, according to the comment on the In() methode in the time.go file the Location info are only for "display purposes". See time.go:1119:
// In returns a copy of t representing the same time instant, but
// with the copy's location information set to loc for display
// purposes.
//
// In panics if loc is nil.
func (t Time) In(loc *Location) Time {
if loc == nil {
panic("time: missing Location in call to Time.In")
}
t.setLoc(loc)
return t
}
The Location is only used if the Time has to be displayed:
// abs returns the time t as an absolute time, adjusted by the zone offset.
// It is called when computing a presentation property like Month or Hour.
func (t Time) abs() uint64 {
l := t.loc
// Avoid function calls when possible.
if l == nil || l == &localLoc {
l = l.get()
}
sec := t.unixSec()
if l != &utcLoc {
if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
sec += int64(l.cacheZone.offset)
} else {
_, offset, _, _ := l.lookup(sec)
sec += int64(offset)
}
}
return uint64(sec + (unixToInternal + internalToAbsolute))
}
Run the following code to see the difference. Both are based on the same UnixNano, only the hour changes, since the location is only applied before printing:
var now = time.Now()
var utc = now.UTC()
fmt.Printf("now UnixNano: %d, Hour: %d, Minute: %d, Second: %d\n", now.UnixNano(), now.Hour(), now.Minute(), now.Second())
fmt.Printf("utc UnixNano: %d, Hour: %d, Minute: %d, Second: %d\n", utc.UnixNano(), utc.Hour(), utc.Minute(), utc.Second())
now UnixNano: 1595836999431598000, Hour: 10, Minute: 3, Second: 19
utc UnixNano: 1595836999431598000, Hour: 8, Minute: 3, Second: 19

You set the pseudo-random number generator seed, to make generated numbers difficult to guess.
When you look at UTC() method documentation, you'll see that only thing it does is sets location (timezone). It is irrelevant which timezone is used for random seed generation.
What is important, is that UnixNano() is used, and that platform would actually return time with such precision. Otherwise, the random seed might be guessed, which may allow for: random number generator attack
Please consider a safer way to initialize random seed generator in answer: https://stackoverflow.com/a/54491783/5279383

Time.UnixNano() returns the Unix time of the source time, the number of nanoseconds elapsed since January 1, 1970 UTC. It is always interpreted in UTC zone, it doesn't matter what location the source time has. The unix time is zone-independent. Its documentation clearly states this:
The result does not depend on the location associated with t.
So you do not need to call Time.UTC(), you will get the same result.
See this example:
t1, err := time.Parse("2006-01-02 15:04:05 -0700", "2020-07-27 13:50:00 +0200")
if err != nil {
panic(err)
}
fmt.Printf("%v\n\t%v\n\t%v\n", t1, t1.UnixNano(), t1.UTC().UnixNano())
t2, err := time.Parse("2006-01-02 15:04:05 -0700", "2020-07-27 13:50:00 +0000")
if err != nil {
panic(err)
}
fmt.Printf("%v\n\t%v\n\t%v\n", t2, t2.UnixNano(), t2.UTC().UnixNano())
We parse 2 input times, once in a non-UTC zone, and another in UTC zone. We print UnixNano() for both, with and without calling UTC(). The results are identical.
Output (try it on the Go Playground):
2020-07-27 13:50:00 +0200 +0200
1595850600000000000
1595850600000000000
2020-07-27 13:50:00 +0000 UTC
1595857800000000000
1595857800000000000

Is there any purpose to UTC() call - YES
is it safe to omit the UTC() - YES

Related

Subtracting time to get age

My aim is to calculate the age of the pod by doing the subtraction of "current_time - pod_creation_time" so that I will get the age, I am getting creation time from metadata but it's in the format "2021-07-13 16:34:22 +0530 IST", so when I trying to subtract it from time.Now(), I am getting parsing error like below:
invalid operation: "t2 : " + t2 (mismatched types string and time.Time)
Anyone could please help how to have creation time "2021-07-13 16:34:22 +0530 IST" from metadata in the proper format so that I can do "time.Now - (creation time)"
I tried some workaround like below:
creatTime, err := time.Parse("2006-01-02 15:04:05 -0700 MST",
pod.ObjectMeta.CreationTimestamp.String())
and then subtracted creationTime from Current Time. It works, but I think this is not the right way.
There's a type mismatch as time.Now() return the current time stored in the type time.Time whereas 2021-07-13 16:34:22 +0530 IST is a string. You can perform the required subtraction operation on mismatched types i.e., time.Time and string.
You have to parse the string by specifying the layout. I'd recommend reading the time package's doc.
I've explained every operation in the sample code below; I hope it helps. If you understand this, you can also then look at helper functions like time.Since that can help you write the same program in fewer lines.
package main
import (
"fmt"
"time"
)
func main() {
// K8s timestamp
t := "2021-07-13 16:34:22 +0530 IST"
// Format of K8s timestamp
format := "2006-01-02 15:04:05 -0700 MST" // Mon Jan 2 15:04:05 -0700 MST 2006
// Parse the timestamp so that it's stored in time.Time
cur, err := time.Parse(format, t)
if err != nil {
panic(err)
}
// Current time
now := time.Now()
// As both are of type time.Time, it's subtractable
dur := now.Sub(cur)
// Print duration
fmt.Println(dur)
// Print duration (in seconds)
fmt.Println(dur.Seconds())
}
Also, I'd like you to learn how to write questions on StackOverflow. The formatting of your question is pretty bad. When seeking good solutions; it is the OP's duty to post the question correctly first so that everybody could understand it and then expect answers.
Read: https://stackoverflow.com/help/how-to-ask

Why does Go time.Format return different value based on timezone?

I thought the Go time.Format should format time based on the layout. But seems it returns different value based on timezone info.
package main
import (
"fmt"
"time"
)
func main() {
formats := []string{
time.RFC3339,
}
times := []string{
"2020-03-08T01:59:50-08:00",
"2020-03-08T01:59:59-08:00", //daylight saving starts 1 second later
"2020-03-08T05:59:59-08:00",
}
for _, f := range formats {
for _, t := range times {
fmt.Printf("Format: %s\n", f)
t, err := time.Parse(f, t)
if err != nil {
panic(err)
}
fmt.Printf("unix: %d\n", t.UnixNano())
fmt.Printf("time: %s\n", t.Format(f))
t = t.Add(time.Second)
fmt.Printf("time + 1s: %s\n", t.Format(f))
}
}
}
Run output:
➜ go version
go version go1.15 darwin/amd64
➜ TZ=UTC go run main.go
Format: 2006-01-02T15:04:05Z07:00
unix: 1583661590000000000
time: 2020-03-08T01:59:50-08:00
time + 1s: 2020-03-08T01:59:51-08:00
Format: 2006-01-02T15:04:05Z07:00
unix: 1583661599000000000
time: 2020-03-08T01:59:59-08:00
time + 1s: 2020-03-08T02:00:00-08:00 (a: this is not expected)
unix: 1583675999000000000
time: 2020-03-08T05:59:59-08:00
time + 1s: 2020-03-08T06:00:00-08:00
➜ TZ=America/Los_Angeles go run main.go
Format: 2006-01-02T15:04:05Z07:00
unix: 1583661590000000000
time: 2020-03-08T01:59:50-08:00
time + 1s: 2020-03-08T01:59:51-08:00
Format: 2006-01-02T15:04:05Z07:00
unix: 1583661599000000000
time: 2020-03-08T01:59:59-08:00
time + 1s: 2020-03-08T03:00:00-07:00 (b: this is expected)
Format: 2006-01-02T15:04:05Z07:00
unix: 1583675999000000000
time: 2020-03-08T05:59:59-08:00
time + 1s: 2020-03-08T06:00:00-08:00 (c: this contradicts with the b)
The behavior is documented. The output of time.Format is just a consequence, not the confusion's source - which is time.Parse:
func Parse:
When parsing a time with a zone offset like -0700, if the offset
corresponds to a time zone used by the current location (Local), then
Parse uses that location and zone in the returned time. Otherwise it
records the time as being in a fabricated location with time fixed at
the given zone offset.
Further explanation can be found under
type Location:
Local represents the system's local time zone. On Unix systems, Local
consults the TZ environment variable to find the time zone to use. No
TZ means use the system default /etc/localtime. TZ="" means use UTC.
TZ="foo" means use file foo in the system timezone directory.
Basically, go's parser tries to infer a time zone from a UTC offset. If the parsed UTC offset matches that of the time zone set by the TZ environment variable, this time zone is set in the returned time. Simplicity always seems to end when it comes to handling date & time.

How to check whether current local time is DST?

In Ruby, for example, there's the Time#dst? function, which returns true in the case it is daylight saving time. Is there a Go standard library API call to do the same?
In August 2021 go 1.17 was released which now adds the time.Time method IsDST:
IsDST reports whether the time in the configured location is in
Daylight Savings Time.
The Location api doesn't export the DST value of the timezone. This was brought up in the golang-nuts forum several years ago. One suggestion is to compare the January 1 timezone offset to the July 1 timezone offset. A working solution of this was posted using this method. One caveat is that goplay has the wrong local time, so it doesn't correctly report the information if you run it there. You can run it locally to verify that it does work.
Another way would be to use reflection via the reflect package. A solution that I wrote to do this is available here. There are a lot of problems with this method.
Edit: Really it should probably use cacheZone but does a linear search of the zones to find one that matches. This can lead to errors because some timezones share name and offset. The correct way would be to look at cacheZone and use that if it is set. Otherwise, you'll need to either look at zoneTrans or at least look at how lookup(int64) is implemented.
You can infer the result. For example,
package main
import (
"fmt"
"time"
)
// isTimeDST returns true if time t occurs within daylight saving time
// for its time zone.
func isTimeDST(t time.Time) bool {
// If the most recent (within the last year) clock change
// was forward then assume the change was from std to dst.
hh, mm, _ := t.UTC().Clock()
tClock := hh*60 + mm
for m := -1; m > -12; m-- {
// assume dst lasts for least one month
hh, mm, _ := t.AddDate(0, m, 0).UTC().Clock()
clock := hh*60 + mm
if clock != tClock {
if clock > tClock {
// std to dst
return true
}
// dst to std
return false
}
}
// assume no dst
return false
}
func main() {
pstLoc, err := time.LoadLocation("America/Los_Angeles")
if err != nil {
fmt.Println(err)
return
}
utc := time.Date(2018, 10, 29, 14, 0, 0, 0, time.UTC)
fmt.Println(utc, utc.Location(), ": DST", isTimeDST(utc))
local := utc.In(time.Local)
fmt.Println(local, local.Location(), ": DST", isTimeDST(local))
pst := utc.In(pstLoc)
fmt.Println(pst, pst.Location(), ": DST", isTimeDST(pst))
utc = utc.AddDate(0, 3, 0)
fmt.Println(utc, utc.Location(), ": DST", isTimeDST(utc))
local = utc.In(time.Local)
fmt.Println(local, local.Location(), ": DST", isTimeDST(local))
pst = utc.In(pstLoc)
fmt.Println(pst, pst.Location(), ": DST", isTimeDST(pst))
}
Output:
2018-10-29 14:00:00 +0000 UTC UTC : DST false
2018-10-29 10:00:00 -0400 EDT Local : DST true
2018-10-29 07:00:00 -0700 PDT America/Los_Angeles : DST true
2019-01-29 14:00:00 +0000 UTC UTC : DST false
2019-01-29 09:00:00 -0500 EST Local : DST false
2019-01-29 06:00:00 -0800 PST America/Los_Angeles : DST false

Why is time.Since returning negative durations on Windows?

I have been trying to work with some go, and have found some weird behavior on windows. If I construct a time object from parsing a time string in a particular format, and then use functions like time.Since(), I get negative durations.
Code sample:
package main
import (
"fmt"
"time"
"strconv"
)
func convertToTimeObject(dateStr string) time.Time {
layout := "2006-01-02T15:04:05.000Z"
t, _:= time.Parse(layout, dateStr)
return t
}
func main() {
timeOlder := convertToTimeObject(time.Now().Add(-30*time.Second).Format("2006-01-02T15:04:05.000Z"))
duration := time.Since(timeOlder)
fmt.Println("Duration in seconds: " + strconv.Itoa(int(duration.Seconds())))
}
If you run it on Linux or the Go Playground link, you get the result as Duration in seconds: 30 which is expected.
However, on Windows, running the same piece of code with Go 1.10.3 gives Duration in seconds: -19769.
I've banged my head on this for hours. Any help on what I might be missing?
The only leads I've had since now are that when go's time package goes to calculate the seconds for both time objects (time.Now() and my parsed time object), one of them has the property hasMonotonic and one doesn't, which results in go calculating vastly different seconds for both.
I'm not the expert in time, so would appreciate some help. I was going to file a bug for Go, but thought to ask here from the experts if there's something obvious I might be missing.
I think I figured out what the reason for the weird behavior of your code snippet is and can provide a solution. The relevant docs read as follows:
since returns the time elapsed since t. It is shorthand for time.Now().Sub(t).
But:
now returns the current local time.
That means you are formatting timeOlder and subtract it from an unformatted local time. That of course causes unexpected behavior. A simple solution is to parse the local time according to your format before subtracting timeOlder from it.
A solution that works on my machine (it probably does not make a lot of sense to give a playground example, though):
func convertToTimeObject(dateStr string) time.Time {
layout := "2006-01-02T15:04:05.000Z"
t, err := time.Parse(layout, dateStr)
// check the error!
if err != nil {
log.Fatalf("error while parsing time: %s\n", err)
}
return t
}
func main() {
timeOlder := convertToTimeObject(time.Now().Add(-30 * time.Second).Format("2006-01-02T15:04:05.000Z"))
duration := time.Since(timeOlder)
// replace time.Since() with a correctly parsed time.Now(), because
// time.Since() returns the time elapsed since the current LOCAL time.
t := time.Now().Format("2006-01-02T15:04:05.000Z")
timeNow := convertToTimeObject(t)
// print the different results
fmt.Println("duration in seconds:", strconv.Itoa(int(duration.Seconds())))
fmt.Printf("duration: %v\n", timeNow.Sub(timeOlder))
}
Outputs:
duration in seconds: 14430
duration: 30s

Golang time error: month out of range

Here is my code:
time.Parse(time.Now().String()[0:19],time.Now().String()[0:19])
error:
parsing time "2016-09-20 16:50:08": month out of range
How to parse time string?
First param is layout, see:
func Parse(layout, value string) (Time, error) {
return parse(layout, value, UTC, Local)
}
Docs:
// Parse parses a formatted string and returns the time value it represents.
// The layout defines the format by showing how the reference time,
// defined to be
// Mon Jan 2 15:04:05 -0700 MST 2006
// would be interpreted if it were the value; it serves as an example of
// the input format. The same interpretation will then be made to the
// input string.
//
// Predefined layouts ANSIC, UnixDate, RFC3339 and others describe standard
// and convenient representations of the reference time. For more information
// about the formats and the definition of the reference time, see the
// documentation for ANSIC and the other constants defined by this package.
// Also, the executable example for time.Format demonstrates the working
// of the layout string in detail and is a good reference.
//
// Elements omitted from the value are assumed to be zero or, when
// zero is impossible, one, so parsing "3:04pm" returns the time
// corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is
// 0, this time is before the zero Time).
// Years must be in the range 0000..9999. The day of the week is checked
// for syntax but it is otherwise ignored.
//
// In the absence of a time zone indicator, Parse returns a time in UTC.
//
// When parsing a time with a zone offset like -0700, if the offset corresponds
// to a time zone used by the current location (Local), then Parse uses that
// location and zone in the returned time. Otherwise it records the time as
// being in a fabricated location with time fixed at the given zone offset.
//
// No checking is done that the day of the month is within the month's
// valid dates; any one- or two-digit value is accepted. For example
// February 31 and even February 99 are valid dates, specifying dates
// in March and May. This behavior is consistent with time.Date.
//
// When parsing a time with a zone abbreviation like MST, if the zone abbreviation
// has a defined offset in the current location, then that offset is used.
// The zone abbreviation "UTC" is recognized as UTC regardless of location.
// If the zone abbreviation is unknown, Parse records the time as being
// in a fabricated location with the given zone abbreviation and a zero offset.
// This choice means that such a time can be parsed and reformatted with the
// same layout losslessly, but the exact instant used in the representation will
// differ by the actual zone offset. To avoid such problems, prefer time layouts
// that use a numeric zone offset, or use ParseInLocation.
You may use
t, err := time.Parse("2006-01-02 15:04:05", time.Now().String()[:19])
Try on The Go Playground:
package main
import (
"fmt"
"time"
)
func main() {
t, err := time.Parse("2006-01-02 15:04:05", time.Now().String()[:19])
if err != nil {
panic(err)
}
fmt.Println(t)
}
output:
2009-11-10 23:00:00 +0000 UTC
I had the same problem, so I came here to say golang will some times mean "month" they meant "DAY OF THE MONTH", the error message is wrong, here is an example:
package main
import (
"fmt"
"time"
)
func main() {
dateAsString:= "31/Oct/2019"
layout := "01/Jan/2006" // BAD BAD BAD SHOULD BE 02 INSTEAD OF 01
fmt.Println("INPUT:" + dateAsString)
t, err := time.Parse(layout, dateAsString)
if err != nil {
fmt.Println("DATE UNPARSEABLE:3", err)
}
fmt.Println(t)
}

Resources