简体   繁体   中英

Why doesn't Go's time.Parse() parse the timezone identifier?

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?

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")
   }
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM