1 package ldmodel
2
3 import (
4 "time"
5 "unicode"
6 )
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 func parseRFC3339TimeUTC(s string) (time.Time, bool) {
24 scanner := newSimpleASCIIScanner(s)
25
26 year, _, ok := parseDateTimeNumericField(&scanner, hyphenTerminator, false, 4, 4, 0, 9999)
27 if !ok {
28 return time.Time{}, false
29 }
30 month, _, ok := parseDateTimeNumericField(&scanner, hyphenTerminator, false, 2, 2, 1, 12)
31 if !ok {
32 return time.Time{}, false
33 }
34 day, _, ok := parseDateTimeNumericField(&scanner, tTerminator, false, 2, 2, 1, 31)
35 if !ok {
36 return time.Time{}, false
37 }
38 hour, _, ok := parseDateTimeNumericField(&scanner, colonTerminator, false, 1, 2, 0, 23)
39
40 if !ok {
41 return time.Time{}, false
42 }
43 minute, _, ok := parseDateTimeNumericField(&scanner, colonTerminator, false, 2, 2, 0, 59)
44 if !ok {
45 return time.Time{}, false
46 }
47 second, term, ok := parseDateTimeNumericField(&scanner, endOfSecondsTerminator, false, 2, 2, 0, 60)
48
49 if !ok {
50 return time.Time{}, false
51 }
52
53 var nanos int
54 if term == '.' {
55 var fractionStr string
56 fractionStr, term = scanner.readUntil(endOfFractionalSecondsTerminator)
57 if term < 0 || len(fractionStr) > 9 {
58
59 return time.Time{}, false
60 }
61 n, ok := parsePositiveNumericString(fractionStr)
62 if !ok {
63 return time.Time{}, false
64 }
65 nanos = n
66 for i := len(fractionStr); i < 9; i++ {
67 nanos *= 10
68 }
69 }
70
71 var tzOffsetSeconds int
72 if term == '+' || term == '-' {
73 offsetHours, _, ok := parseDateTimeNumericField(&scanner, colonTerminator, false, 2, 2, 0, 99)
74
75 if !ok {
76 return time.Time{}, false
77 }
78 offsetMinutes, _, ok := parseDateTimeNumericField(&scanner, noTerminator, true, 2, 2, 0, 59)
79 if !ok {
80 return time.Time{}, false
81 }
82 tzOffsetSeconds = (offsetMinutes + (offsetHours * 60)) * 60
83 if term == '+' {
84 tzOffsetSeconds = -tzOffsetSeconds
85 }
86 }
87
88 t := time.Date(year, time.Month(month), day, hour, minute, second, nanos, time.UTC)
89 if tzOffsetSeconds != 0 {
90 t = t.Add(time.Second * time.Duration(tzOffsetSeconds))
91 }
92
93 return t, true
94 }
95
96 func hyphenTerminator(ch rune) bool { return ch == '-' }
97 func tTerminator(ch rune) bool { return ch == 't' || ch == 'T' }
98 func colonTerminator(ch rune) bool { return ch == ':' }
99 func endOfSecondsTerminator(ch rune) bool {
100 return ch == '.' || ch == 'Z' || ch == 'z' || ch == '+' || ch == '-'
101 }
102 func endOfFractionalSecondsTerminator(ch rune) bool {
103 return ch == 'Z' || ch == 'z' || ch == '+' || ch == '-'
104 }
105
106 func parseDateTimeNumericField(
107 scanner *simpleASCIIScanner,
108 terminatorFn func(rune) bool,
109 eofOK bool,
110 minLength, maxLength, minValue, maxValue int,
111 ) (int, int8, bool) {
112 s, term := scanner.readUntil(terminatorFn)
113 if s == "" || (!eofOK && term < 0) {
114 return 0, term, false
115 }
116 length := len(s)
117 if length < minLength || length > maxLength {
118 return 0, term, false
119 }
120 n, ok := parsePositiveNumericString(s)
121 if !ok || n < minValue || n > maxValue {
122 return 0, term, false
123 }
124 return n, term, true
125 }
126
127
128 func parsePositiveNumericString(s string) (int, bool) {
129 max := len(s)
130 if max == 0 {
131 return 0, false
132 }
133 n := 0
134 for i := 0; i < max; i++ {
135 ch := rune(s[i])
136 if ch < '0' || ch > '9' {
137 return 0, false
138 }
139 n = n*10 + int(ch-'0')
140 }
141 return n, true
142 }
143
144
145
146 type simpleASCIIScanner struct {
147 source string
148 length int
149 pos int
150 }
151
152 const (
153 scannerEOF int8 = -1
154 scannerNonASCII int8 = -2
155 )
156
157 func noTerminator(rune) bool {
158 return false
159 }
160
161 func newSimpleASCIIScanner(source string) simpleASCIIScanner {
162 return simpleASCIIScanner{source: source, length: len(source)}
163 }
164
165 func (s *simpleASCIIScanner) peek() int8 {
166 if s.pos >= s.length {
167 return scannerEOF
168 }
169 var ch uint8 = s.source[s.pos]
170 if ch == 0 || ch > unicode.MaxASCII {
171 return scannerNonASCII
172 }
173 return int8(ch)
174 }
175
176 func (s *simpleASCIIScanner) next() int8 {
177 ch := s.peek()
178 if ch > 0 {
179 s.pos++
180 }
181 return ch
182 }
183
184 func (s *simpleASCIIScanner) readUntil(terminatorFn func(rune) bool) (substring string, terminatedBy int8) {
185 startPos := s.pos
186 var ch int8
187 for {
188 ch = s.next()
189 if ch < 0 || terminatorFn(rune(ch)) {
190 break
191 }
192 }
193 endPos := s.pos
194 if ch > 0 {
195 endPos--
196 }
197 return s.source[startPos:endPos], ch
198 }
199
View as plain text