Reputation: 3128
Consider the following code:
package main
import (
"time"
"fmt"
)
const (
format = "2006 01 02 15:04 MST"
date = "2018 08 01 12:00 EDT"
)
func main() {
aloc, _ := time.LoadLocation("America/New_York")
eloc, _ := time.LoadLocation("Europe/Berlin")
tn, _ := time.Parse(format, date)
tl, _ := time.ParseInLocation(format, date, aloc)
fmt.Println(tn) // Says +0000 despite EDT being -0400
fmt.Println(tn.In(eloc)) // Expect 18:00, but get 14:00
fmt.Println(tl) // Correctly -0400
fmt.Println(tl.In(eloc)) // Correctly 18:00
}
You can also try it out on Go Playground.
When I run it, I get this result (both on my own system and through the Playground):
2018-08-01 12:00:00 +0000 EDT
2018-08-01 14:00:00 +0200 CEST
2018-08-01 12:00:00 -0400 EDT
2018-08-01 18:00:00 +0200 CEST
I had expected the first and third line to be the same, and the second and fourth to be the same.
It seems to me that Go's time library doesn't parse the "EDT" timezone identifier I've written in the date string, despite it being part of the format.
My own system (Fedora 26) also recognises EST/EDT as a timezone:
$ TZ='America/New_York' date 080112002018
Wed 1 Aug 12:00:00 EDT 2018
Of course, as you can see, I can force the issue by using ParseInLocation()
, but that's only useful if I know the timezone beforehand. Otherwise I need to parse the 'EDT' part of the date string into 'America/New_York' myself.
Or am I missing something?
Upvotes: 8
Views: 7491
Reputation: 1
Quoting from time#Parse:
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 key here is "current location". For me, that is CST
. Using this works as
expected:
package main
import "time"
func main() {
t, e := time.Parse(time.RFC1123, "Wed, 01 Aug 2018 12:00:00 CST")
if e != nil {
panic(e)
}
s := t.String()
println(s == "2018-08-01 13:00:00 -0500 CDT")
}
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.
package main
import "time"
func main() {
t, e := time.Parse(time.RFC1123, "Wed, 01 Aug 2018 12:00:00 EDT")
if e != nil {
panic(e)
}
s := t.String()
println(s == "2018-08-01 12:00:00 +0000 EDT")
}
To avoid such problems, prefer time layouts that use a numeric zone offset, or use ParseInLocation.
package main
import "time"
func main() {
{ // example 1
t, e := time.Parse(time.RFC1123Z, "Wed, 01 Aug 2018 12:00:00 -0400")
if e != nil {
panic(e)
}
s := t.String()
println(s == "2018-08-01 12:00:00 -0400 -0400")
}
{ // example 2
ny, e := time.LoadLocation("America/New_York")
if e != nil {
panic(e)
}
t, e := time.ParseInLocation(time.RFC1123, "Wed, 01 Aug 2018 12:00:00 EDT", ny)
if e != nil {
panic(e)
}
s := t.String()
println(s == "2018-08-01 12:00:00 -0400 EDT")
}
}
Upvotes: 4
Reputation: 10082
A simple debug run shows that it all comes down to this function go/1.10/libexec/src/time/zoneinfo.go:226
func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
l = l.get()
// First try for a zone with the right name that was actually
// in effect at the given time. (In Sydney, Australia, both standard
// and daylight-savings time are abbreviated "EST". Using the
// offset helps us pick the right one for the given time.
// It's not perfect: during the backward transition we might pick
// either one.)
for i := range l.zone {
zone := &l.zone[i]
if zone.name == name {
nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset))
if nam == zone.name {
return offset, true
}
}
}
// Otherwise fall back to an ordinary name match.
for i := range l.zone {
zone := &l.zone[i]
if zone.name == name {
return zone.offset, true
}
}
// Otherwise, give up.
return
}
On my OSX (I am in Zurich, so CET
as of now) the call to l.get()
returns on object containing 4 values in the zone slice being CET
, CEST
, CET
and CEST
again. On top of that GMT
and UTC
are treated specially beforehand. All other zones are "unknown" to me, including EDT.
Upvotes: 2