cdfs 0.2.3

Rust implementation of ISO 9660 / ECMA-119
Documentation
// SPDX-License-Identifier: (MIT OR Apache-2.0)

use nom::branch::alt;
use nom::bytes::complete::{tag, take};
use nom::combinator::{map_res, value};
use nom::multi::count;
use nom::number::complete::le_u8;
use nom::sequence::tuple;
use std::convert::TryFrom;
use std::str;
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};

use crate::error::NomRes;

pub fn date_time(i: &[u8]) -> NomRes<&[u8], OffsetDateTime> {
    let (i, (year, month, day, hour, minute, second, gmt_offset)) =
        tuple((le_u8, le_u8, le_u8, le_u8, le_u8, le_u8, le_u8))(i)?;

    // Create Date and Time from parsed values. Since those values can be 0,
    // creating Date and Time struct can fail, in this case assume default
    // values.
    let date = Date::from_calendar_date(
        1900 + year as i32,
        time::Month::try_from(month).unwrap_or(time::Month::January),
        day,
    )
    .unwrap_or_else(|_| Date::from_calendar_date(0, time::Month::January, 1).unwrap());

    let time =
        Time::from_hms(hour, minute, second).unwrap_or_else(|_| Time::from_hms(0, 0, 0).unwrap());

    // gmt_offset represents 15 minutes intervals from GMT.
    let offset =
        UtcOffset::from_whole_seconds((gmt_offset as i32) * 15 * 60).unwrap_or(UtcOffset::UTC);

    Ok((i, PrimitiveDateTime::new(date, time).assume_offset(offset)))
}

fn ascii_i32(n: usize) -> impl Fn(&[u8]) -> NomRes<&[u8], i32> {
    move |i: &[u8]| {
        alt((
            map_res(map_res(take(n), str::from_utf8), str::parse::<i32>),
            value(0, count(tag(b"\0"), n)),
        ))(i)
    }
}

pub fn date_time_ascii(i: &[u8]) -> NomRes<&[u8], OffsetDateTime> {
    let (i, (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, centisecond, gmt_offset)) =
        tuple((
            ascii_i32(4),
            ascii_i32(2),
            ascii_i32(2),
            ascii_i32(2),
            ascii_i32(2),
            ascii_i32(2),
            ascii_i32(2),
            le_u8,
        ))(i)?;

    let date = Date::from_calendar_date(
        1900 + tm_year,
        time::Month::try_from(tm_mon as u8).unwrap_or(time::Month::January),
        tm_mday as u8,
    )
    .unwrap_or_else(|_| Date::from_calendar_date(0, time::Month::January, 1).unwrap());

    let time = Time::from_hms_milli(
        tm_hour as u8,
        tm_min as u8,
        tm_sec as u8,
        centisecond as u16 * 10,
    )
    .unwrap_or_else(|_| Time::from_hms(0, 0, 0).unwrap());

    let offset =
        UtcOffset::from_whole_seconds((gmt_offset as i32) * 15 * 60).unwrap_or(UtcOffset::UTC);

    Ok((i, PrimitiveDateTime::new(date, time).assume_offset(offset)))
}