8

I'm trying to compare two times from different timezones, and see whether one is before the other. How would I do this in Go?

Note: Basically I would like sfTime.Before(nyTime) == true, but my example below would have sfTime.Before(nyTime) == false. Suggestions on how to make this happen would be great.


For example, in this code...

layout := "2006-01-02 15:04 MST"
sfTime, _ := time.Parse(layout, "2017-03-01 12:00 PDT")
nyTime, _ := time.Parse(layout, "2017-03-01 12:00 EDT")

fmt.Printf("Are these times equal? %v\n", sfTime.Equal(nyTime))

This prints:

Are these times equal? true

Playground link here.

Unintuitively, even if you set them to be the same timezone, this only changes the timezone, but not the HH:mm value.

layout := "2006-01-02 15:04 MST"
sfTime, _ := time.Parse(layout, "2017-03-01 12:00 PDT")
nyTime, _ := time.Parse(layout, "2017-03-01 12:00 EDT")

// Set timezone to UTC
utcLocation, _ := time.LoadLocation("UTC")
sfTime = sfTime.In(utcLocation)
nyTime = nyTime.In(utcLocation)

// Timezones should not be equal, but they are
fmt.Printf("Are these times still equal? %v\n", sfTime.Equal(nyTime))
fmt.Printf("The New York Time: %v\n", nyTime)

Prints

Are these times still equal? true

The New York Time: 2017-03-01 12:00:00 +0000 UTC

Playground link.

Community
  • 1
  • 1
hlin117
  • 20,764
  • 31
  • 72
  • 93
  • 1
    from the docs "Equal reports whether t and u represent the same time instant. Two times can be equal even if they are in different locations." – mkopriva Mar 21 '17 at 23:51
  • 1
    I agree with @mkopriva, however, it is interesting the time does not change with UTC. It does appear to work with other locations, for example Europe/Berlin, just not UTC. – Steve Mar 21 '17 at 23:52
  • 1
    Please - and this has been said SO many times already on stackoverflow and elsewhere - Do not attempt to choose a time zone by parsing an abbreviation. Time zone abbreviations are not standardized, are not unique, and many time zones don't even *have* abbreviations. You should be using IANA tz database identifiers, such as `America/Los_Angeles` and `America/New_York`. Go supports these. Please read [the timezone tag wiki](http://stackoverflow.com/tags/timezone/info). – Matt Johnson-Pint Mar 22 '17 at 03:24

3 Answers3

7

Don't use the Go Playground for time calculations. It runs in a sandbox with a fake time:

About the Playground

The Go Playground is a web service that runs on golang.org's servers. The service receives a Go program, compiles, links, and runs the program inside a sandbox, then returns the output.

There are limitations to the programs that can be run in the playground.

In the playground the time begins at 2009-11-10 23:00:00 UTC (determining the significance of this date is an exercise for the reader). This makes it easier to cache programs by giving them deterministic output.

Also, all times in the Go Playground use the UTC time zone. The Go Playground doesn't use the IANA Time Zone Database.

For example, for this program,

package main

import (
    "fmt"
    "time"
)

func main() {
    layout := "2006-01-02 15:04 MST"
    sfTime, err := time.Parse(layout, "2017-03-01 12:00 PDT")
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(sfTime, sfTime.UTC())
    nyTime, err := time.Parse(layout, "2017-03-01 12:00 EDT")
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(nyTime, nyTime.UTC())
    fmt.Printf("Are these times equal? %v\n", sfTime.Equal(nyTime))
}

Output from the Go Playground is:

2017-03-01 12:00:00 +0000 PDT 2017-03-01 12:00:00 +0000 UTC
2017-03-01 12:00:00 +0000 EDT 2017-03-01 12:00:00 +0000 UTC
Are these times equal? true

For the correct output, run the program using the Go gc or gccgo compiler:

$ go run equal.go
2017-03-01 12:00:00 +0000 PDT 2017-03-01 12:00:00 +0000 UTC
2017-03-01 11:00:00 -0500 EST 2017-03-01 16:00:00 +0000 UTC
Are these times equal? false

