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