format_rfc3339.mx raw
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
5 package time
6
7 import "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
18 func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte {
19 _, offset, abs := t.locabs()
20
21 // Format date.
22 year, month, day := abs.days().date()
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 := abs.clock()
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
62 func (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
82 func parseRFC3339[bytes []byte](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
155 func parseStrictRFC3339(b []byte) (Time, error) {
156 t, ok := parseRFC3339(b, Local)
157 if !ok {
158 t, err := Parse(RFC3339, []byte(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, []byte(b), "15", []byte(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, []byte(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, []byte(b), "Z07:00", []byte(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, []byte(b), "Z07:00", []byte(b[len(b)-len("Z07:00"):]), ": timezone minute out of range"}
182 }
183 default: // unknown error; should not occur
184 return Time{}, &ParseError{RFC3339, []byte(b), RFC3339, []byte(b), ""}
185 }
186 }
187 return t, nil
188 }
189