Using the Go gc or gccgo compiler then sfTime.Before(nyTime) == true:

package main

import (
    "fmt"
    "time"
)

func main() {
    layout := "2006-01-02 15:04 MST"
    sfTime, err := time.Parse(layout, "2017-03-01 12:00 PDT")
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(sfTime, sfTime.UTC())
    nyTime, err := time.Parse(layout, "2017-03-01 12:00 EDT")
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(nyTime, nyTime.UTC())
    fmt.Printf("Is the SF time before the NY time? %v\n", sfTime.Before(nyTime))
}

Output:

$ go run before.go
2017-03-01 12:00:00 +0000 PDT 2017-03-01 12:00:00 +0000 UTC
2017-03-01 11:00:00 -0500 EST 2017-03-01 16:00:00 +0000 UTC
Is the SF time before the NY time? true

The Go time package comparison methods (Equal, Before, and After) compare UTC values.

peterSO
  • 158,998
  • 31
  • 281
  • 276
  • 1
    Thanks for the answer. For those who want to run the above script, you can put the code into `script.go` and then run it by `go run script.go`. – hlin117 Mar 22 '17 at 04:18
  • I don't understand your output example: `2017-03-01 12:00:00 +0000 PDT 2017-03-01 12:00:00 +0000 UTC` — it seems to me that the PDT means an offset of -0700 (or -0800 depending on season) and thus the time is off by 7 or 8 hours. – Alexis Wilke May 10 '23 at 21:45
4

I think this is a bug with play.golang.org, when I run it on my local machine it returns Are these times equal? false and Are these times still equal? false.

According to the docs this is the expected behaviour (returning false):

// 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.

Have you tried running it locally? The playground's time is set to a fixed instant so it may somehow be related to that.

Community
  • 1
  • 1
dave
  • 62,300
  • 5
  • 72
  • 93
  • 1
    That's weird @dave, i'm getting `true` on my local machine, both those playground examples return the same results on my machine... – mkopriva Mar 22 '17 at 00:31
3

Your examples are working as intended, Equal comparse an instant in time. If you want to make sure timezones are equal as well you can do something like t1.Equal(t2) && t1.Location().String() == t2.Location().String()

From the docs with added emphasis:

Each Time has associated with it a Location, consulted when computing the presentation form of the time, such as in the Format, Hour, and Year methods. The methods Local, UTC, and In return a Time with a specific location. Changing the location in this way changes only the presentation; it does not change the instant in time being denoted and therefore does not affect the computations described in earlier paragraphs.

So, as far as I understand, whether you do time.Parse("... PDT"), time.Parse("... EDT"), or sfTime.In(time.UTC) you always get the same time instant, the same number of seconds since 1970 and therefore calling Equal, Before, and After on those Time values will return the same result, whatever the Location.

Update: I would just like to add to the chosen answer, that this is not Playground specific, the original examples behave the same way on my machine and if you look at peterSO's output of the PDT time you can see it's still parsed as UTC. This behaviour is described in the last paragraph of Parse's documentation. (emphasis mine)

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.

Here's an example using ParseInLocation and numeric timezone offsets: https://play.golang.org/p/vY0muIvk5d

mkopriva
  • 35,176
  • 4
  • 57
  • 71
  • 1
    Great, thanks for the clarification. Is there a way to test whether `sfTime.Before(nyTime)` correctly then? – hlin117 Mar 21 '17 at 23:59
  • 1
    I'm pretty sure `Before` doesn't care about the location either. https://play.golang.org/p/DrFoWhT2mQ – mkopriva Mar 22 '17 at 00:02
  • 1
    Please check the chosen answer; it turns out that the go playground doesn't support time zones. – hlin117 Mar 22 '17 at 04:17
  • 1
    @hlin117 It's the better answer, well chosen. I've updated mine to provide a bit more info. – mkopriva Mar 22 '17 at 09:13