use deser_core::ext::{Date, Datetime, Offset, Time};
pub(crate) fn is_datetime_start(bytes: &[u8], pos: usize) -> bool {
let digits = |from: usize, n: usize| {
bytes
.get(from..from + n)
.is_some_and(|x| x.iter().all(u8::is_ascii_digit))
};
(digits(pos, 4) && bytes.get(pos + 4) == Some(&b'-'))
|| (digits(pos, 2) && bytes.get(pos + 2) == Some(&b':'))
}
pub(crate) fn parse_datetime(
bytes: &[u8],
start: usize,
) -> Result<(Datetime, usize), (usize, &'static str)> {
let mut pos = start;
let mut rv = Datetime {
date: None,
time: None,
offset: None,
};
if bytes.get(pos + 4) == Some(&b'-') {
let date = Date {
year: digits(bytes, &mut pos, 4)? as u16,
month: {
expect(bytes, &mut pos, b'-', "expected '-' in date")?;
digits(bytes, &mut pos, 2)? as u8
},
day: {
expect(bytes, &mut pos, b'-', "expected '-' in date")?;
digits(bytes, &mut pos, 2)? as u8
},
};
if !date.is_valid() {
return Err((start, "invalid date"));
}
rv.date = Some(date);
match bytes.get(pos) {
Some(b'T' | b't') => pos += 1,
Some(b' ') if is_datetime_start(bytes, pos + 1) => pos += 1,
_ => return Ok((rv, pos)),
}
}
let time_start = pos;
let mut time = Time {
hour: digits(bytes, &mut pos, 2)? as u8,
minute: {
expect(bytes, &mut pos, b':', "expected ':' in time")?;
digits(bytes, &mut pos, 2)? as u8
},
second: 0,
nanosecond: 0,
};
if bytes.get(pos) == Some(&b':') {
pos += 1;
time.second = digits(bytes, &mut pos, 2)? as u8;
if bytes.get(pos) == Some(&b'.') {
pos += 1;
let frac_start = pos;
let mut nanos = 0u32;
while let Some(&c) = bytes.get(pos).filter(|x| x.is_ascii_digit()) {
if pos - frac_start < 9 {
nanos = nanos * 10 + u32::from(c - b'0');
}
pos += 1;
}
if pos == frac_start {
return Err((pos, "expected digits after decimal point"));
}
for _ in (pos - frac_start)..9 {
nanos *= 10;
}
time.nanosecond = nanos;
}
}
if !time.is_valid() {
return Err((time_start, "invalid time"));
}
rv.time = Some(time);
if rv.date.is_some() {
match bytes.get(pos) {
Some(b'Z' | b'z') => {
pos += 1;
rv.offset = Some(Offset::Z);
}
Some(&sign @ (b'+' | b'-')) => {
let offset_start = pos;
pos += 1;
let hours = digits(bytes, &mut pos, 2)?;
expect(bytes, &mut pos, b':', "expected ':' in offset")?;
let minutes = digits(bytes, &mut pos, 2)?;
if hours > 23 || minutes > 59 {
return Err((offset_start, "invalid offset"));
}
let minutes = (hours * 60 + minutes) as i16;
rv.offset = Some(Offset::Custom {
minutes: if sign == b'-' { -minutes } else { minutes },
});
}
_ => {}
}
}
Ok((rv, pos))
}
fn digits(bytes: &[u8], pos: &mut usize, n: usize) -> Result<u32, (usize, &'static str)> {
let mut rv = 0;
for _ in 0..n {
match bytes.get(*pos) {
Some(&c) if c.is_ascii_digit() => rv = rv * 10 + u32::from(c - b'0'),
_ => return Err((*pos, "expected digit in date-time")),
}
*pos += 1;
}
Ok(rv)
}
fn expect(
bytes: &[u8],
pos: &mut usize,
c: u8,
msg: &'static str,
) -> Result<(), (usize, &'static str)> {
if bytes.get(*pos) == Some(&c) {
*pos += 1;
Ok(())
} else {
Err((*pos, msg))
}
}