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
|
|
@ -59,35 +59,39 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_ = iota
|
_ = iota
|
||||||
stdLongMonth = iota + stdNeedDate // "January"
|
stdLongMonth = iota + stdNeedDate // "January"
|
||||||
stdMonth // "Jan"
|
stdMonth // "Jan"
|
||||||
stdNumMonth // "1"
|
stdNumMonth // "1"
|
||||||
stdZeroMonth // "01"
|
stdZeroMonth // "01"
|
||||||
stdLongWeekDay // "Monday"
|
stdLongWeekDay // "Monday"
|
||||||
stdWeekDay // "Mon"
|
stdWeekDay // "Mon"
|
||||||
stdDay // "2"
|
stdDay // "2"
|
||||||
stdUnderDay // "_2"
|
stdUnderDay // "_2"
|
||||||
stdZeroDay // "02"
|
stdZeroDay // "02"
|
||||||
stdHour = iota + stdNeedClock // "15"
|
stdHour = iota + stdNeedClock // "15"
|
||||||
stdHour12 // "3"
|
stdHour12 // "3"
|
||||||
stdZeroHour12 // "03"
|
stdZeroHour12 // "03"
|
||||||
stdMinute // "4"
|
stdMinute // "4"
|
||||||
stdZeroMinute // "04"
|
stdZeroMinute // "04"
|
||||||
stdSecond // "5"
|
stdSecond // "5"
|
||||||
stdZeroSecond // "05"
|
stdZeroSecond // "05"
|
||||||
stdLongYear = iota + stdNeedDate // "2006"
|
stdLongYear = iota + stdNeedDate // "2006"
|
||||||
stdYear // "06"
|
stdYear // "06"
|
||||||
stdPM = iota + stdNeedClock // "PM"
|
stdPM = iota + stdNeedClock // "PM"
|
||||||
stdpm // "pm"
|
stdpm // "pm"
|
||||||
stdTZ = iota // "MST"
|
stdTZ = iota // "MST"
|
||||||
stdISO8601TZ // "Z0700" // prints Z for UTC
|
stdISO8601TZ // "Z0700" // prints Z for UTC
|
||||||
stdISO8601ColonTZ // "Z07:00" // prints Z for UTC
|
stdISO8601SecondsTZ // "Z070000"
|
||||||
stdNumTZ // "-0700" // always numeric
|
stdISO8601ColonTZ // "Z07:00" // prints Z for UTC
|
||||||
stdNumShortTZ // "-07" // always numeric
|
stdISO8601ColonSecondsTZ // "Z07:00:00"
|
||||||
stdNumColonTZ // "-07:00" // always numeric
|
stdNumTZ // "-0700" // always numeric
|
||||||
stdFracSecond0 // ".0", ".00", ... , trailing zeros included
|
stdNumSecondsTz // "-070000"
|
||||||
stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted
|
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
|
stdNeedDate = 1 << 8 // need month, day, year
|
||||||
stdNeedClock = 2 << 8 // need hour, minute, second
|
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:]
|
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" {
|
if len(layout) >= i+5 && layout[i:i+5] == "-0700" {
|
||||||
return layout[0:i], stdNumTZ, layout[i+5:]
|
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" {
|
if len(layout) >= i+3 && layout[i:i+3] == "-07" {
|
||||||
return layout[0:i], stdNumShortTZ, layout[i+3:]
|
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" {
|
if len(layout) >= i+5 && layout[i:i+5] == "Z0700" {
|
||||||
return layout[0:i], stdISO8601TZ, layout[i+5:]
|
return layout[0:i], stdISO8601TZ, layout[i+5:]
|
||||||
}
|
}
|
||||||
if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" {
|
if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" {
|
||||||
return layout[0:i], stdISO8601ColonTZ, layout[i+6:]
|
return layout[0:i], stdISO8601ColonTZ, layout[i+6:]
|
||||||
}
|
}
|
||||||
|
|
||||||
case '.': // .000 or .999 - repeated digits for fractional seconds.
|
case '.': // .000 or .999 - repeated digits for fractional seconds.
|
||||||
if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
|
if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
|
||||||
ch := layout[i+1]
|
ch := layout[i+1]
|
||||||
|
|
@ -507,17 +525,19 @@ func (t Time) Format(layout string) string {
|
||||||
} else {
|
} else {
|
||||||
b = append(b, "am"...)
|
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
|
// Ugly special case. We cheat and take the "Z" variants
|
||||||
// to mean "the time zone as formatted for ISO 8601".
|
// 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')
|
b = append(b, 'Z')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
zone := offset / 60 // convert to minutes
|
zone := offset / 60 // convert to minutes
|
||||||
|
absoffset := offset
|
||||||
if zone < 0 {
|
if zone < 0 {
|
||||||
b = append(b, '-')
|
b = append(b, '-')
|
||||||
zone = -zone
|
zone = -zone
|
||||||
|
absoffset = -absoffset
|
||||||
} else {
|
} else {
|
||||||
b = append(b, '+')
|
b = append(b, '+')
|
||||||
}
|
}
|
||||||
|
|
@ -526,6 +546,15 @@ func (t Time) Format(layout string) string {
|
||||||
b = append(b, ':')
|
b = append(b, ':')
|
||||||
}
|
}
|
||||||
b = appendUint(b, uint(zone%60), '0')
|
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:
|
case stdTZ:
|
||||||
if name != "" {
|
if name != "" {
|
||||||
b = append(b, name...)
|
b = append(b, name...)
|
||||||
|
|
@ -821,13 +850,13 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
|
||||||
default:
|
default:
|
||||||
err = errBad
|
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' {
|
if (std == stdISO8601TZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' {
|
||||||
value = value[1:]
|
value = value[1:]
|
||||||
z = UTC
|
z = UTC
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
var sign, hour, min string
|
var sign, hour, min, seconds string
|
||||||
if std == stdISO8601ColonTZ || std == stdNumColonTZ {
|
if std == stdISO8601ColonTZ || std == stdNumColonTZ {
|
||||||
if len(value) < 6 {
|
if len(value) < 6 {
|
||||||
err = errBad
|
err = errBad
|
||||||
|
|
@ -837,26 +866,45 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
|
||||||
err = errBad
|
err = errBad
|
||||||
break
|
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 {
|
} else if std == stdNumShortTZ {
|
||||||
if len(value) < 3 {
|
if len(value) < 3 {
|
||||||
err = errBad
|
err = errBad
|
||||||
break
|
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 {
|
} else {
|
||||||
if len(value) < 5 {
|
if len(value) < 5 {
|
||||||
err = errBad
|
err = errBad
|
||||||
break
|
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)
|
hr, err = atoi(hour)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
mm, err = atoi(min)
|
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] {
|
switch sign[0] {
|
||||||
case '+':
|
case '+':
|
||||||
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 {
|
type ISOWeekTest struct {
|
||||||
year int // year
|
year int // year
|
||||||
month, day int // month and day
|
month, day int // month and day
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue