use chrono::NaiveTime;
use std::{
fmt::{self, Display},
hash::{Hash, Hasher},
};
use crate::ExcelError;
pub use crate::date_serial::{datetime_to_serial, serial_to_datetime};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DateSystem {
Excel1900,
Excel1904,
}
impl Display for DateSystem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DateSystem::Excel1900 => write!(f, "1900"),
DateSystem::Excel1904 => write!(f, "1904"),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub enum LiteralValue {
Int(i64),
Number(f64),
Text(String),
Boolean(bool),
Array(Vec<Vec<LiteralValue>>), Date(chrono::NaiveDate), DateTime(chrono::NaiveDateTime), Time(chrono::NaiveTime), Duration(chrono::Duration), Empty, Pending,
Error(ExcelError),
}
impl Hash for LiteralValue {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
LiteralValue::Int(i) => i.hash(state),
LiteralValue::Number(n) => n.to_bits().hash(state),
LiteralValue::Text(s) => s.hash(state),
LiteralValue::Boolean(b) => b.hash(state),
LiteralValue::Array(a) => a.hash(state),
LiteralValue::Date(d) => d.hash(state),
LiteralValue::DateTime(dt) => dt.hash(state),
LiteralValue::Time(t) => t.hash(state),
LiteralValue::Duration(d) => d.hash(state),
LiteralValue::Empty => state.write_u8(0),
LiteralValue::Pending => state.write_u8(1),
LiteralValue::Error(e) => e.hash(state),
}
}
}
impl Eq for LiteralValue {}
impl Display for LiteralValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LiteralValue::Int(i) => write!(f, "{i}"),
LiteralValue::Number(n) => write!(f, "{n}"),
LiteralValue::Text(s) => write!(f, "{s}"),
LiteralValue::Boolean(b) => write!(f, "{b}"),
LiteralValue::Error(e) => write!(f, "{e}"),
LiteralValue::Array(a) => write!(f, "{a:?}"),
LiteralValue::Date(d) => write!(f, "{d}"),
LiteralValue::DateTime(dt) => write!(f, "{dt}"),
LiteralValue::Time(t) => write!(f, "{t}"),
LiteralValue::Duration(d) => write!(f, "{d}"),
LiteralValue::Empty => write!(f, ""),
LiteralValue::Pending => write!(f, "Pending"),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq)]
pub enum ValueError {
ImplicitIntersection(String),
}
impl LiteralValue {
pub fn coerce_to_single_value(&self) -> Result<LiteralValue, ValueError> {
match self {
LiteralValue::Array(arr) => {
if arr.len() == 1 && arr[0].len() == 1 {
Ok(arr[0][0].clone())
} else if arr.is_empty() || arr[0].is_empty() {
Ok(LiteralValue::Empty) } else {
Err(ValueError::ImplicitIntersection(
"#LiteralValue! Implicit intersection failed".to_string(),
))
}
}
_ => Ok(self.clone()),
}
}
pub fn as_serial_number_for(&self, system: DateSystem) -> Option<f64> {
match self {
LiteralValue::Date(d) => Some(crate::date_to_serial_for(system, d)),
LiteralValue::DateTime(dt) => Some(crate::datetime_to_serial_for(system, dt)),
LiteralValue::Time(t) => Some(crate::time_to_fraction(t)),
LiteralValue::Duration(d) => Some(d.num_seconds() as f64 / 86_400.0),
LiteralValue::Int(i) => Some(*i as f64),
LiteralValue::Number(n) => Some(*n),
LiteralValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
_ => None,
}
}
pub fn as_serial_number(&self) -> Option<f64> {
self.as_serial_number_for(DateSystem::Excel1900)
}
pub fn try_from_serial_number_for(system: DateSystem, serial: f64) -> Result<Self, ExcelError> {
let dt = crate::try_serial_to_datetime_for(system, serial)?;
if dt.time() == NaiveTime::from_hms_opt(0, 0, 0).unwrap() {
Ok(LiteralValue::Date(dt.date()))
} else {
Ok(LiteralValue::DateTime(dt))
}
}
pub fn try_from_serial_number(serial: f64) -> Result<Self, ExcelError> {
Self::try_from_serial_number_for(DateSystem::Excel1900, serial)
}
pub fn from_serial_number(serial: f64) -> Self {
let dt = crate::serial_to_datetime(serial);
if dt.time() == NaiveTime::from_hms_opt(0, 0, 0).unwrap() {
LiteralValue::Date(dt.date())
} else {
LiteralValue::DateTime(dt)
}
}
pub fn is_truthy(&self) -> bool {
match self {
LiteralValue::Boolean(b) => *b,
LiteralValue::Int(i) => *i != 0,
LiteralValue::Number(n) => *n != 0.0,
LiteralValue::Text(s) => !s.is_empty(),
LiteralValue::Array(arr) => !arr.is_empty(),
LiteralValue::Date(_) => true,
LiteralValue::DateTime(_) => true,
LiteralValue::Time(_) => true,
LiteralValue::Duration(_) => true,
LiteralValue::Error(_) => false,
LiteralValue::Empty => false,
LiteralValue::Pending => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDate;
#[test]
fn literal_temporal_values_use_the_selected_date_system() {
let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
let value = LiteralValue::Date(date);
assert_eq!(
value.as_serial_number_for(DateSystem::Excel1900),
Some(45_306.0)
);
assert_eq!(
value.as_serial_number_for(DateSystem::Excel1904),
Some(43_844.0)
);
assert_eq!(value.as_serial_number(), Some(45_306.0));
}
#[test]
fn literal_serial_construction_is_checked_and_system_aware() {
let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
for (system, serial) in [
(DateSystem::Excel1900, 45_306.0),
(DateSystem::Excel1904, 43_844.0),
] {
assert_eq!(
LiteralValue::try_from_serial_number_for(system, serial).unwrap(),
LiteralValue::Date(date)
);
}
assert!(LiteralValue::try_from_serial_number_for(DateSystem::Excel1900, -1.0).is_err());
assert_eq!(
LiteralValue::from_serial_number(45_306.0),
LiteralValue::Date(date)
);
assert!(LiteralValue::try_from_serial_number(-1.0).is_err());
assert_eq!(
LiteralValue::from_serial_number(-1.0),
LiteralValue::Date(NaiveDate::from_ymd_opt(1899, 12, 30).unwrap())
);
}
}