surql_parser/upstream/syn/lexer/strings/
datetime.rs1use crate::compat::types::PublicDatetime;
2use crate::upstream::syn::error::{SyntaxError, bail, syntax_error};
3use crate::upstream::syn::lexer::{BytesReader, Lexer};
4use chrono::{FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, Offset as _, TimeZone as _, Utc};
5use std::ops::RangeInclusive;
6impl Lexer<'_> {
7 pub fn lex_datetime(str: &str) -> Result<PublicDatetime, SyntaxError> {
9 let mut reader = BytesReader::new(str.as_bytes());
10 let date_start = reader.offset();
11 let neg = reader.eat(b'-');
12 if !neg {
13 reader.eat(b'+');
14 }
15 let year = Self::parse_datetime_digits(&mut reader, 4..=6, 0..=usize::MAX)?;
16 Self::expect_seperator(&mut reader, b'-')?;
17 let month = Self::parse_datetime_digits(&mut reader, 2..=2, 1..=12)?;
18 Self::expect_seperator(&mut reader, b'-')?;
19 let day = Self::parse_datetime_digits(&mut reader, 2..=2, 1..=31)?;
20 let year = if neg { -(year as i32) } else { year as i32 };
21 let date = NaiveDate::from_ymd_opt(year, month as u32, day as u32).ok_or_else(|| {
22 syntax_error!(
23 "Invalid Datetime date: date outside of valid range", @ reader
24 .span_since(date_start)
25 )
26 })?;
27 let before = reader.offset();
28 match reader.next() {
29 Some(b't' | b'T' | b' ') => {}
30 Some(x) => {
31 let c = reader.convert_to_char(x)?;
32 let span = reader.span_since(before);
33 bail!("Unexpected character `{c}`, expected time seperator `T`", @ span)
34 }
35 None => {
36 let time = NaiveTime::default();
37 let date_time = NaiveDateTime::new(date, time);
38 let datetime = Utc
39 .fix()
40 .from_local_datetime(&date_time)
41 .earliest()
42 .expect("valid datetime")
43 .with_timezone(&Utc);
44 return Ok(PublicDatetime::from(datetime));
45 }
46 }
47 let time_start = reader.offset();
48 let hour = Self::parse_datetime_digits(&mut reader, 2..=2, 0..=24)?;
49 Self::expect_seperator(&mut reader, b':')?;
50 let minute = Self::parse_datetime_digits(&mut reader, 2..=2, 0..=59)?;
51 Self::expect_seperator(&mut reader, b':')?;
52 let second = Self::parse_datetime_digits(&mut reader, 2..=2, 0..=60)?;
53 let nanos = if reader.eat(b'.') {
54 let nanos_start = reader.offset();
55 let mut number = 0u32;
56 let mut count = 0;
57 loop {
58 let Some(d) = reader.peek() else {
59 break;
60 };
61 if !d.is_ascii_digit() {
62 break;
63 }
64 reader.next();
65 if count == 9 {
66 if d - b'0' >= 5 {
67 number += 1;
68 }
69 continue;
70 } else if count >= 9 {
71 continue;
72 }
73 number *= 10;
74 number += (d - b'0') as u32;
75 count += 1;
76 }
77 if count == 0 {
78 bail!(
79 "Invalid datetime nanoseconds, expected at least a single digit", @
80 reader.span_since(nanos_start)
81 )
82 }
83 for _ in count..9 {
84 number *= 10;
85 }
86 number
87 } else {
88 0
89 };
90 let time = NaiveTime::from_hms_nano_opt(hour as u32, minute as u32, second as u32, nanos)
91 .ok_or_else(|| {
92 syntax_error!(
93 "Invalid Datetime time: time outside of valid range", @ reader
94 .span_since(time_start)
95 )
96 })?;
97 let timezone_start = reader.offset();
98 let timezone = match reader.next() {
99 Some(x @ (b'-' | b'+')) => {
100 let hour = Self::parse_datetime_digits(&mut reader, 2..=2, 0..=23)?;
101 Self::expect_seperator(&mut reader, b':')?;
102 let minutes = Self::parse_datetime_digits(&mut reader, 2..=2, 0..=59)?;
103 if x == b'-' {
104 FixedOffset::west_opt((hour * 3600 + minutes * 60) as i32)
105 .expect("valid timezone offset")
106 } else {
107 FixedOffset::east_opt((hour * 3600 + minutes * 60) as i32)
108 .expect("valid timezone offset")
109 }
110 }
111 Some(b'Z' | b'z') => Utc.fix(),
112 Some(x) => {
113 let c = reader.convert_to_char(x)?;
114 let span = reader.span_since(before);
115 bail!(
116 "Unexpected character `{c}`, expected `Z` or a timezone offset.",@
117 span
118 )
119 }
120 None => {
121 let span = reader.span_since(timezone_start);
122 bail!("Invalid end of datetime, expected datetime timezone", @ span)
123 }
124 };
125 let date_time = NaiveDateTime::new(date, time);
126 let Some(datetime) = timezone.from_local_datetime(&date_time).earliest() else {
127 bail!(
128 "Invalid Datetime, timezone was outside of the range of valid datetimes",
129 @ reader.span_since(timezone_start)
130 )
131 };
132 let datetime = datetime.with_timezone(&Utc);
133 Ok(PublicDatetime::from(datetime))
134 }
135 fn expect_seperator(reader: &mut BytesReader, sep: u8) -> Result<(), SyntaxError> {
136 match reader.peek() {
137 Some(x) if x == sep => {
138 reader.next();
139 Ok(())
140 }
141 Some(x) => {
142 let before = reader.offset();
143 reader.next();
144 let c = reader.convert_to_char(x)?;
145 let span = reader.span_since(before);
146 bail!(
147 "Unexpected character `{c}`, expected datetime seperator characters `{}`",
148 sep as char, @ span
149 )
150 }
151 None => {
152 let before = reader.offset();
153 let span = reader.span_since(before);
154 bail!(
155 "Expected end of string, expected datetime seperator character `{}`",
156 sep as char, @ span
157 );
158 }
159 }
160 }
161 fn parse_datetime_digits(
162 reader: &mut BytesReader,
163 amount: RangeInclusive<usize>,
164 range: RangeInclusive<usize>,
165 ) -> Result<usize, SyntaxError> {
166 let start = reader.offset();
167 let mut value = 0usize;
168 for _ in 0..(*amount.start()) {
169 let offset = reader.offset();
170 match reader.next() {
171 Some(x) if x.is_ascii_digit() => {
172 value *= 10;
173 value += (x - b'0') as usize;
174 }
175 Some(x) => {
176 let char = reader.convert_to_char(x)?;
177 let span = reader.span_since(offset);
178 bail!(
179 "Invalid datetime, expected digit character found `{char}`", @
180 span
181 );
182 }
183 None => {
184 let span = reader.span_since(offset);
185 bail!(
186 "Expected end of datetime, expected datetime digit character", @
187 span
188 );
189 }
190 }
191 }
192 for _ in amount {
193 match reader.peek() {
194 Some(x) if x.is_ascii_digit() => {
195 reader.next();
196 value *= 10;
197 value += (x - b'0') as usize;
198 }
199 _ => break,
200 }
201 }
202 if !range.contains(&value) {
203 let span = reader.span_since(start);
204 bail!(
205 "Invalid datetime digit section, section not within allowed range", @
206 span => "This section must be within {}..={}", range.start(), range.end()
207 );
208 }
209 Ok(value)
210 }
211}