cbor2 1.1.2

Full-featured CBOR (RFC 8949) for serde: async item I/O, canonical encoding, no_std, Value/RawValue, simple values, tags, COSE keys, well-formedness checks and diagnostics.
Documentation
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)
}