f8d9fa9e80
This upgrades all of libgo other than the runtime package to the Go 1.4 release. In Go 1.4 much of the runtime was rewritten into Go. Merging that code will take more time and will not change the API, so I'm putting it off for now. There are a few runtime changes anyhow, to accomodate other packages that rely on minor modifications to the runtime support. The compiler changes slightly to add a one-bit flag to each type descriptor kind that is stored directly in an interface, which for gccgo is currently only pointer types. Another one-bit flag (gcprog) is reserved because it is used by the gc compiler, but gccgo does not currently use it. There is another error check in the compiler since I ran across it during testing. gotools/: * Makefile.am (go_cmd_go_files): Sort entries. Add generate.go. * Makefile.in: Rebuild. From-SVN: r219627
1249 lines
35 KiB
Go
1249 lines
35 KiB
Go
// Copyright 2010 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package time
|
|
|
|
import "errors"
|
|
|
|
// These are predefined layouts for use in Time.Format and Time.Parse.
|
|
// The reference time used in the layouts is the specific time:
|
|
// Mon Jan 2 15:04:05 MST 2006
|
|
// which is Unix time 1136239445. Since MST is GMT-0700,
|
|
// the reference time can be thought of as
|
|
// 01/02 03:04:05PM '06 -0700
|
|
// To define your own format, write down what the reference time would look
|
|
// like formatted your way; see the values of constants like ANSIC,
|
|
// StampMicro or Kitchen for examples. The model is to demonstrate what the
|
|
// reference time looks like so that the Format and Parse methods can apply
|
|
// the same transformation to a general time value.
|
|
//
|
|
// Within the format string, 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.
|
|
//
|
|
// A decimal point followed by one or more zeros represents a fractional
|
|
// second, printed to the given number of decimal places. A decimal point
|
|
// followed by one or more nines represents a fractional second, printed to
|
|
// the given number of decimal places, with trailing zeros removed.
|
|
// When parsing (only), the input may contain a fractional second
|
|
// field immediately after the seconds field, even if the layout does not
|
|
// signify its presence. In that case a decimal point followed by a maximal
|
|
// series of digits is parsed as a fractional second.
|
|
//
|
|
// 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 15:04 MST"
|
|
RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
|
|
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
|
|
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
|
|
RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
|
|
RFC3339 = "2006-01-02T15:04:05Z07:00"
|
|
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
|
|
Kitchen = "3:04PM"
|
|
// Handy time stamps.
|
|
Stamp = "Jan _2 15:04:05"
|
|
StampMilli = "Jan _2 15:04:05.000"
|
|
StampMicro = "Jan _2 15:04:05.000000"
|
|
StampNano = "Jan _2 15:04:05.000000000"
|
|
)
|
|
|
|
const (
|
|
_ = iota
|
|
stdLongMonth = iota + stdNeedDate // "January"
|
|
stdMonth // "Jan"
|
|
stdNumMonth // "1"
|
|
stdZeroMonth // "01"
|
|
stdLongWeekDay // "Monday"
|
|
stdWeekDay // "Mon"
|
|
stdDay // "2"
|
|
stdUnderDay // "_2"
|
|
stdZeroDay // "02"
|
|
stdHour = iota + stdNeedClock // "15"
|
|
stdHour12 // "3"
|
|
stdZeroHour12 // "03"
|
|
stdMinute // "4"
|
|
stdZeroMinute // "04"
|
|
stdSecond // "5"
|
|
stdZeroSecond // "05"
|
|
stdLongYear = iota + stdNeedDate // "2006"
|
|
stdYear // "06"
|
|
stdPM = iota + stdNeedClock // "PM"
|
|
stdpm // "pm"
|
|
stdTZ = iota // "MST"
|
|
stdISO8601TZ // "Z0700" // prints Z for UTC
|
|
stdISO8601SecondsTZ // "Z070000"
|
|
stdISO8601ColonTZ // "Z07:00" // prints Z for UTC
|
|
stdISO8601ColonSecondsTZ // "Z07:00:00"
|
|
stdNumTZ // "-0700" // always numeric
|
|
stdNumSecondsTz // "-070000"
|
|
stdNumShortTZ // "-07" // always numeric
|
|
stdNumColonTZ // "-07:00" // always numeric
|
|
stdNumColonSecondsTZ // "-07:00:00"
|
|
stdFracSecond0 // ".0", ".00", ... , trailing zeros included
|
|
stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted
|
|
|
|
stdNeedDate = 1 << 8 // need month, day, year
|
|
stdNeedClock = 2 << 8 // need hour, minute, second
|
|
stdArgShift = 16 // extra argument in high bits, above low stdArgShift
|
|
stdMask = 1<<stdArgShift - 1 // mask out argument
|
|
)
|
|
|
|
// std0x records the std values for "01", "02", ..., "06".
|
|
var std0x = [...]int{stdZeroMonth, stdZeroDay, stdZeroHour12, stdZeroMinute, stdZeroSecond, stdYear}
|
|
|
|
// startsWithLowerCase reports whether the string has a lower-case letter at the beginning.
|
|
// Its purpose is to prevent matching strings like "Month" when looking for "Mon".
|
|
func startsWithLowerCase(str string) bool {
|
|
if len(str) == 0 {
|
|
return false
|
|
}
|
|
c := str[0]
|
|
return 'a' <= c && c <= 'z'
|
|
}
|
|
|
|
// 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 string, std int, suffix string) {
|
|
for i := 0; i < len(layout); i++ {
|
|
switch c := int(layout[i]); c {
|
|
case 'J': // January, Jan
|
|
if len(layout) >= i+3 && layout[i:i+3] == "Jan" {
|
|
if len(layout) >= i+7 && layout[i:i+7] == "January" {
|
|
return layout[0:i], stdLongMonth, layout[i+7:]
|
|
}
|
|
if !startsWithLowerCase(layout[i+3:]) {
|
|
return layout[0:i], stdMonth, layout[i+3:]
|
|
}
|
|
}
|
|
|
|
case 'M': // Monday, Mon, MST
|
|
if len(layout) >= i+3 {
|
|
if layout[i:i+3] == "Mon" {
|
|
if len(layout) >= i+6 && layout[i:i+6] == "Monday" {
|
|
return layout[0:i], stdLongWeekDay, layout[i+6:]
|
|
}
|
|
if !startsWithLowerCase(layout[i+3:]) {
|
|
return layout[0:i], stdWeekDay, layout[i+3:]
|
|
}
|
|
}
|
|
if layout[i:i+3] == "MST" {
|
|
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], std0x[layout[i+1]-'1'], 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] == "2006" {
|
|
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':
|
|
return layout[0:i], stdHour12, layout[i+1:]
|
|
|
|
case '4':
|
|
return layout[0:i], stdMinute, layout[i+1:]
|
|
|
|
case '5':
|
|
return layout[0:i], stdSecond, layout[i+1:]
|
|
|
|
case 'P': // PM
|
|
if len(layout) >= i+2 && layout[i+1] == 'M' {
|
|
return layout[0:i], stdPM, layout[i+2:]
|
|
}
|
|
|
|
case 'p': // pm
|
|
if len(layout) >= i+2 && layout[i+1] == 'm' {
|
|
return layout[0:i], stdpm, layout[i+2:]
|
|
}
|
|
|
|
case '-': // -070000, -07:00:00, -0700, -07:00, -07
|
|
if len(layout) >= i+7 && layout[i:i+7] == "-070000" {
|
|
return layout[0:i], stdNumSecondsTz, layout[i+7:]
|
|
}
|
|
if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" {
|
|
return layout[0:i], stdNumColonSecondsTZ, layout[i+9:]
|
|
}
|
|
if len(layout) >= i+5 && layout[i:i+5] == "-0700" {
|
|
return layout[0:i], stdNumTZ, layout[i+5:]
|
|
}
|
|
if len(layout) >= i+6 && layout[i:i+6] == "-07:00" {
|
|
return layout[0:i], stdNumColonTZ, layout[i+6:]
|
|
}
|
|
if len(layout) >= i+3 && layout[i:i+3] == "-07" {
|
|
return layout[0:i], stdNumShortTZ, layout[i+3:]
|
|
}
|
|
|
|
case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00,
|
|
if len(layout) >= i+7 && layout[i:i+7] == "Z070000" {
|
|
return layout[0:i], stdISO8601SecondsTZ, layout[i+7:]
|
|
}
|
|
if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" {
|
|
return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:]
|
|
}
|
|
if len(layout) >= i+5 && layout[i:i+5] == "Z0700" {
|
|
return layout[0:i], stdISO8601TZ, layout[i+5:]
|
|
}
|
|
if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" {
|
|
return layout[0:i], stdISO8601ColonTZ, layout[i+6:]
|
|
}
|
|
|
|
case '.': // .000 or .999 - repeated digits for fractional seconds.
|
|
if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
|
|
ch := layout[i+1]
|
|
j := i + 1
|
|
for j < len(layout) && layout[j] == ch {
|
|
j++
|
|
}
|
|
// String of digits must end here - only fractional second is all digits.
|
|
if !isDigit(layout, j) {
|
|
std := stdFracSecond0
|
|
if layout[i+1] == '9' {
|
|
std = stdFracSecond9
|
|
}
|
|
std |= (j - (i + 1)) << stdArgShift
|
|
return layout[0:i], std, layout[j:]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return layout, 0, ""
|
|
}
|
|
|
|
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",
|
|
}
|
|
|
|
// match returns true if s1 and s2 match ignoring case.
|
|
// It is assumed s1 and s2 are the same length.
|
|
func match(s1, s2 string) bool {
|
|
for i := 0; i < len(s1); i++ {
|
|
c1 := s1[i]
|
|
c2 := s2[i]
|
|
if c1 != c2 {
|
|
// Switch to lower-case; 'a'-'A' is known to be a single bit.
|
|
c1 |= 'a' - 'A'
|
|
c2 |= 'a' - 'A'
|
|
if c1 != c2 || c1 < 'a' || c1 > 'z' {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func lookup(tab []string, val string) (int, string, error) {
|
|
for i, v := range tab {
|
|
if len(val) >= len(v) && match(val[0:len(v)], v) {
|
|
return i, val[len(v):], nil
|
|
}
|
|
}
|
|
return -1, val, errBad
|
|
}
|
|
|
|
// appendUint appends the decimal form of x to b and returns the result.
|
|
// If x is a single-digit number and pad != 0, appendUint inserts the pad byte
|
|
// before the digit.
|
|
// Duplicates functionality in strconv, but avoids dependency.
|
|
func appendUint(b []byte, x uint, pad byte) []byte {
|
|
if x < 10 {
|
|
if pad != 0 {
|
|
b = append(b, pad)
|
|
}
|
|
return append(b, byte('0'+x))
|
|
}
|
|
if x < 100 {
|
|
b = append(b, byte('0'+x/10))
|
|
b = append(b, byte('0'+x%10))
|
|
return b
|
|
}
|
|
|
|
var buf [32]byte
|
|
n := len(buf)
|
|
if x == 0 {
|
|
return append(b, '0')
|
|
}
|
|
for x >= 10 {
|
|
n--
|
|
buf[n] = byte(x%10 + '0')
|
|
x /= 10
|
|
}
|
|
n--
|
|
buf[n] = byte(x + '0')
|
|
return append(b, buf[n:]...)
|
|
}
|
|
|
|
// Never printed, just needs to be non-nil for return by atoi.
|
|
var atoiError = errors.New("time: invalid number")
|
|
|
|
// Duplicates functionality in strconv, but avoids dependency.
|
|
func atoi(s string) (x int, err error) {
|
|
neg := false
|
|
if s != "" && (s[0] == '-' || s[0] == '+') {
|
|
neg = s[0] == '-'
|
|
s = s[1:]
|
|
}
|
|
q, rem, err := leadingInt(s)
|
|
x = int(q)
|
|
if err != nil || rem != "" {
|
|
return 0, atoiError
|
|
}
|
|
if neg {
|
|
x = -x
|
|
}
|
|
return x, nil
|
|
}
|
|
|
|
// formatNano appends a fractional second, as nanoseconds, to b
|
|
// and returns the result.
|
|
func formatNano(b []byte, nanosec uint, n int, trim bool) []byte {
|
|
u := nanosec
|
|
var buf [9]byte
|
|
for start := len(buf); start > 0; {
|
|
start--
|
|
buf[start] = byte(u%10 + '0')
|
|
u /= 10
|
|
}
|
|
|
|
if n > 9 {
|
|
n = 9
|
|
}
|
|
if trim {
|
|
for n > 0 && buf[n-1] == '0' {
|
|
n--
|
|
}
|
|
if n == 0 {
|
|
return b
|
|
}
|
|
}
|
|
b = append(b, '.')
|
|
return append(b, buf[:n]...)
|
|
}
|
|
|
|
// String returns the time formatted using the format string
|
|
// "2006-01-02 15:04:05.999999999 -0700 MST"
|
|
func (t Time) String() string {
|
|
return t.Format("2006-01-02 15:04:05.999999999 -0700 MST")
|
|
}
|
|
|
|
// Format returns a textual representation of the time value formatted
|
|
// according to layout, which defines the format by showing how the reference
|
|
// time, defined to be
|
|
// Mon Jan 2 15:04:05 -0700 MST 2006
|
|
// would be displayed if it were the value; it serves as an example of the
|
|
// desired output. The same display rules will then be applied to the time
|
|
// value.
|
|
// Predefined layouts ANSIC, UnixDate, RFC3339 and others describe standard
|
|
// and convenient representations of the reference time. For more information
|
|
// about the formats and the definition of the reference time, see the
|
|
// documentation for ANSIC and the other constants defined by this package.
|
|
func (t Time) Format(layout string) string {
|
|
var (
|
|
name, offset, abs = t.locabs()
|
|
|
|
year int = -1
|
|
month Month
|
|
day int
|
|
hour int = -1
|
|
min int
|
|
sec int
|
|
|
|
b []byte
|
|
buf [64]byte
|
|
)
|
|
max := len(layout) + 10
|
|
if max <= len(buf) {
|
|
b = buf[:0]
|
|
} else {
|
|
b = make([]byte, 0, max)
|
|
}
|
|
// Each iteration generates one std value.
|
|
for layout != "" {
|
|
prefix, std, suffix := nextStdChunk(layout)
|
|
if prefix != "" {
|
|
b = append(b, prefix...)
|
|
}
|
|
if std == 0 {
|
|
break
|
|
}
|
|
layout = suffix
|
|
|
|
// Compute year, month, day if needed.
|
|
if year < 0 && std&stdNeedDate != 0 {
|
|
year, month, day, _ = absDate(abs, true)
|
|
}
|
|
|
|
// Compute hour, minute, second if needed.
|
|
if hour < 0 && std&stdNeedClock != 0 {
|
|
hour, min, sec = absClock(abs)
|
|
}
|
|
|
|
switch std & stdMask {
|
|
case stdYear:
|
|
y := year
|
|
if y < 0 {
|
|
y = -y
|
|
}
|
|
b = appendUint(b, uint(y%100), '0')
|
|
case stdLongYear:
|
|
// Pad year to at least 4 digits.
|
|
y := year
|
|
switch {
|
|
case year <= -1000:
|
|
b = append(b, '-')
|
|
y = -y
|
|
case year <= -100:
|
|
b = append(b, "-0"...)
|
|
y = -y
|
|
case year <= -10:
|
|
b = append(b, "-00"...)
|
|
y = -y
|
|
case year < 0:
|
|
b = append(b, "-000"...)
|
|
y = -y
|
|
case year < 10:
|
|
b = append(b, "000"...)
|
|
case year < 100:
|
|
b = append(b, "00"...)
|
|
case year < 1000:
|
|
b = append(b, '0')
|
|
}
|
|
b = appendUint(b, uint(y), 0)
|
|
case stdMonth:
|
|
b = append(b, month.String()[:3]...)
|
|
case stdLongMonth:
|
|
m := month.String()
|
|
b = append(b, m...)
|
|
case stdNumMonth:
|
|
b = appendUint(b, uint(month), 0)
|
|
case stdZeroMonth:
|
|
b = appendUint(b, uint(month), '0')
|
|
case stdWeekDay:
|
|
b = append(b, absWeekday(abs).String()[:3]...)
|
|
case stdLongWeekDay:
|
|
s := absWeekday(abs).String()
|
|
b = append(b, s...)
|
|
case stdDay:
|
|
b = appendUint(b, uint(day), 0)
|
|
case stdUnderDay:
|
|
b = appendUint(b, uint(day), ' ')
|
|
case stdZeroDay:
|
|
b = appendUint(b, uint(day), '0')
|
|
case stdHour:
|
|
b = appendUint(b, uint(hour), '0')
|
|
case stdHour12:
|
|
// Noon is 12PM, midnight is 12AM.
|
|
hr := hour % 12
|
|
if hr == 0 {
|
|
hr = 12
|
|
}
|
|
b = appendUint(b, uint(hr), 0)
|
|
case stdZeroHour12:
|
|
// Noon is 12PM, midnight is 12AM.
|
|
hr := hour % 12
|
|
if hr == 0 {
|
|
hr = 12
|
|
}
|
|
b = appendUint(b, uint(hr), '0')
|
|
case stdMinute:
|
|
b = appendUint(b, uint(min), 0)
|
|
case stdZeroMinute:
|
|
b = appendUint(b, uint(min), '0')
|
|
case stdSecond:
|
|
b = appendUint(b, uint(sec), 0)
|
|
case stdZeroSecond:
|
|
b = appendUint(b, uint(sec), '0')
|
|
case stdPM:
|
|
if hour >= 12 {
|
|
b = append(b, "PM"...)
|
|
} else {
|
|
b = append(b, "AM"...)
|
|
}
|
|
case stdpm:
|
|
if hour >= 12 {
|
|
b = append(b, "pm"...)
|
|
} else {
|
|
b = append(b, "am"...)
|
|
}
|
|
case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ:
|
|
// Ugly special case. We cheat and take the "Z" variants
|
|
// to mean "the time zone as formatted for ISO 8601".
|
|
if offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ColonSecondsTZ) {
|
|
b = append(b, 'Z')
|
|
break
|
|
}
|
|
zone := offset / 60 // convert to minutes
|
|
absoffset := offset
|
|
if zone < 0 {
|
|
b = append(b, '-')
|
|
zone = -zone
|
|
absoffset = -absoffset
|
|
} else {
|
|
b = append(b, '+')
|
|
}
|
|
b = appendUint(b, uint(zone/60), '0')
|
|
if std == stdISO8601ColonTZ || std == stdNumColonTZ || std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ {
|
|
b = append(b, ':')
|
|
}
|
|
b = appendUint(b, uint(zone%60), '0')
|
|
|
|
// append seconds if appropriate
|
|
if std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ {
|
|
if std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ {
|
|
b = append(b, ':')
|
|
}
|
|
b = appendUint(b, uint(absoffset%60), '0')
|
|
}
|
|
|
|
case stdTZ:
|
|
if name != "" {
|
|
b = append(b, name...)
|
|
break
|
|
}
|
|
// No time zone known for this time, but we must print one.
|
|
// Use the -0700 format.
|
|
zone := offset / 60 // convert to minutes
|
|
if zone < 0 {
|
|
b = append(b, '-')
|
|
zone = -zone
|
|
} else {
|
|
b = append(b, '+')
|
|
}
|
|
b = appendUint(b, uint(zone/60), '0')
|
|
b = appendUint(b, uint(zone%60), '0')
|
|
case stdFracSecond0, stdFracSecond9:
|
|
b = formatNano(b, uint(t.Nanosecond()), std>>stdArgShift, std&stdMask == stdFracSecond9)
|
|
}
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
var errBad = errors.New("bad value for field") // placeholder not passed to user
|
|
|
|
// ParseError describes a problem parsing a time string.
|
|
type ParseError struct {
|
|
Layout string
|
|
Value string
|
|
LayoutElem string
|
|
ValueElem string
|
|
Message string
|
|
}
|
|
|
|
func quote(s string) string {
|
|
return "\"" + s + "\""
|
|
}
|
|
|
|
// Error returns the string representation of a ParseError.
|
|
func (e *ParseError) Error() string {
|
|
if e.Message == "" {
|
|
return "parsing time " +
|
|
quote(e.Value) + " as " +
|
|
quote(e.Layout) + ": cannot parse " +
|
|
quote(e.ValueElem) + " as " +
|
|
quote(e.LayoutElem)
|
|
}
|
|
return "parsing time " +
|
|
quote(e.Value) + e.Message
|
|
}
|
|
|
|
// isDigit returns true if s[i] is a decimal digit, false if not or
|
|
// if s[i] is out of range.
|
|
func isDigit(s string, i int) bool {
|
|
if len(s) <= i {
|
|
return false
|
|
}
|
|
c := s[i]
|
|
return '0' <= c && c <= '9'
|
|
}
|
|
|
|
// 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, error) {
|
|
if !isDigit(s, 0) {
|
|
return 0, s, errBad
|
|
}
|
|
if !isDigit(s, 1) {
|
|
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, error) {
|
|
for len(prefix) > 0 {
|
|
if prefix[0] == ' ' {
|
|
if len(value) > 0 && value[0] != ' ' {
|
|
return value, errBad
|
|
}
|
|
prefix = cutspace(prefix)
|
|
value = cutspace(value)
|
|
continue
|
|
}
|
|
if len(value) == 0 || value[0] != prefix[0] {
|
|
return value, 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 how the reference time,
|
|
// defined to be
|
|
// Mon Jan 2 15:04:05 -0700 MST 2006
|
|
// would be interpreted if it were the value; it serves as an example of
|
|
// the input format. The same interpretation will then be made to the
|
|
// input string.
|
|
// Predefined layouts ANSIC, UnixDate, RFC3339 and others describe standard
|
|
// and convenient representations of the reference time. For more information
|
|
// about the formats and the definition of the reference time, see the
|
|
// documentation for ANSIC and the other constants defined by this package.
|
|
//
|
|
// Elements omitted from the value are assumed to be zero or, when
|
|
// zero is impossible, one, so parsing "3:04pm" returns the time
|
|
// corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is
|
|
// 0, this time is before the zero Time).
|
|
// Years must be in the range 0000..9999. The day of the week is checked
|
|
// for syntax but it is otherwise ignored.
|
|
//
|
|
// In the absence of a time zone indicator, Parse returns a time in UTC.
|
|
//
|
|
// When parsing a time with a zone offset like -0700, if the offset corresponds
|
|
// to a time zone used by the current location (Local), then Parse uses that
|
|
// location and zone in the returned time. Otherwise it records the time as
|
|
// being in a fabricated location with time fixed at the given zone offset.
|
|
//
|
|
// When parsing a time with a zone abbreviation like MST, if the zone abbreviation
|
|
// has a defined offset in the current location, then that offset is used.
|
|
// The zone abbreviation "UTC" is recognized as UTC regardless of location.
|
|
// If the zone abbreviation is unknown, Parse records the time as being
|
|
// in a fabricated location with the given zone abbreviation and a zero offset.
|
|
// This choice means that such a time can be parsed and reformatted with the
|
|
// same layout losslessly, but the exact instant used in the representation will
|
|
// differ by the actual zone offset. To avoid such problems, prefer time layouts
|
|
// that use a numeric zone offset, or use ParseInLocation.
|
|
func Parse(layout, value string) (Time, error) {
|
|
return parse(layout, value, UTC, Local)
|
|
}
|
|
|
|
// ParseInLocation is like Parse but differs in two important ways.
|
|
// First, in the absence of time zone information, Parse interprets a time as UTC;
|
|
// ParseInLocation interprets the time as in the given location.
|
|
// Second, when given a zone offset or abbreviation, Parse tries to match it
|
|
// against the Local location; ParseInLocation uses the given location.
|
|
func ParseInLocation(layout, value string, loc *Location) (Time, error) {
|
|
return parse(layout, value, loc, loc)
|
|
}
|
|
|
|
func parse(layout, value string, defaultLocation, local *Location) (Time, error) {
|
|
alayout, avalue := layout, value
|
|
rangeErrString := "" // set if a value is out of range
|
|
amSet := false // do we need to subtract 12 from the hour for midnight?
|
|
pmSet := false // do we need to add 12 to the hour?
|
|
|
|
// Time being constructed.
|
|
var (
|
|
year int
|
|
month int = 1 // January
|
|
day int = 1
|
|
hour int
|
|
min int
|
|
sec int
|
|
nsec int
|
|
z *Location
|
|
zoneOffset int = -1
|
|
zoneName string
|
|
)
|
|
|
|
// Each iteration processes one std value.
|
|
for {
|
|
var err error
|
|
prefix, std, suffix := nextStdChunk(layout)
|
|
stdstr := layout[len(prefix) : len(layout)-len(suffix)]
|
|
value, err = skip(value, prefix)
|
|
if err != nil {
|
|
return Time{}, &ParseError{alayout, avalue, prefix, value, ""}
|
|
}
|
|
if std == 0 {
|
|
if len(value) != 0 {
|
|
return Time{}, &ParseError{alayout, avalue, "", value, ": extra text: " + value}
|
|
}
|
|
break
|
|
}
|
|
layout = suffix
|
|
var p string
|
|
switch std & stdMask {
|
|
case stdYear:
|
|
if len(value) < 2 {
|
|
err = errBad
|
|
break
|
|
}
|
|
p, value = value[0:2], value[2:]
|
|
year, err = atoi(p)
|
|
if year >= 69 { // Unix time starts Dec 31 1969 in some time zones
|
|
year += 1900
|
|
} else {
|
|
year += 2000
|
|
}
|
|
case stdLongYear:
|
|
if len(value) < 4 || !isDigit(value, 0) {
|
|
err = errBad
|
|
break
|
|
}
|
|
p, value = value[0:4], value[4:]
|
|
year, err = atoi(p)
|
|
case stdMonth:
|
|
month, value, err = lookup(shortMonthNames, value)
|
|
case stdLongMonth:
|
|
month, value, err = lookup(longMonthNames, value)
|
|
case stdNumMonth, stdZeroMonth:
|
|
month, value, err = getnum(value, std == stdZeroMonth)
|
|
if month <= 0 || 12 < month {
|
|
rangeErrString = "month"
|
|
}
|
|
case stdWeekDay:
|
|
// Ignore weekday except for error checking.
|
|
_, value, err = lookup(shortDayNames, value)
|
|
case stdLongWeekDay:
|
|
_, value, err = lookup(longDayNames, value)
|
|
case stdDay, stdUnderDay, stdZeroDay:
|
|
if std == stdUnderDay && len(value) > 0 && value[0] == ' ' {
|
|
value = value[1:]
|
|
}
|
|
day, value, err = getnum(value, std == stdZeroDay)
|
|
if day < 0 || 31 < day {
|
|
rangeErrString = "day"
|
|
}
|
|
case stdHour:
|
|
hour, value, err = getnum(value, false)
|
|
if hour < 0 || 24 <= hour {
|
|
rangeErrString = "hour"
|
|
}
|
|
case stdHour12, stdZeroHour12:
|
|
hour, value, err = getnum(value, std == stdZeroHour12)
|
|
if hour < 0 || 12 < hour {
|
|
rangeErrString = "hour"
|
|
}
|
|
case stdMinute, stdZeroMinute:
|
|
min, value, err = getnum(value, std == stdZeroMinute)
|
|
if min < 0 || 60 <= min {
|
|
rangeErrString = "minute"
|
|
}
|
|
case stdSecond, stdZeroSecond:
|
|
sec, value, err = getnum(value, std == stdZeroSecond)
|
|
if sec < 0 || 60 <= sec {
|
|
rangeErrString = "second"
|
|
}
|
|
// Special case: do we have a fractional second but no
|
|
// fractional second in the format?
|
|
if len(value) >= 2 && value[0] == '.' && isDigit(value, 1) {
|
|
_, std, _ = nextStdChunk(layout)
|
|
std &= stdMask
|
|
if std == stdFracSecond0 || std == stdFracSecond9 {
|
|
// Fractional second in the layout; proceed normally
|
|
break
|
|
}
|
|
// No fractional second in the layout but we have one in the input.
|
|
n := 2
|
|
for ; n < len(value) && isDigit(value, n); n++ {
|
|
}
|
|
nsec, rangeErrString, err = parseNanoseconds(value, n)
|
|
value = value[n:]
|
|
}
|
|
case stdPM:
|
|
if len(value) < 2 {
|
|
err = errBad
|
|
break
|
|
}
|
|
p, value = value[0:2], value[2:]
|
|
switch p {
|
|
case "PM":
|
|
pmSet = true
|
|
case "AM":
|
|
amSet = true
|
|
default:
|
|
err = errBad
|
|
}
|
|
case stdpm:
|
|
if len(value) < 2 {
|
|
err = errBad
|
|
break
|
|
}
|
|
p, value = value[0:2], value[2:]
|
|
switch p {
|
|
case "pm":
|
|
pmSet = true
|
|
case "am":
|
|
amSet = true
|
|
default:
|
|
err = errBad
|
|
}
|
|
case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ:
|
|
if (std == stdISO8601TZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' {
|
|
value = value[1:]
|
|
z = UTC
|
|
break
|
|
}
|
|
var sign, hour, min, seconds string
|
|
if std == stdISO8601ColonTZ || std == stdNumColonTZ {
|
|
if len(value) < 6 {
|
|
err = errBad
|
|
break
|
|
}
|
|
if value[3] != ':' {
|
|
err = errBad
|
|
break
|
|
}
|
|
sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:]
|
|
} else if std == stdNumShortTZ {
|
|
if len(value) < 3 {
|
|
err = errBad
|
|
break
|
|
}
|
|
sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:]
|
|
} else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ {
|
|
if len(value) < 9 {
|
|
err = errBad
|
|
break
|
|
}
|
|
if value[3] != ':' || value[6] != ':' {
|
|
err = errBad
|
|
break
|
|
}
|
|
sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:]
|
|
} else if std == stdISO8601SecondsTZ || std == stdNumSecondsTz {
|
|
if len(value) < 7 {
|
|
err = errBad
|
|
break
|
|
}
|
|
sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:]
|
|
} else {
|
|
if len(value) < 5 {
|
|
err = errBad
|
|
break
|
|
}
|
|
sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:]
|
|
}
|
|
var hr, mm, ss int
|
|
hr, err = atoi(hour)
|
|
if err == nil {
|
|
mm, err = atoi(min)
|
|
}
|
|
if err == nil {
|
|
ss, err = atoi(seconds)
|
|
}
|
|
zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds
|
|
switch sign[0] {
|
|
case '+':
|
|
case '-':
|
|
zoneOffset = -zoneOffset
|
|
default:
|
|
err = errBad
|
|
}
|
|
case stdTZ:
|
|
// Does it look like a time zone?
|
|
if len(value) >= 3 && value[0:3] == "UTC" {
|
|
z = UTC
|
|
value = value[3:]
|
|
break
|
|
}
|
|
n, ok := parseTimeZone(value)
|
|
if !ok {
|
|
err = errBad
|
|
break
|
|
}
|
|
zoneName, value = value[:n], value[n:]
|
|
|
|
case stdFracSecond0:
|
|
// stdFracSecond0 requires the exact number of digits as specified in
|
|
// the layout.
|
|
ndigit := 1 + (std >> stdArgShift)
|
|
if len(value) < ndigit {
|
|
err = errBad
|
|
break
|
|
}
|
|
nsec, rangeErrString, err = parseNanoseconds(value, ndigit)
|
|
value = value[ndigit:]
|
|
|
|
case stdFracSecond9:
|
|
if len(value) < 2 || value[0] != '.' || value[1] < '0' || '9' < value[1] {
|
|
// Fractional second omitted.
|
|
break
|
|
}
|
|
// Take any number of digits, even more than asked for,
|
|
// because it is what the stdSecond case would do.
|
|
i := 0
|
|
for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' {
|
|
i++
|
|
}
|
|
nsec, rangeErrString, err = parseNanoseconds(value, 1+i)
|
|
value = value[1+i:]
|
|
}
|
|
if rangeErrString != "" {
|
|
return Time{}, &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"}
|
|
}
|
|
if err != nil {
|
|
return Time{}, &ParseError{alayout, avalue, stdstr, value, ""}
|
|
}
|
|
}
|
|
if pmSet && hour < 12 {
|
|
hour += 12
|
|
} else if amSet && hour == 12 {
|
|
hour = 0
|
|
}
|
|
|
|
if z != nil {
|
|
return Date(year, Month(month), day, hour, min, sec, nsec, z), nil
|
|
}
|
|
|
|
if zoneOffset != -1 {
|
|
t := Date(year, Month(month), day, hour, min, sec, nsec, UTC)
|
|
t.sec -= int64(zoneOffset)
|
|
|
|
// Look for local zone with the given offset.
|
|
// If that zone was in effect at the given time, use it.
|
|
name, offset, _, _, _ := local.lookup(t.sec + internalToUnix)
|
|
if offset == zoneOffset && (zoneName == "" || name == zoneName) {
|
|
t.loc = local
|
|
return t, nil
|
|
}
|
|
|
|
// Otherwise create fake zone to record offset.
|
|
t.loc = FixedZone(zoneName, zoneOffset)
|
|
return t, nil
|
|
}
|
|
|
|
if zoneName != "" {
|
|
t := Date(year, Month(month), day, hour, min, sec, nsec, UTC)
|
|
// Look for local zone with the given offset.
|
|
// If that zone was in effect at the given time, use it.
|
|
offset, _, ok := local.lookupName(zoneName, t.sec+internalToUnix)
|
|
if ok {
|
|
t.sec -= int64(offset)
|
|
t.loc = local
|
|
return t, nil
|
|
}
|
|
|
|
// Otherwise, create fake zone with unknown offset.
|
|
if len(zoneName) > 3 && zoneName[:3] == "GMT" {
|
|
offset, _ = atoi(zoneName[3:]) // Guaranteed OK by parseGMT.
|
|
offset *= 3600
|
|
}
|
|
t.loc = FixedZone(zoneName, offset)
|
|
return t, nil
|
|
}
|
|
|
|
// Otherwise, fall back to default.
|
|
return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil
|
|
}
|
|
|
|
// parseTimeZone parses a time zone string and returns its length. Time zones
|
|
// are human-generated and unpredictable. We can't do precise error checking.
|
|
// On the other hand, for a correct parse there must be a time zone at the
|
|
// beginning of the string, so it's almost always true that there's one
|
|
// there. We look at the beginning of the string for a run of upper-case letters.
|
|
// If there are more than 5, it's an error.
|
|
// If there are 4 or 5 and the last is a T, it's a time zone.
|
|
// If there are 3, it's a time zone.
|
|
// Otherwise, other than special cases, it's not a time zone.
|
|
// GMT is special because it can have an hour offset.
|
|
func parseTimeZone(value string) (length int, ok bool) {
|
|
if len(value) < 3 {
|
|
return 0, false
|
|
}
|
|
// Special case 1: ChST and MeST are the only zones with a lower-case letter.
|
|
if len(value) >= 4 && (value[:4] == "ChST" || value[:4] == "MeST") {
|
|
return 4, true
|
|
}
|
|
// Special case 2: GMT may have an hour offset; treat it specially.
|
|
if value[:3] == "GMT" {
|
|
length = parseGMT(value)
|
|
return length, true
|
|
}
|
|
// How many upper-case letters are there? Need at least three, at most five.
|
|
var nUpper int
|
|
for nUpper = 0; nUpper < 6; nUpper++ {
|
|
if nUpper >= len(value) {
|
|
break
|
|
}
|
|
if c := value[nUpper]; c < 'A' || 'Z' < c {
|
|
break
|
|
}
|
|
}
|
|
switch nUpper {
|
|
case 0, 1, 2, 6:
|
|
return 0, false
|
|
case 5: // Must end in T to match.
|
|
if value[4] == 'T' {
|
|
return 5, true
|
|
}
|
|
case 4: // Must end in T to match.
|
|
if value[3] == 'T' {
|
|
return 4, true
|
|
}
|
|
case 3:
|
|
return 3, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// parseGMT parses a GMT time zone. The input string is known to start "GMT".
|
|
// The function checks whether that is followed by a sign and a number in the
|
|
// range -14 through 12 excluding zero.
|
|
func parseGMT(value string) int {
|
|
value = value[3:]
|
|
if len(value) == 0 {
|
|
return 3
|
|
}
|
|
sign := value[0]
|
|
if sign != '-' && sign != '+' {
|
|
return 3
|
|
}
|
|
x, rem, err := leadingInt(value[1:])
|
|
if err != nil {
|
|
return 3
|
|
}
|
|
if sign == '-' {
|
|
x = -x
|
|
}
|
|
if x == 0 || x < -14 || 12 < x {
|
|
return 3
|
|
}
|
|
return 3 + len(value) - len(rem)
|
|
}
|
|
|
|
func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) {
|
|
if value[0] != '.' {
|
|
err = errBad
|
|
return
|
|
}
|
|
if ns, err = atoi(value[1:nbytes]); err != nil {
|
|
return
|
|
}
|
|
if ns < 0 || 1e9 <= ns {
|
|
rangeErrString = "fractional second"
|
|
return
|
|
}
|
|
// We need nanoseconds, which means scaling by the number
|
|
// of missing digits in the format, maximum length 10. If it's
|
|
// longer than 10, we won't scale.
|
|
scaleDigits := 10 - nbytes
|
|
for i := 0; i < scaleDigits; i++ {
|
|
ns *= 10
|
|
}
|
|
return
|
|
}
|
|
|
|
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
|
|
|
|
// leadingInt consumes the leading [0-9]* from s.
|
|
func leadingInt(s string) (x int64, rem string, err error) {
|
|
i := 0
|
|
for ; i < len(s); i++ {
|
|
c := s[i]
|
|
if c < '0' || c > '9' {
|
|
break
|
|
}
|
|
if x >= (1<<63-10)/10 {
|
|
// overflow
|
|
return 0, "", errLeadingInt
|
|
}
|
|
x = x*10 + int64(c) - '0'
|
|
}
|
|
return x, s[i:], nil
|
|
}
|
|
|
|
var unitMap = map[string]float64{
|
|
"ns": float64(Nanosecond),
|
|
"us": float64(Microsecond),
|
|
"µs": float64(Microsecond), // U+00B5 = micro symbol
|
|
"μs": float64(Microsecond), // U+03BC = Greek letter mu
|
|
"ms": float64(Millisecond),
|
|
"s": float64(Second),
|
|
"m": float64(Minute),
|
|
"h": float64(Hour),
|
|
}
|
|
|
|
// ParseDuration parses a duration string.
|
|
// A duration string is a possibly signed sequence of
|
|
// decimal numbers, each with optional fraction and a unit suffix,
|
|
// such as "300ms", "-1.5h" or "2h45m".
|
|
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
|
func ParseDuration(s string) (Duration, error) {
|
|
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
|
|
orig := s
|
|
f := float64(0)
|
|
neg := false
|
|
|
|
// Consume [-+]?
|
|
if s != "" {
|
|
c := s[0]
|
|
if c == '-' || c == '+' {
|
|
neg = c == '-'
|
|
s = s[1:]
|
|
}
|
|
}
|
|
// Special case: if all that is left is "0", this is zero.
|
|
if s == "0" {
|
|
return 0, nil
|
|
}
|
|
if s == "" {
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
for s != "" {
|
|
g := float64(0) // this element of the sequence
|
|
|
|
var x int64
|
|
var err error
|
|
|
|
// The next character must be [0-9.]
|
|
if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) {
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
// Consume [0-9]*
|
|
pl := len(s)
|
|
x, s, err = leadingInt(s)
|
|
if err != nil {
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
g = float64(x)
|
|
pre := pl != len(s) // whether we consumed anything before a period
|
|
|
|
// Consume (\.[0-9]*)?
|
|
post := false
|
|
if s != "" && s[0] == '.' {
|
|
s = s[1:]
|
|
pl := len(s)
|
|
x, s, err = leadingInt(s)
|
|
if err != nil {
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
scale := 1.0
|
|
for n := pl - len(s); n > 0; n-- {
|
|
scale *= 10
|
|
}
|
|
g += float64(x) / scale
|
|
post = pl != len(s)
|
|
}
|
|
if !pre && !post {
|
|
// no digits (e.g. ".s" or "-.s")
|
|
return 0, errors.New("time: invalid duration " + orig)
|
|
}
|
|
|
|
// Consume unit.
|
|
i := 0
|
|
for ; i < len(s); i++ {
|
|
c := s[i]
|
|
if c == '.' || ('0' <= c && c <= '9') {
|
|
break
|
|
}
|
|
}
|
|
if i == 0 {
|
|
return 0, errors.New("time: missing unit in duration " + orig)
|
|
}
|
|
u := s[:i]
|
|
s = s[i:]
|
|
unit, ok := unitMap[u]
|
|
if !ok {
|
|
return 0, errors.New("time: unknown unit " + u + " in duration " + orig)
|
|
}
|
|
|
|
f += g * unit
|
|
}
|
|
|
|
if neg {
|
|
f = -f
|
|
}
|
|
if f < float64(-1<<63) || f > float64(1<<63-1) {
|
|
return 0, errors.New("time: overflow parsing duration")
|
|
}
|
|
return Duration(f), nil
|
|
}
|