robpike / ivy

ivy, an APL-like calculator
Other
1.32k stars 103 forks source link

time zones not working correctly #158

Open fzipp opened 11 months ago

fzipp commented 11 months ago

I start Ivy and enter:

)timezone 
    CEST 0

CEST is correct for my location, but offset 0 isn't. CEST is UTC+2, so the offset should be 7200.

Now I switch the time zone:

)timezone "CET"
    CET 7200

CET is expected, but offset 7200 isn't. CET is UTC+1, so the offset should be 3600.

Now I want to switch back to the original time zone:

)timezone "CEST"
    no such time zone: no such time zone

Even though Ivy told me this zone name at the beginning, so it should be known.

robpike commented 11 months ago

There's a lot going on here, and some of it is caused by the peculiarities of the timezone database. I presume you're using Unix in some form, because otherwise I believe none of this will work.

One thing to understand (it's really subtle, and I am still missing some nuance; read on) is how time.Location works in Go. They are not time zones per se, they are locations in which time can be determined. Have a look:

% ivy
)timezone
AEST 36000

)timezone "EST"

)timezone
EST -14400

)timezone "EDT"

)timezone
EDT -14400

First, note that my timezone, AEST, prints correctly. That's a property of the system, somehow, and for the life of me I can't figure out where it comes from.

Second, note that it knows about EST and EDT but both print with the same offset, which is actually the current offset to UTC at the location in which EST is a valid time zone, e.g. New York. You can see this happening a bit in the test suite, which asks the time at the Unix epoch. The test has the wrong offset for that date, and will break when the US goes back to standard time. I should probably have commented that, and will return to it then.

Third, I can't explain why you see zero for CEST, but on my machine CEST is not a known zone so I can't get ivy into that state. I can do this though:

% ivy
)timezone "GMT"

)timezone
GMT 3600

)timezone "UTC"

)timezone
UTC 0

)timezone "Europe/Paris"

)timezone
Europe/Paris 7200

Try that and see what you get. You could also try setting the TZ environment variable and explore what happens then.

Despite this explanation, I do believe there are bugs, I just don't know how to deal with the inconsistencies in the timezone database and its interaction with what the local system says.

fzipp commented 11 months ago

I'm using macOS. If you have a macOS machine you should be able to my reproduce my behavior:

System Settings -> General -> Date & Time

Now the time zone should be "Central European Summer Time" (until Oct 29).


In the following experiments I'll use this short Go program for comparison with Ivy:

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println(time.Now().Zone())
}

First experiment, with my system settings (location "Berlin - Germany"):

% go run main.go
CEST 7200
% ivy -e ")timezone"
CEST 0

Here, the Go program prints what I expected, because CEST is UTC+2.

In the next experiment, the time zone database doesn't seem to know about CEST and falls back to UTC if I specify TZ=CEST:

% TZ=CEST go run main.go
UTC 0
% TZ=CEST ivy -e ")timezone"
UTC 0

In the next experiment both interpret TZ=CET as a location (which I'm not a fan of, but perhaps I'll have to accept it) and switch to CEST:

% TZ=CET go run main.go    
CEST 7200
% TZ=CET ivy -e ")timezone"
CEST 0

The output of the short Go program is the consistent one.

Next experiment with TZ=GMT:

% TZ=GMT go run main.go
GMT 0
% TZ=GMT ivy -e ")timezone"
GMT 3600

In my understanding GMT is defined as UTC+0 and should always have the offset 0. However, Ivy interprets it as location "Europe/London", which is currently on British Summer Time (BST, UTC+1) rather than GMT.

robpike commented 11 months ago

Thanks for the good report.

I know what's wrong with GMT - I have it mapped to Europe/London instead of UTC - and that's trivial to fix.

The others require more investigation.

fzipp commented 11 months ago

The following is my understanding when looking at this table: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List

fzipp commented 11 months ago

There is no general lookup mechanism from a "Time zone abbreviation" string to its corresponding "UTC Offset", at least not in Go's time package.

Since this lookup direction doesn't exist in the public API, I wondered if Go manages to parse "2023-09-27 18:00:00 CEST" correctly. So I ran the following program:


import (
    "fmt"
    "log"
    "time"
)

func main() {
    t, err := time.Parse("2006-01-02 15:04:05 MST", "2023-09-27 18:00:00 CEST")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(t)
}

The output

2023-09-27 18:00:00 +0200 CEST

is correct. Then I changed my location from "Berlin - Germany" to "New York, NY - United States" in the macOS system settings, and now the output is wrong!

2023-09-27 18:00:00 +0000 CEST

I find it surprising that Go's time parsing, when provided with an explicit time zone, is dependent on the location setting.

robpike commented 11 months ago

Yes, you've put your finger on something that's at the heart of this. I plan to investigate with the Go team.

robpike commented 11 months ago

https://github.com/golang/go/issues/63345