1use std::ops::RangeInclusive;
2
3use crate::parser::errors::CustomError;
4use crate::parser::prelude::*;
5use crate::parser::trivia::from_utf8_unchecked;
6
7use nom8::branch::alt;
8use nom8::bytes::one_of;
9use nom8::bytes::take_while_m_n;
10use nom8::combinator::cut;
11use nom8::combinator::opt;
12use nom8::sequence::preceded;
13use toml_datetime::*;
14
15pub(crate) fn date_time(input: Input<'_>) -> IResult<Input<'_>, Datetime, ParserError<'_>> {
24 alt((
25 (full_date, opt((time_delim, partial_time, opt(time_offset))))
26 .map(|(date, opt)| {
27 match opt {
28 Some((_, time, offset)) => Datetime {
30 date: Some(date),
31 time: Some(time),
32 offset,
33 },
34 None => Datetime {
36 date: Some(date),
37 time: None,
38 offset: None,
39 },
40 }
41 })
42 .context(Context::Expression("date-time")),
43 partial_time
44 .map(|t| t.into())
45 .context(Context::Expression("time")),
46 ))
47 .parse(input)
48}
49
50pub(crate) fn full_date(input: Input<'_>) -> IResult<Input<'_>, Date, ParserError<'_>> {
52 (date_fullyear, b'-', cut((date_month, b'-', date_mday)))
53 .map(|(year, _, (month, _, day))| Date { year, month, day })
54 .parse(input)
55}
56
57pub(crate) fn partial_time(input: Input<'_>) -> IResult<Input<'_>, Time, ParserError<'_>> {
59 (
60 time_hour,
61 b':',
62 cut((time_minute, b':', time_second, opt(time_secfrac))),
63 )
64 .map(|(hour, _, (minute, _, second, nanosecond))| Time {
65 hour,
66 minute,
67 second,
68 nanosecond: nanosecond.unwrap_or_default(),
69 })
70 .parse(input)
71}
72
73pub(crate) fn time_offset(input: Input<'_>) -> IResult<Input<'_>, Offset, ParserError<'_>> {
76 alt((
77 one_of((b'Z', b'z')).value(Offset::Z),
78 (one_of((b'+', b'-')), cut((time_hour, b':', time_minute))).map(
79 |(sign, (hours, _, minutes))| {
80 let hours = hours as i8;
81 let hours = match sign {
82 b'+' => hours,
83 b'-' => -hours,
84 _ => unreachable!("Parser prevents this"),
85 };
86 Offset::Custom { hours, minutes }
87 },
88 ),
89 ))
90 .context(Context::Expression("time offset"))
91 .parse(input)
92}
93
94pub(crate) fn date_fullyear(input: Input<'_>) -> IResult<Input<'_>, u16, ParserError<'_>> {
96 unsigned_digits::<4, 4>
97 .map(|s: &str| s.parse::<u16>().expect("4DIGIT should match u8"))
98 .parse(input)
99}
100
101pub(crate) fn date_month(input: Input<'_>) -> IResult<Input<'_>, u8, ParserError<'_>> {
103 unsigned_digits::<2, 2>
104 .map_res(|s: &str| {
105 let d = s.parse::<u8>().expect("2DIGIT should match u8");
106 if (1..=12).contains(&d) {
107 Ok(d)
108 } else {
109 Err(CustomError::OutOfRange)
110 }
111 })
112 .parse(input)
113}
114
115pub(crate) fn date_mday(input: Input<'_>) -> IResult<Input<'_>, u8, ParserError<'_>> {
117 unsigned_digits::<2, 2>
118 .map_res(|s: &str| {
119 let d = s.parse::<u8>().expect("2DIGIT should match u8");
120 if (1..=31).contains(&d) {
121 Ok(d)
122 } else {
123 Err(CustomError::OutOfRange)
124 }
125 })
126 .parse(input)
127}
128
129pub(crate) fn time_delim(input: Input<'_>) -> IResult<Input<'_>, u8, ParserError<'_>> {
131 one_of(TIME_DELIM).parse(input)
132}
133
134const TIME_DELIM: (u8, u8, u8) = (b'T', b't', b' ');
135
136pub(crate) fn time_hour(input: Input<'_>) -> IResult<Input<'_>, u8, ParserError<'_>> {
138 unsigned_digits::<2, 2>
139 .map_res(|s: &str| {
140 let d = s.parse::<u8>().expect("2DIGIT should match u8");
141 if (0..=23).contains(&d) {
142 Ok(d)
143 } else {
144 Err(CustomError::OutOfRange)
145 }
146 })
147 .parse(input)
148}
149
150pub(crate) fn time_minute(input: Input<'_>) -> IResult<Input<'_>, u8, ParserError<'_>> {
152 unsigned_digits::<2, 2>
153 .map_res(|s: &str| {
154 let d = s.parse::<u8>().expect("2DIGIT should match u8");
155 if (0..=59).contains(&d) {
156 Ok(d)
157 } else {
158 Err(CustomError::OutOfRange)
159 }
160 })
161 .parse(input)
162}
163
164pub(crate) fn time_second(input: Input<'_>) -> IResult<Input<'_>, u8, ParserError<'_>> {
166 unsigned_digits::<2, 2>
167 .map_res(|s: &str| {
168 let d = s.parse::<u8>().expect("2DIGIT should match u8");
169 if (0..=60).contains(&d) {
170 Ok(d)
171 } else {
172 Err(CustomError::OutOfRange)
173 }
174 })
175 .parse(input)
176}
177
178pub(crate) fn time_secfrac(input: Input<'_>) -> IResult<Input<'_>, u32, ParserError<'_>> {
180 static SCALE: [u32; 10] = [
181 0,
182 100_000_000,
183 10_000_000,
184 1_000_000,
185 100_000,
186 10_000,
187 1_000,
188 100,
189 10,
190 1,
191 ];
192 const INF: usize = usize::MAX;
193 preceded(b'.', unsigned_digits::<1, INF>)
194 .map_res(|mut repr: &str| -> Result<u32, CustomError> {
195 let max_digits = SCALE.len() - 1;
196 if max_digits < repr.len() {
197 repr = &repr[0..max_digits];
201 }
202
203 let v = repr.parse::<u32>().map_err(|_| CustomError::OutOfRange)?;
204 let num_digits = repr.len();
205
206 let scale = SCALE.get(num_digits).ok_or(CustomError::OutOfRange)?;
208 let v = v.checked_mul(*scale).ok_or(CustomError::OutOfRange)?;
209 Ok(v)
210 })
211 .parse(input)
212}
213
214pub(crate) fn unsigned_digits<const MIN: usize, const MAX: usize>(
215 input: Input<'_>,
216) -> IResult<Input<'_>, &str, ParserError<'_>> {
217 take_while_m_n(MIN, MAX, DIGIT)
218 .map(|b: &[u8]| unsafe { from_utf8_unchecked(b, "`is_ascii_digit` filters out on-ASCII") })
219 .parse(input)
220}
221
222const DIGIT: RangeInclusive<u8> = b'0'..=b'9';
224
225#[cfg(test)]
226mod test {
227 use super::*;
228
229 #[test]
230 fn offset_date_time() {
231 let inputs = [
232 "1979-05-27T07:32:00Z",
233 "1979-05-27T00:32:00-07:00",
234 "1979-05-27T00:32:00.999999-07:00",
235 ];
236 for input in inputs {
237 dbg!(input);
238 date_time.parse(new_input(input)).finish().unwrap();
239 }
240 }
241
242 #[test]
243 fn local_date_time() {
244 let inputs = ["1979-05-27T07:32:00", "1979-05-27T00:32:00.999999"];
245 for input in inputs {
246 dbg!(input);
247 date_time.parse(new_input(input)).finish().unwrap();
248 }
249 }
250
251 #[test]
252 fn local_date() {
253 let inputs = ["1979-05-27", "2017-07-20"];
254 for input in inputs {
255 dbg!(input);
256 date_time.parse(new_input(input)).finish().unwrap();
257 }
258 }
259
260 #[test]
261 fn local_time() {
262 let inputs = ["07:32:00", "00:32:00.999999"];
263 for input in inputs {
264 dbg!(input);
265 date_time.parse(new_input(input)).finish().unwrap();
266 }
267 }
268
269 #[test]
270 fn time_fraction_truncated() {
271 let input = "1987-07-05T17:45:00.123456789012345Z";
272 date_time.parse(new_input(input)).finish().unwrap();
273 }
274}