1// Copyright 2022 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package time 6 7import "errors" 8 9// RFC 3339 is the most commonly used format. 10// 11// It is implicitly used by the Time.(Marshal|Unmarshal)(Text|JSON) methods. 12// Also, according to analysis on https://go.dev/issue/52746, 13// RFC 3339 accounts for 57% of all explicitly specified time formats, 14// with the second most popular format only being used 8% of the time. 15// The overwhelming use of RFC 3339 compared to all other formats justifies 16// the addition of logic to optimize formatting and parsing. 17 18func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte { 19 _, offset, abs := t.locabs() 20 21 // Format date. 22 year, month, day, _ := absDate(abs, true) 23 b = appendInt(b, year, 4) 24 b = append(b, '-') 25 b = appendInt(b, int(month), 2) 26 b = append(b, '-') 27 b = appendInt(b, day, 2) 28 29 b = append(b, 'T') 30 31 // Format time. 32 hour, min, sec := absClock(abs) 33 b = appendInt(b, hour, 2) 34 b = append(b, ':') 35 b = appendInt(b, min, 2) 36 b = append(b, ':') 37 b = appendInt(b, sec, 2) 38 39 if nanos { 40 std := stdFracSecond(stdFracSecond9, 9, '.') 41 b = appendNano(b, t.Nanosecond(), std) 42 } 43 44 if offset == 0 { 45 return append(b, 'Z') 46 } 47 48 // Format zone. 49 zone := offset / 60 // convert to minutes 50 if zone < 0 { 51 b = append(b, '-') 52 zone = -zone 53 } else { 54 b = append(b, '+') 55 } 56 b = appendInt(b, zone/60, 2) 57 b = append(b, ':') 58 b = appendInt(b, zone%60, 2) 59 return b 60} 61 62func (t Time) appendStrictRFC3339(b []byte) ([]byte, error) { 63 n0 := len(b) 64 b = t.appendFormatRFC3339(b, true) 65 66 // Not all valid Go timestamps can be serialized as valid RFC 3339. 67 // Explicitly check for these edge cases. 68 // See https://go.dev/issue/4556 and https://go.dev/issue/54580. 69 num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') } 70 switch { 71 case b[n0+len("9999")] != '-': // year must be exactly 4 digits wide 72 return b, errors.New("year outside of range [0,9999]") 73 case b[len(b)-1] != 'Z': 74 c := b[len(b)-len("Z07:00")] 75 if ('0' <= c && c <= '9') || num2(b[len(b)-len("07:00"):]) >= 24 { 76 return b, errors.New("timezone hour outside of range [0,23]") 77 } 78 } 79 return b, nil 80} 81 82func parseRFC3339[bytes []byte | string](s bytes, local *Location) (Time, bool) { 83 // parseUint parses s as an unsigned decimal integer and 84 // verifies that it is within some range. 85 // If it is invalid or out-of-range, 86 // it sets ok to false and returns the min value. 87 ok := true 88 parseUint := func(s bytes, min, max int) (x int) { 89 for _, c := range []byte(s) { 90 if c < '0' || '9' < c { 91 ok = false 92 return min 93 } 94 x = x*10 + int(c) - '0' 95 } 96 if x < min || max < x { 97 ok = false 98 return min 99 } 100 return x 101 } 102 103 // Parse the date and time. 104 if len(s) < len("2006-01-02T15:04:05") { 105 return Time{}, false 106 } 107 year := parseUint(s[0:4], 0, 9999) // e.g., 2006 108 month := parseUint(s[5:7], 1, 12) // e.g., 01 109 day := parseUint(s[8:10], 1, daysIn(Month(month), year)) // e.g., 02 110 hour := parseUint(s[11:13], 0, 23) // e.g., 15 111 min := parseUint(s[14:16], 0, 59) // e.g., 04 112 sec := parseUint(s[17:19], 0, 59) // e.g., 05 113 if !ok || !(s[4] == '-' && s[7] == '-' && s[10] == 'T' && s[13] == ':' && s[16] == ':') { 114 return Time{}, false 115 } 116 s = s[19:] 117 118 // Parse the fractional second. 119 var nsec int 120 if len(s) >= 2 && s[0] == '.' && isDigit(s, 1) { 121 n := 2 122 for ; n < len(s) && isDigit(s, n); n++ { 123 } 124 nsec, _, _ = parseNanoseconds(s, n) 125 s = s[n:] 126 } 127 128 // Parse the time zone. 129 t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) 130 if len(s) != 1 || s[0] != 'Z' { 131 if len(s) != len("-07:00") { 132 return Time{}, false 133 } 134 hr := parseUint(s[1:3], 0, 23) // e.g., 07 135 mm := parseUint(s[4:6], 0, 59) // e.g., 00 136 if !ok || !((s[0] == '-' || s[0] == '+') && s[3] == ':') { 137 return Time{}, false 138 } 139 zoneOffset := (hr*60 + mm) * 60 140 if s[0] == '-' { 141 zoneOffset *= -1 142 } 143 t.addSec(-int64(zoneOffset)) 144 145 // Use local zone with the given offset if possible. 146 if _, offset, _, _, _ := local.lookup(t.unixSec()); offset == zoneOffset { 147 t.setLoc(local) 148 } else { 149 t.setLoc(FixedZone("", zoneOffset)) 150 } 151 } 152 return t, true 153} 154 155func parseStrictRFC3339(b []byte) (Time, error) { 156 t, ok := parseRFC3339(b, Local) 157 if !ok { 158 t, err := Parse(RFC3339, string(b)) 159 if err != nil { 160 return Time{}, err 161 } 162 163 // The parse template syntax cannot correctly validate RFC 3339. 164 // Explicitly check for cases that Parse is unable to validate for. 165 // See https://go.dev/issue/54580. 166 num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') } 167 switch { 168 // TODO(https://go.dev/issue/54580): Strict parsing is disabled for now. 169 // Enable this again with a GODEBUG opt-out. 170 case true: 171 return t, nil 172 case b[len("2006-01-02T")+1] == ':': // hour must be two digits 173 return Time{}, &ParseError{RFC3339, string(b), "15", string(b[len("2006-01-02T"):][:1]), ""} 174 case b[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period 175 return Time{}, &ParseError{RFC3339, string(b), ".", ",", ""} 176 case b[len(b)-1] != 'Z': 177 switch { 178 case num2(b[len(b)-len("07:00"):]) >= 24: // timezone hour must be in range 179 return Time{}, &ParseError{RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone hour out of range"} 180 case num2(b[len(b)-len("00"):]) >= 60: // timezone minute must be in range 181 return Time{}, &ParseError{RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone minute out of range"} 182 } 183 default: // unknown error; should not occur 184 return Time{}, &ParseError{RFC3339, string(b), RFC3339, string(b), ""} 185 } 186 } 187 return t, nil 188} 189