diff options
Diffstat (limited to 'src/pkg/time/format.go')
-rw-r--r-- | src/pkg/time/format.go | 1247 |
1 files changed, 0 insertions, 1247 deletions
diff --git a/src/pkg/time/format.go b/src/pkg/time/format.go deleted file mode 100644 index 9f210ea27..000000000 --- a/src/pkg/time/format.go +++ /dev/null @@ -1,1247 +0,0 @@ -// 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: -// 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, -// 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 { - 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, -// 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 parse 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 -} |