Reputation: 313
I am trying to parse "Tue Jun 11 2019 13:26:45 GMT+0000" with "Mon Jan 02 2006 15:04:05 MST-0700" as a layout string using time.Parse
but I get this error parsing time "Tue Jun 11 2019 13:26:45 GMT+0000" as "Mon Jan 02 2006 15:04:05 MST-0700": cannot parse "" as "-0700"
.
I've been using above layout string for other offsets and it works fine. But, I think "+0000" is not being considered a valid offset or something? Any suggestions would be helpful.
EDIT: Using "Mon Jan 02 2006 15:04:05 GMT-0700" as the layout works and I get the output as 2019-06-11 13:26:45 +0000 +0000
.
Upvotes: 1
Views: 1075
Reputation: 8222
EDIT 2: According to reply in github issues, it turns out that in layout string "MST-0700" is actually two time zone value "MST"
and numeric time zone value "-0700"
which takes precedence over the former. And "GMT+08"
or "GMT+00"
is considered as a time zone value as a whole and matches against "MST". So it would be "GMT+08+0800"
for "MST-0700"
.
I rarely touches time zone related problems, but I personally think this behaviour is confusing.
Original answer:
This is a bug confusing behaviour of Go's time library. While I am not certain of the desired behaviour of GMT time format (since I rarely see something like GMT+0800
or so in a time format string), the code logic that make GMT+0800
valid but GMT+0000
does not makes sense.
I have submitted an issue on the github: https://github.com/golang/go/issues/40472
In short, Go's time library parses format by consuming the layout string and the value string together. And currently when parsing a "GMT" time zone, it consumes more of the value string, if the following value string (say, "+0800" or "+0000") is signed and in range from -23
to +23
. So not only does "GMT+0000"
fails, but "GMT+0030"
and "GMT+08"
(when matching to `"MST-07") fails too.
Here is the detail: When parsing time zone, it checks the layout string and found "MST", so it tries to parse the time zone value in the value string, which at that time only has "GMT+0X00" (where X is '0' or '8') - others have been consumed (correctly). Source
case stdTZ:
// Does it look like a time zone?
if len(value) >= 3 && value[0:3] == "UTC" {
z = UTC
value = value[3:]
break
}
n, ok := parseTimeZone(value)
if !ok {
err = errBad
break
}
zoneName, value = value[:n], value[n:]
It's all good here. The parseTimeZone function makes a special case for GMT format for reason that is not obvious to me, but here is the code:
// Special case 2: GMT may have an hour offset; treat it specially.
if value[:3] == "GMT" {
length = parseGMT(value)
return length, true
}
And then parseGMT
code is like this:
// parseGMT parses a GMT time zone. The input string is known to start "GMT".
// The function checks whether that is followed by a sign and a number in the
// range -23 through +23 excluding zero.
func parseGMT(value string) int {
value = value[3:]
if len(value) == 0 {
return 3
}
return 3 + parseSignedOffset(value)
}
From the comment, we can already sense problem: "+0800" is not a number in range from -23
to +23
excluding leading zero (while "+0000") is. And obviously, this function is trying to indicate (from the return value) to consume more than 3 bytes, that is more than the "GMT". We can confirm it in code of parseSignedOffset
.
// parseSignedOffset parses a signed timezone offset (e.g. "+03" or "-04").
// The function checks for a signed number in the range -23 through +23 excluding zero.
// Returns length of the found offset string or 0 otherwise
func parseSignedOffset(value string) int {
sign := value[0]
if sign != '-' && sign != '+' {
return 0
}
x, rem, err := leadingInt(value[1:])
// fail if nothing consumed by leadingInt
if err != nil || value[1:] == rem {
return 0
}
if sign == '-' {
x = -x
}
if x < -23 || 23 < x {
return 0
}
return len(value) - len(rem)
}
Here, leadingInt
is an uninteresting function that converts the prefix of a string
to an int64
, and the remaining part of the string.
So parseSignedOffset
did what the documentation advertises: it parse the value string and see if it is in the range form -23
to +23
, but at face value:
It considers +0800
as 800
, larger than +23
, so it returns 0
. As a result, the parseGMT
(and parseTimeZone
) returns 3
, so the Parse
function only consumes 3 bytes this time, and leave "+0800"
to match with "-0700"
, so it is parsed correctly.
It considers +0000
as 0
, a valid value in the range, so it returns 5, meaning "+0000" is part of the time zone, and thus parseGMT
(and parseTimeZone
) returns 8, making the Parse
function consumes the whole string, leaving ""
to match with "-0700"
, and hence the error.
EDIT:
Using GMT in format string and get the "right" value is because that the "GMT" in format string is not considered as time zone, but a part of the format (just like spaces in the string), and "GMT" time zone is the same as the default time zone ("UTC").
You can time.Parse("Mon Jan 02 2006 15:04:05 XYZ-0700", "Tue Jun 11 2019 13:26:45 XYZ+0800")
without getting an error.
Upvotes: 3