From: Ulf Holm Nielsen Date: Wed, 31 Jul 2013 06:11:02 +0000 (+1000) Subject: time: Allow Parse and Format to handle time zone offsets with seconds X-Git-Tag: go1.2rc2~873 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=aa38aeaeaf5d9a87b490fa16de9de850a9f5956c;p=gostls13.git 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 --- diff --git a/src/pkg/time/format.go b/src/pkg/time/format.go index 7aa60e75dc..f5bb6291dd 100644 --- a/src/pkg/time/format.go +++ b/src/pkg/time/format.go @@ -59,35 +59,39 @@ const ( ) const ( - _ = iota - stdLongMonth = iota + stdNeedDate // "January" - stdMonth // "Jan" - stdNumMonth // "1" - stdZeroMonth // "01" - stdLongWeekDay // "Monday" - stdWeekDay // "Mon" - stdDay // "2" - stdUnderDay // "_2" - stdZeroDay // "02" - stdHour = iota + stdNeedClock // "15" - stdHour12 // "3" - stdZeroHour12 // "03" - stdMinute // "4" - stdZeroMinute // "04" - stdSecond // "5" - stdZeroSecond // "05" - stdLongYear = iota + stdNeedDate // "2006" - stdYear // "06" - stdPM = iota + stdNeedClock // "PM" - stdpm // "pm" - stdTZ = iota // "MST" - stdISO8601TZ // "Z0700" // prints Z for UTC - stdISO8601ColonTZ // "Z07:00" // prints Z for UTC - stdNumTZ // "-0700" // always numeric - stdNumShortTZ // "-07" // always numeric - stdNumColonTZ // "-07:00" // always numeric - stdFracSecond0 // ".0", ".00", ... , trailing zeros included - stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted + _ = iota + stdLongMonth = iota + stdNeedDate // "January" + stdMonth // "Jan" + stdNumMonth // "1" + stdZeroMonth // "01" + stdLongWeekDay // "Monday" + stdWeekDay // "Mon" + stdDay // "2" + stdUnderDay // "_2" + stdZeroDay // "02" + stdHour = iota + stdNeedClock // "15" + stdHour12 // "3" + stdZeroHour12 // "03" + stdMinute // "4" + stdZeroMinute // "04" + stdSecond // "5" + stdZeroSecond // "05" + stdLongYear = iota + stdNeedDate // "2006" + stdYear // "06" + stdPM = iota + stdNeedClock // "PM" + 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 stdNeedDate = 1 << 8 // need month, day, year stdNeedClock = 2 << 8 // need hour, minute, second @@ -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 '-': diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go index 2caaf698b7..703e2be9a5 100644 --- a/src/pkg/time/time_test.go +++ b/src/pkg/time/time_test.go @@ -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