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