mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
1) hack regalloc to leave R9 (m) and R10 (g) alone. the real fix is tricker, but this gets us running 2) fix up the few places in the package sources that the shortage of registers affects, by simplifying some expressions. all of this should be reverted when the right fix is in. Fixes #1084. R=rsc CC=golang-dev https://golang.org/cl/2132046
620 lines
15 KiB
Go
620 lines
15 KiB
Go
package time
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"strconv"
|
|
)
|
|
|
|
const (
|
|
numeric = iota
|
|
alphabetic
|
|
separator
|
|
plus
|
|
minus
|
|
)
|
|
|
|
// These are predefined layouts for use in Time.Format.
|
|
// The standard time used in the layouts is:
|
|
// Mon Jan 2 15:04:05 MST 2006 (MST is GMT-0700)
|
|
// which is Unix time 1136243045.
|
|
// (Think of it as 01/02 03:04:05PM '06 -0700.)
|
|
// An underscore _ represents a space that
|
|
// 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"
|
|
RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
|
|
RFC822 = "02 Jan 06 1504 MST"
|
|
// RFC822 with Zulu time.
|
|
RFC822Z = "02 Jan 06 1504 -0700"
|
|
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
|
|
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
|
|
Kitchen = "3:04PM"
|
|
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 = "Z0700" // prints Z for UTC
|
|
stdISO8601ColonTZ = "Z07:00" // prints Z for UTC
|
|
stdNumTZ = "-0700" // always numeric
|
|
stdNumShortTZ = "-07" // always numeric
|
|
stdNumColonTZ = "-07:00" // always numeric
|
|
)
|
|
|
|
// nextStdChunk finds the first occurrence of a std string in
|
|
// layout and returns the text before, the std string, and the text after.
|
|
func nextStdChunk(layout string) (prefix, std, suffix string) {
|
|
for i := 0; i < len(layout); i++ {
|
|
switch layout[i] {
|
|
case 'J': // January, Jan
|
|
if len(layout) >= i+7 && layout[i:i+7] == stdLongMonth {
|
|
return layout[0:i], stdLongMonth, layout[i+7:]
|
|
}
|
|
if len(layout) >= i+3 && layout[i:i+3] == stdMonth {
|
|
return layout[0:i], stdMonth, layout[i+3:]
|
|
}
|
|
|
|
case 'M': // Monday, Mon, MST
|
|
if len(layout) >= i+6 && layout[i:i+6] == stdLongWeekDay {
|
|
return layout[0:i], stdLongWeekDay, layout[i+6:]
|
|
}
|
|
if len(layout) >= i+3 {
|
|
if layout[i:i+3] == stdWeekDay {
|
|
return layout[0:i], stdWeekDay, layout[i+3:]
|
|
}
|
|
if layout[i:i+3] == stdTZ {
|
|
return layout[0:i], stdTZ, layout[i+3:]
|
|
}
|
|
}
|
|
|
|
case '0': // 01, 02, 03, 04, 05, 06
|
|
if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' {
|
|
return layout[0:i], layout[i : i+2], layout[i+2:]
|
|
}
|
|
|
|
case '1': // 15, 1
|
|
if len(layout) >= i+2 && layout[i+1] == '5' {
|
|
return layout[0:i], stdHour, layout[i+2:]
|
|
}
|
|
return layout[0:i], stdNumMonth, layout[i+1:]
|
|
|
|
case '2': // 2006, 2
|
|
if len(layout) >= i+4 && layout[i:i+4] == stdLongYear {
|
|
return layout[0:i], stdLongYear, layout[i+4:]
|
|
}
|
|
return layout[0:i], stdDay, layout[i+1:]
|
|
|
|
case '_': // _2
|
|
if len(layout) >= i+2 && layout[i+1] == '2' {
|
|
return layout[0:i], stdUnderDay, layout[i+2:]
|
|
}
|
|
|
|
case '3', '4', '5': // 3, 4, 5
|
|
return layout[0:i], layout[i : i+1], layout[i+1:]
|
|
|
|
case 'P': // PM
|
|
if len(layout) >= i+2 && layout[i+1] == 'M' {
|
|
return layout[0:i], layout[i : i+2], layout[i+2:]
|
|
}
|
|
|
|
case 'p': // pm
|
|
if len(layout) >= i+2 && layout[i+1] == 'm' {
|
|
return layout[0:i], layout[i : i+2], layout[i+2:]
|
|
}
|
|
|
|
case '-': // -0700, -07:00, -07
|
|
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:]
|
|
}
|
|
if len(layout) >= i+3 && layout[i:i+3] == stdNumShortTZ {
|
|
return layout[0:i], layout[i : i+3], layout[i+3:]
|
|
}
|
|
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, "", ""
|
|
}
|
|
|
|
var longDayNames = []string{
|
|
"Sunday",
|
|
"Monday",
|
|
"Tuesday",
|
|
"Wednesday",
|
|
"Thursday",
|
|
"Friday",
|
|
"Saturday",
|
|
}
|
|
|
|
var shortDayNames = []string{
|
|
"Sun",
|
|
"Mon",
|
|
"Tue",
|
|
"Wed",
|
|
"Thu",
|
|
"Fri",
|
|
"Sat",
|
|
}
|
|
|
|
var shortMonthNames = []string{
|
|
"---",
|
|
"Jan",
|
|
"Feb",
|
|
"Mar",
|
|
"Apr",
|
|
"May",
|
|
"Jun",
|
|
"Jul",
|
|
"Aug",
|
|
"Sep",
|
|
"Oct",
|
|
"Nov",
|
|
"Dec",
|
|
}
|
|
|
|
var longMonthNames = []string{
|
|
"---",
|
|
"January",
|
|
"February",
|
|
"March",
|
|
"April",
|
|
"May",
|
|
"June",
|
|
"July",
|
|
"August",
|
|
"September",
|
|
"October",
|
|
"November",
|
|
"December",
|
|
}
|
|
|
|
func lookup(tab []string, val string) (int, string, os.Error) {
|
|
for i, v := range tab {
|
|
if len(val) >= len(v) && val[0:len(v)] == v {
|
|
return i, val[len(v):], nil
|
|
}
|
|
}
|
|
return -1, val, errBad
|
|
}
|
|
|
|
func pad(i int, padding string) string {
|
|
s := strconv.Itoa(i)
|
|
if i < 10 {
|
|
s = padding + s
|
|
}
|
|
return s
|
|
}
|
|
|
|
func zeroPad(i int) string { return pad(i, "0") }
|
|
|
|
// Format returns a textual representation of the time value formatted
|
|
// 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,
|
|
// RFC3339 and others describe standard representations.
|
|
func (t *Time) Format(layout string) string {
|
|
b := new(bytes.Buffer)
|
|
// Each iteration generates one std value.
|
|
for {
|
|
prefix, std, suffix := nextStdChunk(layout)
|
|
b.WriteString(prefix)
|
|
if std == "" {
|
|
break
|
|
}
|
|
var p string
|
|
switch std {
|
|
case stdYear:
|
|
p = strconv.Itoa64(t.Year % 100)
|
|
case stdLongYear:
|
|
p = strconv.Itoa64(t.Year)
|
|
case stdMonth:
|
|
p = shortMonthNames[t.Month]
|
|
case stdLongMonth:
|
|
p = longMonthNames[t.Month]
|
|
case stdNumMonth:
|
|
p = strconv.Itoa(t.Month)
|
|
case stdZeroMonth:
|
|
p = zeroPad(t.Month)
|
|
case stdWeekDay:
|
|
p = shortDayNames[t.Weekday]
|
|
case stdLongWeekDay:
|
|
p = longDayNames[t.Weekday]
|
|
case stdDay:
|
|
p = strconv.Itoa(t.Day)
|
|
case stdUnderDay:
|
|
p = pad(t.Day, " ")
|
|
case stdZeroDay:
|
|
p = zeroPad(t.Day)
|
|
case stdHour:
|
|
p = zeroPad(t.Hour)
|
|
case stdHour12:
|
|
p = strconv.Itoa(t.Hour % 12)
|
|
case stdZeroHour12:
|
|
p = zeroPad(t.Hour % 12)
|
|
case stdMinute:
|
|
p = strconv.Itoa(t.Minute)
|
|
case stdZeroMinute:
|
|
p = zeroPad(t.Minute)
|
|
case stdSecond:
|
|
p = strconv.Itoa(t.Second)
|
|
case stdZeroSecond:
|
|
p = zeroPad(t.Second)
|
|
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
|
|
}
|
|
zone := t.ZoneOffset / 60 // convert to minutes
|
|
if zone < 0 {
|
|
p = "-"
|
|
zone = -zone
|
|
} else {
|
|
p = "+"
|
|
}
|
|
p += zeroPad(zone / 60)
|
|
if std == stdISO8601ColonTZ || std == stdNumColonTZ {
|
|
p += ":"
|
|
}
|
|
p += zeroPad(zone % 60)
|
|
case stdPM:
|
|
if t.Hour >= 12 {
|
|
p = "PM"
|
|
} else {
|
|
p = "AM"
|
|
}
|
|
case stdpm:
|
|
if t.Hour >= 12 {
|
|
p = "pm"
|
|
} else {
|
|
p = "am"
|
|
}
|
|
case stdTZ:
|
|
if t.Zone != "" {
|
|
p = t.Zone
|
|
} else {
|
|
// No time zone known for this time, but we must print one.
|
|
// Use the -0700 format.
|
|
zone := t.ZoneOffset / 60 // convert to minutes
|
|
if zone < 0 {
|
|
p = "-"
|
|
zone = -zone
|
|
} else {
|
|
p = "+"
|
|
}
|
|
p += zeroPad(zone / 60)
|
|
p += zeroPad(zone % 60)
|
|
}
|
|
}
|
|
b.WriteString(p)
|
|
layout = suffix
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// String returns a Unix-style representation of the time value.
|
|
func (t *Time) String() string {
|
|
if t == nil {
|
|
return "<nil>"
|
|
}
|
|
return t.Format(UnixDate)
|
|
}
|
|
|
|
var errBad = os.ErrorString("bad") // just a marker; not returned to user
|
|
|
|
// ParseError describes a problem parsing a time string.
|
|
type ParseError struct {
|
|
Layout string
|
|
Value string
|
|
LayoutElem string
|
|
ValueElem string
|
|
Message string
|
|
}
|
|
|
|
// String is the string representation of a ParseError.
|
|
func (e *ParseError) String() string {
|
|
if e.Message == "" {
|
|
// BUG: work around bug in 5g by simplifying expression.
|
|
// return "parsing time " +
|
|
// strconv.Quote(e.Value) + " as " +
|
|
// strconv.Quote(e.Layout) + ": cannot parse " +
|
|
// strconv.Quote(e.ValueElem) + " as " +
|
|
// strconv.Quote(e.LayoutElem)
|
|
str := "parsing time " + strconv.Quote(e.Value) + " as "
|
|
str += strconv.Quote(e.Layout) + ": cannot parse "
|
|
str += strconv.Quote(e.ValueElem) + " as "
|
|
str += strconv.Quote(e.LayoutElem)
|
|
return str
|
|
}
|
|
return "parsing time " +
|
|
strconv.Quote(e.Value) + e.Message
|
|
}
|
|
|
|
// getnum parses s[0:1] or s[0:2] (fixed forces the latter)
|
|
// as a decimal integer and returns the integer and the
|
|
// remainder of the string.
|
|
func getnum(s string, fixed bool) (int, string, os.Error) {
|
|
if len(s) == 0 || s[0] < '0' || s[0] > '9' {
|
|
return 0, s, errBad
|
|
}
|
|
if len(s) == 1 || s[1] < '0' || s[1] > '9' {
|
|
if fixed {
|
|
return 0, s, errBad
|
|
}
|
|
return int(s[0] - '0'), s[1:], nil
|
|
}
|
|
return int(s[0]-'0')*10 + int(s[1]-'0'), s[2:], nil
|
|
}
|
|
|
|
func cutspace(s string) string {
|
|
for len(s) > 0 && s[0] == ' ' {
|
|
s = s[1:]
|
|
}
|
|
return s
|
|
}
|
|
|
|
// skip removes the given prefix from value,
|
|
// treating runs of space characters as equivalent.
|
|
func skip(value, prefix string) (string, os.Error) {
|
|
for len(prefix) > 0 {
|
|
if prefix[0] == ' ' {
|
|
if len(value) > 0 && value[0] != ' ' {
|
|
return "", errBad
|
|
}
|
|
prefix = cutspace(prefix)
|
|
value = cutspace(value)
|
|
continue
|
|
}
|
|
if len(value) == 0 || value[0] != prefix[0] {
|
|
return "", errBad
|
|
}
|
|
prefix = prefix[1:]
|
|
value = value[1:]
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
// 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, RFC3339 and others describe standard
|
|
// representations.
|
|
//
|
|
// Only those elements present in the value will be set in the returned time
|
|
// structure. Also, if the input string represents an inconsistent time
|
|
// (such as having the wrong day of the week), the returned value will also
|
|
// be inconsistent. In any case, the elements of the returned time will be
|
|
// sane: hours in 0..23, minutes in 0..59, day of month in 0..31, etc.
|
|
// Years must be in the range 0000..9999.
|
|
func Parse(alayout, avalue string) (*Time, os.Error) {
|
|
var t Time
|
|
rangeErrString := "" // set if a value is out of range
|
|
pmSet := false // do we need to add 12 to the hour?
|
|
layout, value := alayout, avalue
|
|
// Each iteration processes one std value.
|
|
for {
|
|
var err os.Error
|
|
prefix, std, suffix := nextStdChunk(layout)
|
|
value, err = skip(value, prefix)
|
|
if err != nil {
|
|
return nil, &ParseError{alayout, avalue, prefix, value, ""}
|
|
}
|
|
if len(std) == 0 {
|
|
if len(value) != 0 {
|
|
return nil, &ParseError{alayout, avalue, "", value, ": extra text: " + value}
|
|
}
|
|
break
|
|
}
|
|
layout = suffix
|
|
var p string
|
|
switch std {
|
|
case stdYear:
|
|
if len(value) < 2 {
|
|
err = errBad
|
|
break
|
|
}
|
|
p, value = value[0:2], value[2:]
|
|
t.Year, err = strconv.Atoi64(p)
|
|
if t.Year >= 69 { // Unix time starts Dec 31 1969 in some time zones
|
|
t.Year += 1900
|
|
} else {
|
|
t.Year += 2000
|
|
}
|
|
case stdLongYear:
|
|
if len(value) < 4 || value[0] < '0' || value[0] > '9' {
|
|
err = errBad
|
|
break
|
|
}
|
|
p, value = value[0:4], value[4:]
|
|
t.Year, err = strconv.Atoi64(p)
|
|
case stdMonth:
|
|
t.Month, value, err = lookup(shortMonthNames, value)
|
|
case stdLongMonth:
|
|
t.Month, value, err = lookup(longMonthNames, value)
|
|
case stdNumMonth, stdZeroMonth:
|
|
t.Month, value, err = getnum(value, std == stdZeroMonth)
|
|
if t.Month <= 0 || 12 < t.Month {
|
|
rangeErrString = "month"
|
|
}
|
|
case stdWeekDay:
|
|
t.Weekday, value, err = lookup(shortDayNames, value)
|
|
case stdLongWeekDay:
|
|
t.Weekday, value, err = lookup(longDayNames, value)
|
|
case stdDay, stdUnderDay, stdZeroDay:
|
|
if std == stdUnderDay && len(value) > 0 && value[0] == ' ' {
|
|
value = value[1:]
|
|
}
|
|
t.Day, value, err = getnum(value, std == stdZeroDay)
|
|
if t.Day < 0 || 31 < t.Day {
|
|
// TODO: be more thorough in date check?
|
|
rangeErrString = "day"
|
|
}
|
|
case stdHour:
|
|
t.Hour, value, err = getnum(value, false)
|
|
if t.Hour < 0 || 24 <= t.Hour {
|
|
rangeErrString = "hour"
|
|
}
|
|
case stdHour12, stdZeroHour12:
|
|
t.Hour, value, err = getnum(value, std == stdZeroHour12)
|
|
if t.Hour < 0 || 12 < t.Hour {
|
|
rangeErrString = "hour"
|
|
}
|
|
case stdMinute, stdZeroMinute:
|
|
t.Minute, value, err = getnum(value, std == stdZeroMinute)
|
|
if t.Minute < 0 || 60 <= t.Minute {
|
|
rangeErrString = "minute"
|
|
}
|
|
case stdSecond, stdZeroSecond:
|
|
t.Second, value, err = getnum(value, std == stdZeroSecond)
|
|
if t.Second < 0 || 60 <= t.Second {
|
|
rangeErrString = "second"
|
|
}
|
|
case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ:
|
|
if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' {
|
|
value = value[1:]
|
|
t.Zone = "UTC"
|
|
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 std == stdNumShortTZ {
|
|
if len(value) < 3 {
|
|
err = errBad
|
|
break
|
|
}
|
|
sign, hh, mm, value = value[0:1], value[1:3], "00", value[3:]
|
|
} else {
|
|
if len(value) < 5 {
|
|
err = errBad
|
|
break
|
|
}
|
|
sign, hh, mm, value = value[0:1], value[1:3], value[3:5], value[5:]
|
|
}
|
|
var hr, min int
|
|
hr, err = strconv.Atoi(hh)
|
|
if err == nil {
|
|
min, err = strconv.Atoi(mm)
|
|
}
|
|
t.ZoneOffset = (hr*60 + min) * 60 // offset is in seconds
|
|
switch sign[0] {
|
|
case '+':
|
|
case '-':
|
|
t.ZoneOffset = -t.ZoneOffset
|
|
default:
|
|
err = errBad
|
|
}
|
|
case stdPM:
|
|
if len(value) < 2 {
|
|
err = errBad
|
|
break
|
|
}
|
|
p, value = value[0:2], value[2:]
|
|
if p == "PM" {
|
|
pmSet = true
|
|
} else if p != "AM" {
|
|
err = errBad
|
|
}
|
|
case stdpm:
|
|
if len(value) < 2 {
|
|
err = errBad
|
|
break
|
|
}
|
|
p, value = value[0:2], value[2:]
|
|
if p == "pm" {
|
|
pmSet = true
|
|
} else if p != "am" {
|
|
err = errBad
|
|
}
|
|
case stdTZ:
|
|
// Does it look like a time zone?
|
|
if len(value) >= 3 && value[0:3] == "UTC" {
|
|
t.Zone, value = value[0:3], value[3:]
|
|
break
|
|
}
|
|
|
|
if len(value) >= 3 && value[2] == 'T' {
|
|
p, value = value[0:3], value[3:]
|
|
} else if len(value) >= 4 && value[3] == 'T' {
|
|
p, value = value[0:4], value[4:]
|
|
} else {
|
|
err = errBad
|
|
break
|
|
}
|
|
for i := 0; i < len(p); i++ {
|
|
if p[i] < 'A' || 'Z' < p[i] {
|
|
err = errBad
|
|
}
|
|
}
|
|
if err != nil {
|
|
break
|
|
}
|
|
// It's a valid format.
|
|
t.Zone = p
|
|
// Can we find its offset?
|
|
if offset, found := lookupByName(p); found {
|
|
t.ZoneOffset = offset
|
|
}
|
|
}
|
|
if rangeErrString != "" {
|
|
return nil, &ParseError{alayout, avalue, std, value, ": " + rangeErrString + " out of range"}
|
|
}
|
|
if err != nil {
|
|
return nil, &ParseError{alayout, avalue, std, value, ""}
|
|
}
|
|
}
|
|
if pmSet && t.Hour < 12 {
|
|
t.Hour += 12
|
|
}
|
|
return &t, nil
|
|
}
|