mirror of
https://github.com/golang/go
synced 2024-11-20 10:04:45 -07:00
time: remove incorrect time.ISO8601 and add time.RFC3339
Fixes #734. R=rsc, r CC=golang-dev https://golang.org/cl/975042
This commit is contained in:
parent
a83c5f5cad
commit
6c124cb879
@ -24,6 +24,15 @@ const (
|
|||||||
// may be replaced by a digit if the following number
|
// may be replaced by a digit if the following number
|
||||||
// (a day) has two digits; for compatibility with
|
// (a day) has two digits; for compatibility with
|
||||||
// fixed-width Unix time formats.
|
// fixed-width Unix time formats.
|
||||||
|
//
|
||||||
|
// Numeric time zone offsets format as follows:
|
||||||
|
// -0700 ±hhmm
|
||||||
|
// -07:00 ±hh:mm
|
||||||
|
// Replacing the sign in the format with a Z triggers
|
||||||
|
// the ISO 8601 behavior of printing Z instead of an
|
||||||
|
// offset for the UTC zone. Thus:
|
||||||
|
// Z0700 Z or ±hhmm
|
||||||
|
// Z07:00 Z or ±hh:mm
|
||||||
const (
|
const (
|
||||||
ANSIC = "Mon Jan _2 15:04:05 2006"
|
ANSIC = "Mon Jan _2 15:04:05 2006"
|
||||||
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
|
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
|
||||||
@ -34,35 +43,35 @@ const (
|
|||||||
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
|
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
|
||||||
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
|
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
|
||||||
Kitchen = "3:04PM"
|
Kitchen = "3:04PM"
|
||||||
// Special case: use Z to get the time zone formatted according to ISO 8601,
|
RFC3339 = "2006-01-02T15:04:05Z07:00"
|
||||||
// which is -0700 or Z for UTC
|
|
||||||
ISO8601 = "2006-01-02T15:04:05Z"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
stdLongMonth = "January"
|
stdLongMonth = "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 = "15"
|
stdHour = "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 = "2006"
|
stdLongYear = "2006"
|
||||||
stdYear = "06"
|
stdYear = "06"
|
||||||
stdPM = "PM"
|
stdPM = "PM"
|
||||||
stdpm = "pm"
|
stdpm = "pm"
|
||||||
stdTZ = "MST"
|
stdTZ = "MST"
|
||||||
stdISO8601TZ = "Z" // prints Z for UTC
|
stdISO8601TZ = "Z0700" // prints Z for UTC
|
||||||
stdNumTZ = "-0700" // always numeric
|
stdISO8601ColonTZ = "Z07:00" // prints Z for UTC
|
||||||
|
stdNumTZ = "-0700" // always numeric
|
||||||
|
stdNumColonTZ = "-07:00" // always numeric
|
||||||
)
|
)
|
||||||
|
|
||||||
// nextStdChunk finds the first occurrence of a std string in
|
// nextStdChunk finds the first occurrence of a std string in
|
||||||
@ -113,7 +122,7 @@ func nextStdChunk(layout string) (prefix, std, suffix string) {
|
|||||||
return layout[0:i], stdUnderDay, layout[i+2:]
|
return layout[0:i], stdUnderDay, layout[i+2:]
|
||||||
}
|
}
|
||||||
|
|
||||||
case '3', '4', '5', 'Z': // 3, 4, 5, Z
|
case '3', '4', '5': // 3, 4, 5
|
||||||
return layout[0:i], layout[i : i+1], layout[i+1:]
|
return layout[0:i], layout[i : i+1], layout[i+1:]
|
||||||
|
|
||||||
case 'P': // PM
|
case 'P': // PM
|
||||||
@ -126,10 +135,20 @@ func nextStdChunk(layout string) (prefix, std, suffix string) {
|
|||||||
return layout[0:i], layout[i : i+2], layout[i+2:]
|
return layout[0:i], layout[i : i+2], layout[i+2:]
|
||||||
}
|
}
|
||||||
|
|
||||||
case '-': // -0700
|
case '-': // -0700, -07:00
|
||||||
if len(layout) >= i+5 && layout[i:i+5] == stdNumTZ {
|
if len(layout) >= i+5 && layout[i:i+5] == stdNumTZ {
|
||||||
return layout[0:i], layout[i : i+5], layout[i+5:]
|
return layout[0:i], layout[i : i+5], layout[i+5:]
|
||||||
}
|
}
|
||||||
|
if len(layout) >= i+6 && layout[i:i+6] == stdNumColonTZ {
|
||||||
|
return layout[0:i], layout[i : i+6], layout[i+6:]
|
||||||
|
}
|
||||||
|
case 'Z': // Z0700, Z07:00
|
||||||
|
if len(layout) >= i+5 && layout[i:i+5] == stdISO8601TZ {
|
||||||
|
return layout[0:i], layout[i : i+5], layout[i+5:]
|
||||||
|
}
|
||||||
|
if len(layout) >= i+6 && layout[i:i+6] == stdISO8601ColonTZ {
|
||||||
|
return layout[0:i], layout[i : i+6], layout[i+6:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return layout, "", ""
|
return layout, "", ""
|
||||||
@ -210,7 +229,7 @@ func zeroPad(i int) string { return pad(i, "0") }
|
|||||||
// according to layout. The layout defines the format by showing the
|
// according to layout. The layout defines the format by showing the
|
||||||
// representation of a standard time, which is then used to describe
|
// representation of a standard time, which is then used to describe
|
||||||
// the time to be formatted. Predefined layouts ANSIC, UnixDate,
|
// the time to be formatted. Predefined layouts ANSIC, UnixDate,
|
||||||
// ISO8601 and others describe standard representations.
|
// RFC3339 and others describe standard representations.
|
||||||
func (t *Time) Format(layout string) string {
|
func (t *Time) Format(layout string) string {
|
||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
// Each iteration generates one std value.
|
// Each iteration generates one std value.
|
||||||
@ -258,10 +277,10 @@ func (t *Time) Format(layout string) string {
|
|||||||
p = strconv.Itoa(t.Second)
|
p = strconv.Itoa(t.Second)
|
||||||
case stdZeroSecond:
|
case stdZeroSecond:
|
||||||
p = zeroPad(t.Second)
|
p = zeroPad(t.Second)
|
||||||
case stdISO8601TZ, stdNumTZ:
|
case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ:
|
||||||
// Ugly special case. We cheat and take "Z" to mean "the time
|
// Ugly special case. We cheat and take the "Z" variants
|
||||||
// zone as formatted for ISO 8601".
|
// to mean "the time zone as formatted for ISO 8601".
|
||||||
if std == stdISO8601TZ && t.ZoneOffset == 0 {
|
if t.ZoneOffset == 0 && std[0] == 'Z' {
|
||||||
p = "Z"
|
p = "Z"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -273,6 +292,9 @@ func (t *Time) Format(layout string) string {
|
|||||||
p = "+"
|
p = "+"
|
||||||
}
|
}
|
||||||
p += zeroPad(zone / 60)
|
p += zeroPad(zone / 60)
|
||||||
|
if std == stdISO8601ColonTZ || std == stdNumColonTZ {
|
||||||
|
p += ":"
|
||||||
|
}
|
||||||
p += zeroPad(zone % 60)
|
p += zeroPad(zone % 60)
|
||||||
case stdPM:
|
case stdPM:
|
||||||
if t.Hour >= 12 {
|
if t.Hour >= 12 {
|
||||||
@ -383,7 +405,7 @@ func skip(value, prefix string) (string, os.Error) {
|
|||||||
// Parse parses a formatted string and returns the time value it represents.
|
// Parse parses a formatted string and returns the time value it represents.
|
||||||
// The layout defines the format by showing the representation of a standard
|
// The layout defines the format by showing the representation of a standard
|
||||||
// time, which is then used to describe the string to be parsed. Predefined
|
// time, which is then used to describe the string to be parsed. Predefined
|
||||||
// layouts ANSIC, UnixDate, ISO8601 and others describe standard
|
// layouts ANSIC, UnixDate, RFC3339 and others describe standard
|
||||||
// representations.
|
// representations.
|
||||||
//
|
//
|
||||||
// Only those elements present in the value will be set in the returned time
|
// Only those elements present in the value will be set in the returned time
|
||||||
@ -475,22 +497,34 @@ func Parse(alayout, avalue string) (*Time, os.Error) {
|
|||||||
if t.Second < 0 || 60 <= t.Second {
|
if t.Second < 0 || 60 <= t.Second {
|
||||||
rangeErrString = "second"
|
rangeErrString = "second"
|
||||||
}
|
}
|
||||||
case stdISO8601TZ, stdNumTZ:
|
case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ:
|
||||||
if std == stdISO8601TZ && len(value) >= 1 && value[0] == 'Z' {
|
if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' {
|
||||||
value = value[1:]
|
value = value[1:]
|
||||||
t.Zone = "UTC"
|
t.Zone = "UTC"
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if len(value) < 5 {
|
var sign, hh, mm string
|
||||||
err = errBad
|
if std == stdISO8601ColonTZ || std == stdNumColonTZ {
|
||||||
break
|
if len(value) < 6 {
|
||||||
|
err = errBad
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if value[3] != ':' {
|
||||||
|
err = errBad
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sign, hh, mm, value = value[0:1], value[1:3], value[4:6], value[6:]
|
||||||
|
} else {
|
||||||
|
if len(value) < 5 {
|
||||||
|
err = errBad
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sign, hh, mm, value = value[0:1], value[1:3], value[3:5], value[5:]
|
||||||
}
|
}
|
||||||
var sign string
|
|
||||||
sign, p, value = value[0:1], value[1:5], value[5:]
|
|
||||||
var hr, min int
|
var hr, min int
|
||||||
hr, err = strconv.Atoi(p[0:2])
|
hr, err = strconv.Atoi(hh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
min, err = strconv.Atoi(p[2:4])
|
min, err = strconv.Atoi(mm)
|
||||||
}
|
}
|
||||||
t.ZoneOffset = (hr*60 + min) * 60 // offset is in seconds
|
t.ZoneOffset = (hr*60 + min) * 60 // offset is in seconds
|
||||||
switch sign[0] {
|
switch sign[0] {
|
||||||
|
@ -107,18 +107,18 @@ type TimeFormatTest struct {
|
|||||||
formattedValue string
|
formattedValue string
|
||||||
}
|
}
|
||||||
|
|
||||||
var iso8601Formats = []TimeFormatTest{
|
var rfc3339Formats = []TimeFormatTest{
|
||||||
TimeFormatTest{Time{2008, 9, 17, 20, 4, 26, Wednesday, 0, "UTC"}, "2008-09-17T20:04:26Z"},
|
TimeFormatTest{Time{2008, 9, 17, 20, 4, 26, Wednesday, 0, "UTC"}, "2008-09-17T20:04:26Z"},
|
||||||
TimeFormatTest{Time{1994, 9, 17, 20, 4, 26, Wednesday, -18000, "EST"}, "1994-09-17T20:04:26-0500"},
|
TimeFormatTest{Time{1994, 9, 17, 20, 4, 26, Wednesday, -18000, "EST"}, "1994-09-17T20:04:26-05:00"},
|
||||||
TimeFormatTest{Time{2000, 12, 26, 1, 15, 6, Wednesday, 15600, "OTO"}, "2000-12-26T01:15:06+0420"},
|
TimeFormatTest{Time{2000, 12, 26, 1, 15, 6, Wednesday, 15600, "OTO"}, "2000-12-26T01:15:06+04:20"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestISO8601Conversion(t *testing.T) {
|
func TestRFC3339Conversion(t *testing.T) {
|
||||||
for _, f := range iso8601Formats {
|
for _, f := range rfc3339Formats {
|
||||||
if f.time.Format(ISO8601) != f.formattedValue {
|
if f.time.Format(RFC3339) != f.formattedValue {
|
||||||
t.Error("ISO8601:")
|
t.Error("RFC3339:")
|
||||||
t.Errorf(" want=%+v", f.formattedValue)
|
t.Errorf(" want=%+v", f.formattedValue)
|
||||||
t.Errorf(" have=%+v", f.time.Format(ISO8601))
|
t.Errorf(" have=%+v", f.time.Format(RFC3339))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ var formatTests = []FormatTest{
|
|||||||
FormatTest{"RFC822", RFC822, "04 Feb 10 2100 PST"},
|
FormatTest{"RFC822", RFC822, "04 Feb 10 2100 PST"},
|
||||||
FormatTest{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST"},
|
FormatTest{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST"},
|
||||||
FormatTest{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST"},
|
FormatTest{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST"},
|
||||||
FormatTest{"ISO8601", ISO8601, "2010-02-04T21:00:57-0800"},
|
FormatTest{"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00"},
|
||||||
FormatTest{"Kitchen", Kitchen, "9:00PM"},
|
FormatTest{"Kitchen", Kitchen, "9:00PM"},
|
||||||
FormatTest{"am/pm", "3pm", "9pm"},
|
FormatTest{"am/pm", "3pm", "9pm"},
|
||||||
FormatTest{"AM/PM", "3PM", "9PM"},
|
FormatTest{"AM/PM", "3PM", "9PM"},
|
||||||
@ -168,7 +168,7 @@ var parseTests = []ParseTest{
|
|||||||
ParseTest{"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1},
|
ParseTest{"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1},
|
||||||
ParseTest{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST", true, true, 1},
|
ParseTest{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST", true, true, 1},
|
||||||
ParseTest{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST", true, true, 1},
|
ParseTest{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST", true, true, 1},
|
||||||
ParseTest{"ISO8601", ISO8601, "2010-02-04T21:00:57-0800", true, false, 1},
|
ParseTest{"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00", true, false, 1},
|
||||||
// Amount of white space should not matter.
|
// Amount of white space should not matter.
|
||||||
ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1},
|
ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1},
|
||||||
ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1},
|
ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1},
|
||||||
@ -234,7 +234,7 @@ func checkTime(time *Time, test *ParseTest, t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatAndParse(t *testing.T) {
|
func TestFormatAndParse(t *testing.T) {
|
||||||
const fmt = "Mon MST " + ISO8601 // all fields
|
const fmt = "Mon MST " + RFC3339 // all fields
|
||||||
f := func(sec int64) bool {
|
f := func(sec int64) bool {
|
||||||
t1 := SecondsToLocalTime(sec)
|
t1 := SecondsToLocalTime(sec)
|
||||||
if t1.Year < 1000 || t1.Year > 9999 {
|
if t1.Year < 1000 || t1.Year > 9999 {
|
||||||
|
Loading…
Reference in New Issue
Block a user