use crate::datetime::*;
use crate::parser::errors::CustomError;
use crate::parser::trivia::from_utf8_unchecked;
use combine::parser::byte::byte;
use combine::parser::range::{recognize, take_while1};
use combine::stream::RangeStream;
use combine::*;
parse!(date_time() -> Datetime, {
choice!(
(
full_date(),
optional((
satisfy(is_time_delim),
partial_time(),
optional(time_offset()),
))
)
.map(|(date, opt)| {
match opt {
Some((_, time, offset)) => {
Datetime { date: Some(date), time: Some(time), offset }
}
None => {
Datetime { date: Some(date), time: None, offset: None}
},
}
}),
partial_time()
.message("While parsing a Time")
.map(|t| {
t.into()
})
)
.message("While parsing a Date-Time")
});
parse!(full_date() -> Date, {
(
attempt((date_fullyear(), byte(b'-'))),
date_month(),
byte(b'-'),
date_mday(),
).map(|((year, _), month, _, day)| {
Date { year, month, day }
})
});
parse!(partial_time() -> Time, {
(
attempt((
time_hour(),
byte(b':'),
)),
time_minute(),
byte(b':'),
time_second(),
optional(attempt(time_secfrac())),
).map(|((hour, _), minute, _, second, nanosecond)| {
Time { hour, minute, second, nanosecond: nanosecond.unwrap_or_default() }
})
});
parse!(time_offset() -> Offset, {
attempt(satisfy(|c| c == b'Z' || c == b'z')).map(|_| Offset::Z)
.or(
(
attempt(choice([byte(b'+'), byte(b'-')])),
time_hour(),
byte(b':'),
time_minute(),
).map(|(sign, hours, _, minutes)| {
let hours = hours as i8;
let hours = match sign {
b'+' => hours,
b'-' => -hours,
_ => unreachable!("Parser prevents this"),
};
Offset::Custom { hours, minutes }
})
).message("While parsing a Time Offset")
});
parse!(date_fullyear() -> u16, {
signed_digits(4).map(|d| d as u16)
});
parse!(date_month() -> u8, {
unsigned_digits(2).map(|d| d as u8).and_then(|v| {
if (1..=12).contains(&v) {
Ok(v)
} else {
Err(CustomError::OutOfRange)
}
})
});
parse!(date_mday() -> u8, {
unsigned_digits(2).map(|d| d as u8).and_then(|v| {
if (1..=31).contains(&v) {
Ok(v)
} else {
Err(CustomError::OutOfRange)
}
})
});
fn is_time_delim(c: u8) -> bool {
matches!(c, b'T' | b't' | b' ')
}
parse!(time_hour() -> u8, {
unsigned_digits(2).map(|d| d as u8).and_then(|v| {
if (0..=23).contains(&v) {
Ok(v)
} else {
Err(CustomError::OutOfRange)
}
})
});
parse!(time_minute() -> u8, {
unsigned_digits(2).map(|d| d as u8).and_then(|v| {
if (0..=59).contains(&v) {
Ok(v)
} else {
Err(CustomError::OutOfRange)
}
})
});
parse!(time_second() -> u8, {
unsigned_digits(2).map(|d| d as u8).and_then(|v| {
if (0..=60).contains(&v) {
Ok(v)
} else {
Err(CustomError::OutOfRange)
}
})
});
parse!(time_secfrac() -> u32, {
byte(b'.').and(take_while1(|c: u8| c.is_ascii_digit())).and_then::<_, _, CustomError>(|(_, repr): (u8, &[u8])| {
let repr = unsafe { from_utf8_unchecked(repr, "`is_ascii_digit` filters out on-ASCII") };
let v = repr.parse::<u32>().map_err(|_| CustomError::OutOfRange)?;
let consumed = repr.len();
static SCALE: [u32; 10] =
[0, 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1];
let scale = SCALE.get(consumed).ok_or(CustomError::OutOfRange)?;
let v = v.checked_mul(*scale).ok_or(CustomError::OutOfRange)?;
Ok(v)
})
});
parse!(signed_digits(count: usize) -> i32, {
recognize(skip_count_min_max(
*count, *count,
satisfy(|c: u8| c.is_ascii_digit()),
)).and_then(|b: &[u8]| {
let s = unsafe { from_utf8_unchecked(b, "`is_ascii_digit` filters out on-ASCII") };
s.parse::<i32>()
})
});
parse!(unsigned_digits(count: usize) -> u32, {
recognize(skip_count_min_max(
*count, *count,
satisfy(|c: u8| c.is_ascii_digit()),
)).and_then(|b: &[u8]| {
let s = unsafe { from_utf8_unchecked(b, "`is_ascii_digit` filters out on-ASCII") };
s.parse::<u32>()
})
});