use alloc::vec::Vec;
use crate::de::Error;
use super::encode::write_uint;
use super::parser::Parser;
use super::types::{Atom, BigInt, Indicator};
pub(super) fn datetime_atom(content: &str, tagged: bool, offset: usize) -> Result<Atom, Error> {
let (seconds, fractional) = parse_datetime(content, offset)?;
let atom = if let Some(frac) = fractional {
Atom::Float(seconds as f64 + frac)
} else {
Atom::Integer(BigInt::from_i128(seconds))
};
if !tagged {
return Ok(atom);
}
let mut inner = Vec::new();
Parser::new("").emit_atom(&mut inner, atom, Indicator::None)?;
let mut out = Vec::new();
write_uint(&mut out, 6, 1, Indicator::None).map_err(|msg| Error::semantic(offset, msg))?;
out.extend_from_slice(&inner);
Ok(Atom::Raw(out))
}
fn parse_datetime(content: &str, offset: usize) -> Result<(i128, Option<f64>), Error> {
let b = content.as_bytes();
if b.len() < 20 {
return Err(Error::Syntax(offset));
}
let year = dec_fixed(b, 0, 4, offset)? as i32;
expect_byte(b, 4, b'-', offset)?;
let month = dec_fixed(b, 5, 2, offset)?;
expect_byte(b, 7, b'-', offset)?;
let day = dec_fixed(b, 8, 2, offset)?;
expect_byte_ci(b, 10, b'T', offset)?;
let hour = dec_fixed(b, 11, 2, offset)?;
expect_byte(b, 13, b':', offset)?;
let minute = dec_fixed(b, 14, 2, offset)?;
expect_byte(b, 16, b':', offset)?;
let second = dec_fixed(b, 17, 2, offset)?;
let mut pos = 19usize;
let fractional = if b.get(pos) == Some(&b'.') {
pos += 1;
let start = pos;
while b.get(pos).is_some_and(u8::is_ascii_digit) {
pos += 1;
}
if pos == start {
return Err(Error::Syntax(offset + pos));
}
let mut frac = 0.0;
let mut scale = 1.0;
for &digit in &b[start..pos] {
scale *= 10.0;
frac += f64::from(digit - b'0') / scale;
}
Some(frac)
} else {
None
};
let offset_seconds = match b.get(pos).copied() {
Some(b'Z' | b'z') => {
pos += 1;
0i32
}
Some(sign @ (b'+' | b'-')) => {
let off_hour = dec_fixed(b, pos + 1, 2, offset)? as i32;
expect_byte(b, pos + 3, b':', offset)?;
let off_min = dec_fixed(b, pos + 4, 2, offset)? as i32;
pos += 6;
if off_hour > 23 || off_min > 59 {
return Err(Error::Syntax(offset + pos));
}
let value = off_hour * 3600 + off_min * 60;
if sign == b'+' {
value
} else {
-value
}
}
_ => return Err(Error::Syntax(offset + pos)),
};
if pos != b.len() {
return Err(Error::Syntax(offset + pos));
}
if !(1..=12).contains(&month)
|| day == 0
|| day > days_in_month(year, month)
|| hour > 23
|| minute > 59
|| second > 60
{
return Err(Error::Syntax(offset));
}
let days = days_from_civil(year, month, day);
let seconds = i128::from(days) * 86_400
+ i128::from(hour) * 3600
+ i128::from(minute) * 60
+ i128::from(second)
- i128::from(offset_seconds);
if second == 60 && !is_positive_leap_second_epoch(seconds) {
return Err(Error::Syntax(offset));
}
Ok((seconds, fractional))
}
fn dec_fixed(bytes: &[u8], start: usize, len: usize, offset: usize) -> Result<u32, Error> {
let end = start + len;
if end > bytes.len() {
return Err(Error::Syntax(offset + start));
}
let mut value = 0u32;
for (idx, &b) in bytes[start..end].iter().enumerate() {
if !b.is_ascii_digit() {
return Err(Error::Syntax(offset + start + idx));
}
value = value * 10 + u32::from(b - b'0');
}
Ok(value)
}
fn expect_byte(bytes: &[u8], idx: usize, expected: u8, offset: usize) -> Result<(), Error> {
if bytes.get(idx) == Some(&expected) {
Ok(())
} else {
Err(Error::Syntax(offset + idx))
}
}
fn expect_byte_ci(bytes: &[u8], idx: usize, expected: u8, offset: usize) -> Result<(), Error> {
if bytes
.get(idx)
.is_some_and(|byte| byte.eq_ignore_ascii_case(&expected))
{
Ok(())
} else {
Err(Error::Syntax(offset + idx))
}
}
fn is_positive_leap_second_epoch(seconds: i128) -> bool {
const POSITIVE_LEAP_SECOND_DAYS: &[(i32, u32, u32)] = &[
(1972, 6, 30),
(1972, 12, 31),
(1973, 12, 31),
(1974, 12, 31),
(1975, 12, 31),
(1976, 12, 31),
(1977, 12, 31),
(1978, 12, 31),
(1979, 12, 31),
(1981, 6, 30),
(1982, 6, 30),
(1983, 6, 30),
(1985, 6, 30),
(1987, 12, 31),
(1989, 12, 31),
(1990, 12, 31),
(1992, 6, 30),
(1993, 6, 30),
(1994, 6, 30),
(1995, 12, 31),
(1997, 6, 30),
(1998, 12, 31),
(2005, 12, 31),
(2008, 12, 31),
(2012, 6, 30),
(2015, 6, 30),
(2016, 12, 31),
];
POSITIVE_LEAP_SECOND_DAYS.iter().any(|&(year, month, day)| {
i128::from(days_from_civil(year, month, day) + 1) * 86_400 == seconds
})
}
fn days_in_month(year: i32, month: u32) -> u32 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 if is_leap_year(year) => 29,
2 => 28,
_ => 0,
}
}
fn is_leap_year(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
fn days_from_civil(year: i32, month: u32, day: u32) -> i64 {
let year = year - i32::from(month <= 2);
let era = if year >= 0 { year } else { year - 399 } / 400;
let yoe = year - era * 400;
let month = month as i32;
let doy = (153 * (month + if month > 2 { -3 } else { 9 }) + 2) / 5 + day as i32 - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
i64::from(era * 146097 + doe - 719468)
}