| // Copyright 2022 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" |
| |
| // RFC 3339 is the most commonly used format. |
| // |
| // It is implicitly used by the Time.(Marshal|Unmarshal)(Text|JSON) methods. |
| // Also, according to analysis on https://go.dev/issue/52746, |
| // RFC 3339 accounts for 57% of all explicitly specified time formats, |
| // with the second most popular format only being used 8% of the time. |
| // The overwhelming use of RFC 3339 compared to all other formats justifies |
| // the addition of logic to optimize formatting and parsing. |
| |
| func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte { |
| _, offset, abs := t.locabs() |
| |
| // Format date. |
| year, month, day, _ := absDate(abs, true) |
| b = appendInt(b, year, 4) |
| b = append(b, '-') |
| b = appendInt(b, int(month), 2) |
| b = append(b, '-') |
| b = appendInt(b, day, 2) |
| |
| b = append(b, 'T') |
| |
| // Format time. |
| hour, min, sec := absClock(abs) |
| b = appendInt(b, hour, 2) |
| b = append(b, ':') |
| b = appendInt(b, min, 2) |
| b = append(b, ':') |
| b = appendInt(b, sec, 2) |
| |
| if nanos { |
| std := stdFracSecond(stdFracSecond9, 9, '.') |
| b = appendNano(b, t.Nanosecond(), std) |
| } |
| |
| if offset == 0 { |
| return append(b, 'Z') |
| } |
| |
| // Format zone. |
| zone := offset / 60 // convert to minutes |
| if zone < 0 { |
| b = append(b, '-') |
| zone = -zone |
| } else { |
| b = append(b, '+') |
| } |
| b = appendInt(b, zone/60, 2) |
| b = append(b, ':') |
| b = appendInt(b, zone%60, 2) |
| return b |
| } |
| |
| func (t Time) appendStrictRFC3339(b []byte) ([]byte, error) { |
| n0 := len(b) |
| b = t.appendFormatRFC3339(b, true) |
| |
| // Not all valid Go timestamps can be serialized as valid RFC 3339. |
| // Explicitly check for these edge cases. |
| // See https://go.dev/issue/4556 and https://go.dev/issue/54580. |
| num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') } |
| switch { |
| case b[n0+len("9999")] != '-': // year must be exactly 4 digits wide |
| return b, errors.New("year outside of range [0,9999]") |
| case b[len(b)-1] != 'Z': |
| c := b[len(b)-len("Z07:00")] |
| if ('0' <= c && c <= '9') || num2(b[len(b)-len("07:00"):]) >= 24 { |
| return b, errors.New("timezone hour outside of range [0,23]") |
| } |
| } |
| return b, nil |
| } |
| |
| func parseRFC3339[bytes []byte | string](s bytes, local *Location) (Time, bool) { |
| // parseUint parses s as an unsigned decimal integer and |
| // verifies that it is within some range. |
| // If it is invalid or out-of-range, |
| // it sets ok to false and returns the min value. |
| ok := true |
| parseUint := func(s bytes, min, max int) (x int) { |
| for _, c := range []byte(s) { |
| if c < '0' || '9' < c { |
| ok = false |
| return min |
| } |
| x = x*10 + int(c) - '0' |
| } |
| if x < min || max < x { |
| ok = false |
| return min |
| } |
| return x |
| } |
| |
| // Parse the date and time. |
| if len(s) < len("2006-01-02T15:04:05") { |
| return Time{}, false |
| } |
| year := parseUint(s[0:4], 0, 9999) // e.g., 2006 |
| month := parseUint(s[5:7], 1, 12) // e.g., 01 |
| day := parseUint(s[8:10], 1, daysIn(Month(month), year)) // e.g., 02 |
| hour := parseUint(s[11:13], 0, 23) // e.g., 15 |
| min := parseUint(s[14:16], 0, 59) // e.g., 04 |
| sec := parseUint(s[17:19], 0, 59) // e.g., 05 |
| if !ok || !(s[4] == '-' && s[7] == '-' && s[10] == 'T' && s[13] == ':' && s[16] == ':') { |
| return Time{}, false |
| } |
| s = s[19:] |
| |
| // Parse the fractional second. |
| var nsec int |
| if len(s) >= 2 && s[0] == '.' && isDigit(s, 1) { |
| n := 2 |
| for ; n < len(s) && isDigit(s, n); n++ { |
| } |
| nsec, _, _ = parseNanoseconds(s, n) |
| s = s[n:] |
| } |
| |
| // Parse the time zone. |
| t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) |
| if len(s) != 1 || s[0] != 'Z' { |
| if len(s) != len("-07:00") { |
| return Time{}, false |
| } |
| hr := parseUint(s[1:3], 0, 23) // e.g., 07 |
| mm := parseUint(s[4:6], 0, 59) // e.g., 00 |
| if !ok || !((s[0] == '-' || s[0] == '+') && s[3] == ':') { |
| return Time{}, false |
| } |
| zoneOffset := (hr*60 + mm) * 60 |
| if s[0] == '-' { |
| zoneOffset *= -1 |
| } |
| t.addSec(-int64(zoneOffset)) |
| |
| // Use local zone with the given offset if possible. |
| if _, offset, _, _, _ := local.lookup(t.unixSec()); offset == zoneOffset { |
| t.setLoc(local) |
| } else { |
| t.setLoc(FixedZone("", zoneOffset)) |
| } |
| } |
| return t, true |
| } |
| |
| func parseStrictRFC3339(b []byte) (Time, error) { |
| t, ok := parseRFC3339(b, Local) |
| if !ok { |
| if _, err := Parse(RFC3339, string(b)); err != nil { |
| return Time{}, err |
| } |
| |
| // The parse template syntax cannot correctly validate RFC 3339. |
| // Explicitly check for cases that Parse is unable to validate for. |
| // See https://go.dev/issue/54580. |
| num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') } |
| switch { |
| case b[len("2006-01-02T")+1] == ':': // hour must be two digits |
| return Time{}, &ParseError{RFC3339, string(b), "15", string(b[len("2006-01-02T"):][:1]), ""} |
| case b[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period |
| return Time{}, &ParseError{RFC3339, string(b), ".", ",", ""} |
| case b[len(b)-1] != 'Z': |
| switch { |
| case num2(b[len(b)-len("07:00"):]) >= 24: // timezone hour must be in range |
| return Time{}, &ParseError{RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone hour out of range"} |
| case num2(b[len(b)-len("00"):]) >= 60: // timezone minute must be in range |
| return Time{}, &ParseError{RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone minute out of range"} |
| } |
| default: // unknown error; should not occur |
| return Time{}, &ParseError{RFC3339, string(b), RFC3339, string(b), ""} |
| } |
| } |
| return t, nil |
| } |