Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is time.Parse not using the timezone?

Why is time.Parse not using timezone information? It should return different time for different timezones.

Code

package main

import (
    "fmt"
    "time"
)

func main() {
    t, err := time.Parse("2006-01-02 MST", "2018-05-11 IST")
    if err != nil {
        return
    }
    t2, err := time.Parse("2006-01-02 MST", "2018-05-11 UTC")
    if err != nil {
        return
    }
    fmt.Println(t.Unix())
    fmt.Println(t2.Unix())
}

Output:

1525996800
1525996800
like image 379
Prasanna Mahajan Avatar asked Jan 28 '23 03:01

Prasanna Mahajan


1 Answers

Some explanation to the question itself:

These 2 timestamps 2018-05-11 IST and 2018-05-11 UTC do not designate the same time instant because IST has a different offset than UTC: India Standard Time (IST) is 5:30 hours ahead of Coordinated Universal Time (UTC).

And Time.Unix() returns the elapsed seconds up to the time instant, elapsed since the reference time (January 1, 1970 UTC). Which implies the output should be different!

Running your code on the PlayGround indeed gives wrong result (link).

And it is a timezone related issue. If you try to load the IST timezone:

loc, err := time.LoadLocation("IST")
fmt.Println(loc, err)

Output:

UTC cannot find IST in zip file /usr/local/go/lib/time/zoneinfo.zip

And the reason why "IST" is not supported is because it is ambiguous. It could mean India, Ireland, Israel, etc. time zones, which have different zone offsets and rules.

And documentation of time.Parse() states that

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.

So the time.time returned by parse.Parse() will have 0 offset just like the UTC zone, hence it will result in the same "unix time" (Time.Unix() will return the same value).

But running it locally (with my CET) timezone gives different, correct result:

t, err := time.Parse("2006-01-02 MST", "2018-05-11 CET")
if err != nil {
    panic(err)
}
t2, err := time.Parse("2006-01-02 MST", "2018-05-11 UTC")
if err != nil {
    panic(err)
}
fmt.Println(t)
fmt.Println(t2)

fmt.Println(t.Unix())
fmt.Println(t2.Unix())

Output:

2018-05-11 01:00:00 +0200 CEST
2018-05-11 00:00:00 +0000 UTC
1525993200
1525996800

Documentation of time.Parse() has this to say about parsing a time with zone abbreviation:

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.

Documentation suggests to parse with a layout with numeric zone offset, like this:

t, err := time.Parse("2006-01-02 -0700", "2018-05-11 +0530")
if err != nil {
    panic(err)
}

Then output (try it on the Go Playground):

2018-05-11 00:00:00 +0530 +0530
2018-05-11 00:00:00 +0000 UTC
1525977000
1525996800

Another option is to use time.FixedZone() to construct IST yourself, and use time.ParseInLocation(), passing our manual IST location:

ist := time.FixedZone("IST", 330*60) // +5:30
t, err := time.ParseInLocation("2006-01-02 MST", "2018-05-11 IST", ist)

Output will be (try it on the Go Playground):

2018-05-11 00:00:00 +0530 IST
2018-05-11 00:00:00 +0000 UTC
1525977000
1525996800

Yet another option is to load Indian IST zone by Kolkata city:

loc, err := time.LoadLocation("Asia/Kolkata")
if err != nil {
    panic(err)
}

Which will produce the same output. Try it on the Go Playground.

like image 113
icza Avatar answered Feb 05 '23 15:02

icza