openprxl 0.1.0

A Rust spreadsheet library inspired by Python's openpyxl
Documentation
//! Date conversion helpers.
//!
//! Excel stores dates as serial numbers using the 1900 date system. The epoch
//! is 1899-12-30 because of the historical 1900 leap-year bug, which is
//! reproduced here for compatibility with Excel.

use chrono::{NaiveDate, NaiveDateTime, NaiveTime, Timelike};

/// Excel epoch for the 1900 date system.
const EXCEL_EPOCH: NaiveDate = match NaiveDate::from_ymd_opt(1899, 12, 30) {
    Some(d) => d,
    None => panic!("invalid Excel epoch"),
};

/// Convert a [`NaiveDateTime`] to an Excel serial date value.
pub fn datetime_to_excel(dt: NaiveDateTime) -> f64 {
    let days = dt.date().signed_duration_since(EXCEL_EPOCH).num_days() as f64;
    let seconds = dt.time().num_seconds_from_midnight() as f64;
    days + seconds / 86_400.0
}

/// Convert a [`NaiveDate`] to an Excel serial date value.
pub fn date_to_excel(date: NaiveDate) -> f64 {
    date.signed_duration_since(EXCEL_EPOCH).num_days() as f64
}

/// Convert an Excel serial date value to a [`NaiveDateTime`].
pub fn excel_to_datetime(serial: f64) -> Option<NaiveDateTime> {
    let days = serial.trunc() as i64;
    let frac = serial - serial.trunc();
    let date = EXCEL_EPOCH.checked_add_signed(chrono::Duration::days(days))?;
    let seconds = (frac * 86_400.0).round() as u32;
    let time = NaiveTime::from_num_seconds_from_midnight_opt(seconds, 0)?;
    Some(NaiveDateTime::new(date, time))
}

/// Convert an Excel serial date value to a [`NaiveDate`].
pub fn excel_to_date(serial: f64) -> Option<NaiveDate> {
    let days = serial.trunc() as i64;
    EXCEL_EPOCH.checked_add_signed(chrono::Duration::days(days))
}

/// Convenience helper to create a [`NaiveDateTime`] from parts.
pub fn make_datetime(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> Option<NaiveDateTime> {
    let date = NaiveDate::from_ymd_opt(year, month, day)?;
    let time = NaiveTime::from_hms_opt(hour, min, sec)?;
    Some(NaiveDateTime::new(date, time))
}