]> Cypherpunks repositories - gostls13.git/commitdiff
time: remove incorrect time.ISO8601 and add time.RFC3339
authorMicah Stetson <micah.stetson@gmail.com>
Tue, 27 Apr 2010 07:05:24 +0000 (00:05 -0700)
committerRuss Cox <rsc@golang.org>
Tue, 27 Apr 2010 07:05:24 +0000 (00:05 -0700)
Fixes #734.

R=rsc, r
CC=golang-dev
https://golang.org/cl/975042

src/pkg/time/format.go
src/pkg/time/time_test.go

index dbf4f38a0fc18a2631fe14240660031077ed9da9..226826aca2f0b7b335ddfc8f5d4153abd55337f7 100644 (file)
@@ -24,6 +24,15 @@ const (
 // may be replaced by a digit if the following number
 // (a day) has two digits; for compatibility with
 // 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 (
        ANSIC    = "Mon Jan _2 15:04:05 2006"
        UnixDate = "Mon Jan _2 15:04:05 MST 2006"
@@ -34,35 +43,35 @@ const (
        RFC850  = "Monday, 02-Jan-06 15:04:05 MST"
        RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
        Kitchen = "3:04PM"
-       // Special case: use Z to get the time zone formatted according to ISO 8601,
-       // which is -0700 or Z for UTC
-       ISO8601 = "2006-01-02T15:04:05Z"
+       RFC3339 = "2006-01-02T15:04:05Z07:00"
 )
 
 const (
-       stdLongMonth   = "January"
-       stdMonth       = "Jan"
-       stdNumMonth    = "1"
-       stdZeroMonth   = "01"
-       stdLongWeekDay = "Monday"
-       stdWeekDay     = "Mon"
-       stdDay         = "2"
-       stdUnderDay    = "_2"
-       stdZeroDay     = "02"
-       stdHour        = "15"
-       stdHour12      = "3"
-       stdZeroHour12  = "03"
-       stdMinute      = "4"
-       stdZeroMinute  = "04"
-       stdSecond      = "5"
-       stdZeroSecond  = "05"
-       stdLongYear    = "2006"
-       stdYear        = "06"
-       stdPM          = "PM"
-       stdpm          = "pm"
-       stdTZ          = "MST"
-       stdISO8601TZ   = "Z"     // prints Z for UTC
-       stdNumTZ       = "-0700" // always numeric
+       stdLongMonth      = "January"
+       stdMonth          = "Jan"
+       stdNumMonth       = "1"
+       stdZeroMonth      = "01"
+       stdLongWeekDay    = "Monday"
+       stdWeekDay        = "Mon"
+       stdDay            = "2"
+       stdUnderDay       = "_2"
+       stdZeroDay        = "02"
+       stdHour           = "15"
+       stdHour12         = "3"
+       stdZeroHour12     = "03"
+       stdMinute         = "4"
+       stdZeroMinute     = "04"
+       stdSecond         = "5"
+       stdZeroSecond     = "05"
+       stdLongYear       = "2006"
+       stdYear           = "06"
+       stdPM             = "PM"
+       stdpm             = "pm"
+       stdTZ             = "MST"
+       stdISO8601TZ      = "Z0700"  // prints Z for UTC
+       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
@@ -113,7 +122,7 @@ func nextStdChunk(layout string) (prefix, std, suffix string) {
                                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:]
 
                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:]
                        }
 
-               case '-': // -0700
+               case '-': // -0700, -07:00
                        if len(layout) >= i+5 && layout[i:i+5] == stdNumTZ {
                                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, "", ""
@@ -210,7 +229,7 @@ func zeroPad(i int) string { return pad(i, "0") }
 // according to layout.  The layout defines the format by showing the
 // representation of a standard time, which is then used to describe
 // 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 {
        b := new(bytes.Buffer)
        // Each iteration generates one std value.
@@ -258,10 +277,10 @@ func (t *Time) Format(layout string) string {
                        p = strconv.Itoa(t.Second)
                case stdZeroSecond:
                        p = zeroPad(t.Second)
-               case stdISO8601TZ, stdNumTZ:
-                       // Ugly special case.  We cheat and take "Z" to mean "the time
-                       // zone as formatted for ISO 8601".
-                       if std == stdISO8601TZ && t.ZoneOffset == 0 {
+               case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ:
+                       // Ugly special case.  We cheat and take the "Z" variants
+                       // to mean "the time zone as formatted for ISO 8601".
+                       if t.ZoneOffset == 0 && std[0] == 'Z' {
                                p = "Z"
                                break
                        }
@@ -273,6 +292,9 @@ func (t *Time) Format(layout string) string {
                                p = "+"
                        }
                        p += zeroPad(zone / 60)
+                       if std == stdISO8601ColonTZ || std == stdNumColonTZ {
+                               p += ":"
+                       }
                        p += zeroPad(zone % 60)
                case stdPM:
                        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.
 // 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
-// layouts ANSIC, UnixDate, ISO8601 and others describe standard
+// layouts ANSIC, UnixDate, RFC3339 and others describe standard
 // representations.
 //
 // 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 {
                                rangeErrString = "second"
                        }
-               case stdISO8601TZ, stdNumTZ:
-                       if std == stdISO8601TZ && len(value) >= 1 && value[0] == 'Z' {
+               case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ:
+                       if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' {
                                value = value[1:]
                                t.Zone = "UTC"
                                break
                        }
-                       if len(value) < 5 {
-                               err = errBad
-                               break
+                       var sign, hh, mm string
+                       if std == stdISO8601ColonTZ || std == stdNumColonTZ {
+                               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
-                       hr, err = strconv.Atoi(p[0:2])
+                       hr, err = strconv.Atoi(hh)
                        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
                        switch sign[0] {
index a5a680986cf2799a76ee2ecb0d57143938c0102a..32bf9652ee08523b62cb522ebfd58c3a0975d69e 100644 (file)
@@ -107,18 +107,18 @@ type TimeFormatTest struct {
        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{1994, 9, 17, 20, 4, 26, Wednesday, -18000, "EST"}, "1994-09-17T20:04:26-0500"},
-       TimeFormatTest{Time{2000, 12, 26, 1, 15, 6, Wednesday, 15600, "OTO"}, "2000-12-26T01:15:06+0420"},
+       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+04:20"},
 }
 
-func TestISO8601Conversion(t *testing.T) {
-       for _, f := range iso8601Formats {
-               if f.time.Format(ISO8601) != f.formattedValue {
-                       t.Error("ISO8601:")
+func TestRFC3339Conversion(t *testing.T) {
+       for _, f := range rfc3339Formats {
+               if f.time.Format(RFC3339) != f.formattedValue {
+                       t.Error("RFC3339:")
                        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{"RFC850", RFC850, "Thursday, 04-Feb-10 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{"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{"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{"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.
        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) {
-       const fmt = "Mon MST " + ISO8601 // all fields
+       const fmt = "Mon MST " + RFC3339 // all fields
        f := func(sec int64) bool {
                t1 := SecondsToLocalTime(sec)
                if t1.Year < 1000 || t1.Year > 9999 {