clean up handling of numeric time zones
allow formatting of ruby-style times.
Fixes #518.
R=rsc
CC=golang-dev
https://golang.org/cl/186119
diff --git a/src/pkg/time/format.go b/src/pkg/time/format.go
index 745d9fb..0e885d4 100644
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -11,6 +11,8 @@
numeric = iota
alphabetic
separator
+ plus
+ minus
)
// These are predefined layouts for use in Time.Format.
@@ -25,6 +27,7 @@
const (
ANSIC = "Mon Jan _2 15:04:05 2006"
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
+ RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
Kitchen = "3:04PM"
@@ -56,7 +59,8 @@
stdPM = "PM"
stdpm = "pm"
stdTZ = "MST"
- stdISO8601TZ = "Z"
+ stdISO8601TZ = "Z" // prints Z for UTC
+ stdNumTZ = "0700" // always numeric
)
var longDayNames = []string{
@@ -126,8 +130,12 @@
return numeric
case c == '_': // underscore; treated like a number when printing
return numeric
- case 'a' <= c && c < 'z', 'A' <= c && c <= 'Z':
+ case 'a' <= c && c <= 'z', 'A' <= c && c <= 'Z':
return alphabetic
+ case c == '+':
+ return plus
+ case c == '-':
+ return minus
}
return separator
}
@@ -198,19 +206,31 @@
p = zeroPad(t.Second)
case stdZulu:
p = zeroPad(t.Hour) + zeroPad(t.Minute)
- case stdISO8601TZ:
- // Rather ugly special case, required because the time zone is too broken down
- // in this format to recognize easily. We cheat and take "Z" to mean "the time
+ case stdISO8601TZ, stdNumTZ:
+ // Ugly special case. We cheat and take "Z" to mean "the time
// zone as formatted for ISO 8601".
- if t.ZoneOffset == 0 {
+ zone := t.ZoneOffset / 60 // conver to minutes
+ if p == stdISO8601TZ && t.ZoneOffset == 0 {
p = "Z"
} else {
- zone := t.ZoneOffset / 60 // minutes
- if zone < 0 {
- p = "-"
- zone = -zone
+ // If the reference time is stdNumTZ (0700), the sign has already been
+ // emitted but may be wrong. For stdISO8601TZ we must print it.
+ if p == stdNumTZ && b.Len() > 0 {
+ soFar := b.Bytes()
+ if soFar[len(soFar)-1] == '-' && zone >= 0 {
+ // fix the sign
+ soFar[len(soFar)-1] = '+'
+ } else {
+ zone = -zone
+ }
+ p = ""
} else {
- p = "+"
+ if zone < 0 {
+ p = "-"
+ zone = -zone
+ } else {
+ p = "+"
+ }
}
p += zeroPad(zone / 60)
p += zeroPad(zone % 60)
@@ -294,8 +314,8 @@
rangeErrString := "" // set if a value is out of range
pmSet := false // do we need to add 12 to the hour?
// Each iteration steps along one piece
- nextIsYear := false // whether next item is a Year; means we saw a minus sign.
layout, value := alayout, avalue
+ sign := "" // pending + or - from previous iteration
for len(layout) > 0 && len(value) > 0 {
c := layout[0]
pieceType := charType(c)
@@ -303,10 +323,12 @@
for i = 0; i < len(layout) && charType(layout[i]) == pieceType; i++ {
}
reference := layout[0:i]
+ prevLayout := layout
layout = layout[i:]
- if reference == "Z" {
+ // Ugly time zone handling.
+ if reference == "Z" || reference == "z" {
// Special case for ISO8601 time zone: "Z" or "-0800"
- if value[0] == 'Z' {
+ if reference == "Z" && value[0] == 'Z' {
i = 1
} else if len(value) >= 5 {
i = 5
@@ -316,6 +338,13 @@
} else {
c = value[0]
if charType(c) != pieceType {
+ // could be a minus sign introducing a negative year
+ if c == '-' && pieceType != minus {
+ value = value[1:]
+ sign = "-"
+ layout = prevLayout // don't consume reference item
+ continue
+ }
return nil, &ParseError{Layout: alayout, Value: avalue, Message: formatErr + alayout}
}
for i = 0; i < len(value) && charType(value[i]) == pieceType; i++ {
@@ -323,21 +352,17 @@
}
p := value[0:i]
value = value[i:]
- // Separators must match but:
- // - initial run of spaces is treated as a single space
- // - there could be a following minus sign for negative years
- if pieceType == separator {
- if len(p) != len(reference) {
- // must be exactly a following minus sign
- pp := collapseSpaces(p)
- rr := collapseSpaces(reference)
- if pp != rr {
- if len(pp) != len(rr)+1 || p[len(pp)-1] != '-' {
- return nil, &ParseError{Layout: alayout, Value: avalue, Message: formatErr + alayout}
- }
- nextIsYear = true
- continue
- }
+ switch pieceType {
+ case separator:
+ // Separators must match but initial run of spaces is treated as a single space.
+ if collapseSpaces(p) != collapseSpaces(reference) {
+ return nil, &ParseError{Layout: alayout, Value: avalue, Message: formatErr + alayout}
+ }
+ continue
+ case plus, minus:
+ if len(p) == 1 { // ++ or -- don't count as signs.
+ sign = p
+ continue
}
}
var err os.Error
@@ -351,9 +376,8 @@
}
case stdLongYear:
t.Year, err = strconv.Atoi64(p)
- if nextIsYear {
+ if sign == "-" {
t.Year = -t.Year
- nextIsYear = false
}
case stdMonth:
t.Month, err = lookup(shortMonthNames, p)
@@ -403,19 +427,25 @@
if err != nil {
t.Minute, err = strconv.Atoi(p[2:4])
}
- case stdISO8601TZ:
- if p == "Z" {
- t.Zone = "UTC"
- break
+ case stdISO8601TZ, stdNumTZ:
+ if reference == stdISO8601TZ {
+ if p == "Z" {
+ t.Zone = "UTC"
+ break
+ }
+ // len(p) known to be 5: "-0800"
+ sign = p[0:1]
+ p = p[1:]
+ } else {
+ // len(p) known to be 4: "0800" and sign is set
}
- // len(p) known to be 5: "-0800"
var hr, min int
- hr, err = strconv.Atoi(p[1:3])
+ hr, err = strconv.Atoi(p[0:2])
if err != nil {
- min, err = strconv.Atoi(p[3:5])
+ min, err = strconv.Atoi(p[2:4])
}
t.ZoneOffset = (hr*60 + min) * 60 // offset is in seconds
- switch p[0] {
+ switch sign[0] {
case '+':
case '-':
t.ZoneOffset = -t.ZoneOffset
@@ -463,16 +493,13 @@
}
}
}
- if nextIsYear {
- // Means we didn't see a year when we were expecting one
- return nil, &ParseError{Layout: alayout, Value: value, Message: formatErr + alayout}
- }
if rangeErrString != "" {
return nil, &ParseError{alayout, avalue, reference, p, ": " + rangeErrString + " out of range"}
}
if err != nil {
return nil, &ParseError{alayout, avalue, reference, p, ""}
}
+ sign = ""
}
if pmSet && t.Hour < 12 {
t.Hour += 12
diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go
index 5036ceb..af1e50f 100644
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -132,6 +132,7 @@
var formatTests = []FormatTest{
FormatTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010"},
FormatTest{"UnixDate", UnixDate, "Thu Feb 4 21:00:57 PST 2010"},
+ FormatTest{"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010"},
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"},
@@ -152,22 +153,26 @@
}
type ParseTest struct {
- name string
- format string
- value string
- hasTZ bool // contains a time zone
- hasWD bool // contains a weekday
+ name string
+ format string
+ value string
+ hasTZ bool // contains a time zone
+ hasWD bool // contains a weekday
+ yearSign int64 // sign of year
}
var parseTests = []ParseTest{
- ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true},
- ParseTest{"UnixDate", UnixDate, "Thu Feb 4 21:00:57 PST 2010", true, true},
- ParseTest{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST", true, true},
- ParseTest{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST", true, true},
- ParseTest{"ISO8601", ISO8601, "2010-02-04T21:00:57-0800", true, false},
+ ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1},
+ ParseTest{"UnixDate", UnixDate, "Thu Feb 4 21:00:57 PST 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{"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},
+ // Negative year
+ ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 -2010", false, true, -1},
// Amount of white space should not matter.
- ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true},
- ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true},
+ 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},
}
func TestParse(t *testing.T) {
@@ -183,7 +188,7 @@
func checkTime(time *Time, test *ParseTest, t *testing.T) {
// The time should be Thu Feb 4 21:00:57 PST 2010
- if time.Year != 2010 {
+ if test.yearSign*time.Year != 2010 {
t.Errorf("%s: bad year: %d not %d\n", test.name, time.Year, 2010)
}
if time.Month != 2 {