mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
time: Allow Parse and Format to handle time zone offsets with seconds
Adds layout cases with seconds for stdISO8601 and stdNumTZ with and without colons. Update time.Format to append seconds for those cases. Fixes #4934. R=golang-dev, r, bradfitz CC=golang-dev https://golang.org/cl/8132044
This commit is contained in:
parent
936cc5e7fa
commit
aa38aeaeaf
2 changed files with 126 additions and 40 deletions
|
|
@ -82,10 +82,14 @@ const (
|
|||
stdpm // "pm"
|
||||
stdTZ = iota // "MST"
|
||||
stdISO8601TZ // "Z0700" // prints Z for UTC
|
||||
stdISO8601SecondsTZ // "Z070000"
|
||||
stdISO8601ColonTZ // "Z07:00" // prints Z for UTC
|
||||
stdISO8601ColonSecondsTZ // "Z07:00:00"
|
||||
stdNumTZ // "-0700" // always numeric
|
||||
stdNumSecondsTz // "-070000"
|
||||
stdNumShortTZ // "-07" // always numeric
|
||||
stdNumColonTZ // "-07:00" // always numeric
|
||||
stdNumColonSecondsTZ // "-07:00:00"
|
||||
stdFracSecond0 // ".0", ".00", ... , trailing zeros included
|
||||
stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted
|
||||
|
||||
|
|
@ -165,7 +169,13 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) {
|
|||
return layout[0:i], stdpm, layout[i+2:]
|
||||
}
|
||||
|
||||
case '-': // -0700, -07:00, -07
|
||||
case '-': // -070000, -07:00:00, -0700, -07:00, -07
|
||||
if len(layout) >= i+7 && layout[i:i+7] == "-070000" {
|
||||
return layout[0:i], stdNumSecondsTz, layout[i+7:]
|
||||
}
|
||||
if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" {
|
||||
return layout[0:i], stdNumColonSecondsTZ, layout[i+9:]
|
||||
}
|
||||
if len(layout) >= i+5 && layout[i:i+5] == "-0700" {
|
||||
return layout[0:i], stdNumTZ, layout[i+5:]
|
||||
}
|
||||
|
|
@ -175,13 +185,21 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) {
|
|||
if len(layout) >= i+3 && layout[i:i+3] == "-07" {
|
||||
return layout[0:i], stdNumShortTZ, layout[i+3:]
|
||||
}
|
||||
case 'Z': // Z0700, Z07:00
|
||||
|
||||
case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00,
|
||||
if len(layout) >= i+7 && layout[i:i+7] == "Z070000" {
|
||||
return layout[0:i], stdISO8601SecondsTZ, layout[i+7:]
|
||||
}
|
||||
if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" {
|
||||
return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:]
|
||||
}
|
||||
if len(layout) >= i+5 && layout[i:i+5] == "Z0700" {
|
||||
return layout[0:i], stdISO8601TZ, layout[i+5:]
|
||||
}
|
||||
if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" {
|
||||
return layout[0:i], stdISO8601ColonTZ, layout[i+6:]
|
||||
}
|
||||
|
||||
case '.': // .000 or .999 - repeated digits for fractional seconds.
|
||||
if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
|
||||
ch := layout[i+1]
|
||||
|
|
@ -507,17 +525,19 @@ func (t Time) Format(layout string) string {
|
|||
} else {
|
||||
b = append(b, "am"...)
|
||||
}
|
||||
case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ:
|
||||
case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ:
|
||||
// Ugly special case. We cheat and take the "Z" variants
|
||||
// to mean "the time zone as formatted for ISO 8601".
|
||||
if offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ) {
|
||||
if offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ColonSecondsTZ) {
|
||||
b = append(b, 'Z')
|
||||
break
|
||||
}
|
||||
zone := offset / 60 // convert to minutes
|
||||
absoffset := offset
|
||||
if zone < 0 {
|
||||
b = append(b, '-')
|
||||
zone = -zone
|
||||
absoffset = -absoffset
|
||||
} else {
|
||||
b = append(b, '+')
|
||||
}
|
||||
|
|
@ -526,6 +546,15 @@ func (t Time) Format(layout string) string {
|
|||
b = append(b, ':')
|
||||
}
|
||||
b = appendUint(b, uint(zone%60), '0')
|
||||
|
||||
// append seconds if appropriate
|
||||
if std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ {
|
||||
if std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ {
|
||||
b = append(b, ':')
|
||||
}
|
||||
b = appendUint(b, uint(absoffset%60), '0')
|
||||
}
|
||||
|
||||
case stdTZ:
|
||||
if name != "" {
|
||||
b = append(b, name...)
|
||||
|
|
@ -821,13 +850,13 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
|
|||
default:
|
||||
err = errBad
|
||||
}
|
||||
case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ:
|
||||
case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ:
|
||||
if (std == stdISO8601TZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' {
|
||||
value = value[1:]
|
||||
z = UTC
|
||||
break
|
||||
}
|
||||
var sign, hour, min string
|
||||
var sign, hour, min, seconds string
|
||||
if std == stdISO8601ColonTZ || std == stdNumColonTZ {
|
||||
if len(value) < 6 {
|
||||
err = errBad
|
||||
|
|
@ -837,26 +866,45 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
|
|||
err = errBad
|
||||
break
|
||||
}
|
||||
sign, hour, min, value = value[0:1], value[1:3], value[4:6], value[6:]
|
||||
sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:]
|
||||
} else if std == stdNumShortTZ {
|
||||
if len(value) < 3 {
|
||||
err = errBad
|
||||
break
|
||||
}
|
||||
sign, hour, min, value = value[0:1], value[1:3], "00", value[3:]
|
||||
sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:]
|
||||
} else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ {
|
||||
if len(value) < 9 {
|
||||
err = errBad
|
||||
break
|
||||
}
|
||||
if value[3] != ':' || value[6] != ':' {
|
||||
err = errBad
|
||||
break
|
||||
}
|
||||
sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:]
|
||||
} else if std == stdISO8601SecondsTZ || std == stdNumSecondsTz {
|
||||
if len(value) < 7 {
|
||||
err = errBad
|
||||
break
|
||||
}
|
||||
sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:]
|
||||
} else {
|
||||
if len(value) < 5 {
|
||||
err = errBad
|
||||
break
|
||||
}
|
||||
sign, hour, min, value = value[0:1], value[1:3], value[3:5], value[5:]
|
||||
sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:]
|
||||
}
|
||||
var hr, mm int
|
||||
var hr, mm, ss int
|
||||
hr, err = atoi(hour)
|
||||
if err == nil {
|
||||
mm, err = atoi(min)
|
||||
}
|
||||
zoneOffset = (hr*60 + mm) * 60 // offset is in seconds
|
||||
if err == nil {
|
||||
ss, err = atoi(seconds)
|
||||
}
|
||||
zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds
|
||||
switch sign[0] {
|
||||
case '+':
|
||||
case '-':
|
||||
|
|
|
|||
|
|
@ -781,6 +781,44 @@ func TestMinutesInTimeZone(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type SecondsTimeZoneOffsetTest struct {
|
||||
format string
|
||||
value string
|
||||
expectedoffset int
|
||||
}
|
||||
|
||||
var secondsTimeZoneOffsetTests = []SecondsTimeZoneOffsetTest{
|
||||
{"2006-01-02T15:04:05-070000", "1871-01-01T05:33:02-003408", -(34*60 + 8)},
|
||||
{"2006-01-02T15:04:05-07:00:00", "1871-01-01T05:33:02-00:34:08", -(34*60 + 8)},
|
||||
{"2006-01-02T15:04:05-070000", "1871-01-01T05:33:02+003408", 34*60 + 8},
|
||||
{"2006-01-02T15:04:05-07:00:00", "1871-01-01T05:33:02+00:34:08", 34*60 + 8},
|
||||
{"2006-01-02T15:04:05Z070000", "1871-01-01T05:33:02-003408", -(34*60 + 8)},
|
||||
{"2006-01-02T15:04:05Z07:00:00", "1871-01-01T05:33:02+00:34:08", 34*60 + 8},
|
||||
}
|
||||
|
||||
func TestParseSecondsInTimeZone(t *testing.T) {
|
||||
// should accept timezone offsets with seconds like: Zone America/New_York -4:56:02 - LMT 1883 Nov 18 12:03:58
|
||||
for _, test := range secondsTimeZoneOffsetTests {
|
||||
time, err := Parse(test.format, test.value)
|
||||
if err != nil {
|
||||
t.Fatal("error parsing date:", err)
|
||||
}
|
||||
_, offset := time.Zone()
|
||||
if offset != test.expectedoffset {
|
||||
t.Errorf("ZoneOffset = %d, want %d", offset, test.expectedoffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatSecondsInTimeZone(t *testing.T) {
|
||||
d := Date(1871, 9, 17, 20, 4, 26, 0, FixedZone("LMT", -(34*60+8)))
|
||||
timestr := d.Format("2006-01-02T15:04:05Z070000")
|
||||
expected := "1871-09-17T20:04:26-003408"
|
||||
if timestr != expected {
|
||||
t.Errorf("Got %s, want %s", timestr, expected)
|
||||
}
|
||||
}
|
||||
|
||||
type ISOWeekTest struct {
|
||||
year int // year
|
||||
month, day int // month and day
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue