Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

time.Parse doesn't seem to parse the abbreviated timezone as expected #56528

Closed
gnvk opened this issue Nov 2, 2022 · 6 comments
Closed

time.Parse doesn't seem to parse the abbreviated timezone as expected #56528

gnvk opened this issue Nov 2, 2022 · 6 comments

Comments

@gnvk
Copy link

gnvk commented Nov 2, 2022

What version of Go are you using (go version)?

$ go version
go version go1.19.3 darwin/arm64

Does this issue reproduce with the latest release?

Yes.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="arm64"
GOBIN=""
GOCACHE="/Users/gnvk/Library/Caches/go-build"
GOENV="/Users/gnvk/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="arm64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/gnvk/Dev/go/pkg/mod"
GONOPROXY="github.com/alpacahq/*"
GONOSUMDB="github.com/alpacahq/*"
GOOS="darwin"
GOPATH="/Users/gnvk/Dev/go"
GOPRIVATE="github.com/alpacahq/*"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/opt/homebrew/opt/go/libexec"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/opt/homebrew/opt/go/libexec/pkg/tool/darwin_arm64"
GOVCS=""
GOVERSION="go1.19.3"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/gnvk/Dev/alpaca/corpaca/go.mod"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/xv/5sz1kn411vb_g2_vmhnstl1c0000gn/T/go-build2211926623=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

I ran the first example in the example under https://pkg.go.dev/time#Parse both in the Go Playground and on my local machine:

const longForm = "Jan 2, 2006 at 3:04pm (MST)"
t, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")
fmt.Println(t)

What did you expect to see?

2013-02-03 19:54:00 -0800 PST

What did you see instead?

2013-02-03 19:54:00 +0000 PST

https://github.com/golang/go/blob/master/src/time/example_test.go#L375 also seems to fail with this:

=== RUN   ExampleParse
--- FAIL: ExampleParse (0.00s)
got:
2013-02-03 19:54:00 +0000 PST
2013-02-03 00:00:00 +0000 UTC
2006-01-02 15:04:05 +0000 UTC
2006-01-02 15:04:05 +0700 +0700
error parsing time "2006-01-02T15:04:05Z07:00": extra text: "07:00"
want:
2013-02-03 19:54:00 -0800 PST
2013-02-03 00:00:00 +0000 UTC
2006-01-02 15:04:05 +0000 UTC
2006-01-02 15:04:05 +0700 +0700
error parsing time "2006-01-02T15:04:05Z07:00": extra text: "07:00"
FAIL
@Mark-Pintye
Copy link

Totally agree!

@lszamosi
Copy link

lszamosi commented Nov 2, 2022

Same result in a European timezone, but I got the right result in a Pacific timezone
in European timezone:

root@57bc7499a835:/go/src/time_test# date
Wed Nov  2 18:05:46 CET 2022
root@57bc7499a835:/go/src/time_test# go test example_test.go -count=1
--- FAIL: ExampleParse (0.00s)
got:
2013-02-03 19:54:00 +0000 PST
2013-02-03 00:00:00 +0000 UTC
2006-01-02 15:04:05 +0000 UTC
2006-01-02 15:04:05 +0700 +0700
error parsing time "2006-01-02T15:04:05Z07:00": extra text: "07:00"
want:
2013-02-03 19:54:00 -0800 PST
2013-02-03 00:00:00 +0000 UTC
2006-01-02 15:04:05 +0000 UTC
2006-01-02 15:04:05 +0700 +0700
error parsing time "2006-01-02T15:04:05Z07:00": extra text: "07:00"
FAIL
FAIL    command-line-arguments  0.004s
FAIL

In Pacific timezone:

root@57bc7499a835:/go/src/time_test# date
Wed Nov  2 10:06:12 PDT 2022
root@57bc7499a835:/go/src/time_test# go test example_test.go -count=1
ok      command-line-arguments  0.003s

@ianlancetaylor
Copy link
Contributor

After parsing the time string, you will get a time.Time value for which the Location method will return a location named "PST". However, the standard timezone database has no mapping from "PST" to a zone offset.

Quoting the documentation for 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 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.

Closing because this is working as documented.

@ianlancetaylor ianlancetaylor closed this as not planned Won't fix, can't repro, duplicate, stale Nov 2, 2022
@gnvk
Copy link
Author

gnvk commented Nov 3, 2022

@ianlancetaylor

In my opinion this behaviour is unusual to say the least. It effectively means that the correctness of the parsing depends on your current location.

However, the standard timezone database has no mapping from "PST" to a zone offset.

Anyway, can you please help me how to work around this limitation? Say you have a list of timestamps in this format: "Jan 2, 2006 at 3:04pm (MST)". How can you parse them correctly?

@ZekeLu
Copy link
Contributor

ZekeLu commented Nov 3, 2022

It effectively means that the correctness of the parsing depends on your current location.

The document quoted by ianlancetaylor@ has pointed out that you can use time.ParseInLocation to specify the location other than the current location.

Please note that a zone abbreviation alone is not enough to determine the time zone offset sometimes. For example, CST means different time zone offset in different locations. See the demo below:

package main

import (
	"fmt"
	"time"
)

func main() {
	parseIn("Asia/Shanghai")
	parseIn("America/Chicago")
	parseIn("America/Havana")
}

func parseIn(locationName string) {
	loc, err := time.LoadLocation(locationName)
	if err != nil {
		panic(err)
	}

	const longForm = "Jan 2, 2006 at 3:04pm (MST)"
	t, err := time.ParseInLocation(longForm, "Feb 3, 2013 at 7:54pm (CST)", loc)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%15s: %v\n", loc, t)
}

The output:

  Asia/Shanghai: 2013-02-03 19:54:00 +0800 CST
America/Chicago: 2013-02-03 19:54:00 -0600 CST
 America/Havana: 2013-02-03 19:54:00 -0500 CST

If you need to parse a time layout with arbitrary zone abbreviation, I think the solution is to maintain a mapping from zone abbreviation to location. Then for each input, read the zone abbreviation first, load the location for that zone abbreviation, and finally parse the input in that location.

@gnvk
Copy link
Author

gnvk commented Nov 3, 2022

@ZekeLu Thank you for the example. I didn't know that the zone abbreviation alone is not enough to determine the time zone.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants