Skip to main content

formualizer_common/
value.rs

1use chrono::NaiveTime;
2use std::{
3    fmt::{self, Display},
4    hash::{Hash, Hasher},
5};
6
7use crate::ExcelError;
8
9pub use crate::date_serial::{datetime_to_serial, serial_to_datetime};
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum DateSystem {
16    Excel1900,
17    Excel1904,
18}
19
20impl Display for DateSystem {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            DateSystem::Excel1900 => write!(f, "1900"),
24            DateSystem::Excel1904 => write!(f, "1904"),
25        }
26    }
27}
28
29/// An **interpeter** LiteralValue. This is distinct
30/// from the possible types that can be stored in a cell.
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32#[derive(Debug, Clone, PartialEq)]
33pub enum LiteralValue {
34    Int(i64),
35    Number(f64),
36    Text(String),
37    Boolean(bool),
38    Array(Vec<Vec<LiteralValue>>),   // For array results
39    Date(chrono::NaiveDate),         // For date values
40    DateTime(chrono::NaiveDateTime), // For date/time values
41    Time(chrono::NaiveTime),         // For time values
42    Duration(chrono::Duration),      // For durations
43    Empty,                           // For empty cells/optional arguments
44    Pending,                         // For pending values
45
46    Error(ExcelError),
47}
48
49impl Hash for LiteralValue {
50    fn hash<H: Hasher>(&self, state: &mut H) {
51        match self {
52            LiteralValue::Int(i) => i.hash(state),
53            LiteralValue::Number(n) => n.to_bits().hash(state),
54            LiteralValue::Text(s) => s.hash(state),
55            LiteralValue::Boolean(b) => b.hash(state),
56            LiteralValue::Array(a) => a.hash(state),
57            LiteralValue::Date(d) => d.hash(state),
58            LiteralValue::DateTime(dt) => dt.hash(state),
59            LiteralValue::Time(t) => t.hash(state),
60            LiteralValue::Duration(d) => d.hash(state),
61            LiteralValue::Empty => state.write_u8(0),
62            LiteralValue::Pending => state.write_u8(1),
63            LiteralValue::Error(e) => e.hash(state),
64        }
65    }
66}
67
68impl Eq for LiteralValue {}
69
70impl Display for LiteralValue {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match self {
73            LiteralValue::Int(i) => write!(f, "{i}"),
74            LiteralValue::Number(n) => write!(f, "{n}"),
75            LiteralValue::Text(s) => write!(f, "{s}"),
76            LiteralValue::Boolean(b) => write!(f, "{b}"),
77            LiteralValue::Error(e) => write!(f, "{e}"),
78            LiteralValue::Array(a) => write!(f, "{a:?}"),
79            LiteralValue::Date(d) => write!(f, "{d}"),
80            LiteralValue::DateTime(dt) => write!(f, "{dt}"),
81            LiteralValue::Time(t) => write!(f, "{t}"),
82            LiteralValue::Duration(d) => write!(f, "{d}"),
83            LiteralValue::Empty => write!(f, ""),
84            LiteralValue::Pending => write!(f, "Pending"),
85        }
86    }
87}
88
89#[non_exhaustive]
90#[derive(Debug, Clone, PartialEq)]
91pub enum ValueError {
92    ImplicitIntersection(String),
93}
94
95impl LiteralValue {
96    /// The value a cell can store: a NaN or infinite `Number` becomes `#NUM!`
97    /// (no spreadsheet value is non-finite), anything else is unchanged. Every
98    /// write and load path uses this, so the check costs one `is_finite` per
99    /// number.
100    #[inline]
101    pub fn finite_or_num_error(self) -> Self {
102        match self {
103            LiteralValue::Number(n) if !n.is_finite() => Self::non_finite_number(),
104            other => other,
105        }
106    }
107
108    #[cold]
109    fn non_finite_number() -> Self {
110        LiteralValue::Error(ExcelError::new(crate::error::ExcelErrorKind::Num))
111    }
112
113    /// Coerce
114    pub fn coerce_to_single_value(&self) -> Result<LiteralValue, ValueError> {
115        match self {
116            LiteralValue::Array(arr) => {
117                // Excel's implicit intersection or single LiteralValue coercion logic here
118                // Simplest: take top-left or return #LiteralValue! if not 1x1
119                if arr.len() == 1 && arr[0].len() == 1 {
120                    Ok(arr[0][0].clone())
121                } else if arr.is_empty() || arr[0].is_empty() {
122                    Ok(LiteralValue::Empty) // Or maybe error?
123                } else {
124                    Err(ValueError::ImplicitIntersection(
125                        "#LiteralValue! Implicit intersection failed".to_string(),
126                    ))
127                }
128            }
129            _ => Ok(self.clone()),
130        }
131    }
132
133    /// Convert this value to a serial number in the selected date system.
134    pub fn as_serial_number_for(&self, system: DateSystem) -> Option<f64> {
135        match self {
136            LiteralValue::Date(d) => Some(crate::date_to_serial_for(system, d)),
137            LiteralValue::DateTime(dt) => Some(crate::datetime_to_serial_for(system, dt)),
138            LiteralValue::Time(t) => Some(crate::time_to_fraction(t)),
139            LiteralValue::Duration(d) => Some(d.num_seconds() as f64 / 86_400.0),
140            LiteralValue::Int(i) => Some(*i as f64),
141            LiteralValue::Number(n) => Some(*n),
142            LiteralValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
143            _ => None,
144        }
145    }
146
147    /// Compatibility wrapper using the historical implicit Excel-1900 system.
148    pub fn as_serial_number(&self) -> Option<f64> {
149        self.as_serial_number_for(DateSystem::Excel1900)
150    }
151
152    /// Build the appropriate temporal value from an Excel serial number.
153    ///
154    /// This conversion uses representable `chrono` calendar values. In the
155    /// Excel-1900 system, phantom serial 60 therefore becomes 1900-02-28 and
156    /// re-encodes as serial 59. Use the display-parts API when the fictitious
157    /// 1900-02-29 must remain distinguishable.
158    pub fn try_from_serial_number_for(system: DateSystem, serial: f64) -> Result<Self, ExcelError> {
159        let dt = crate::try_serial_to_datetime_for(system, serial)?;
160        if dt.time() == NaiveTime::from_hms_opt(0, 0, 0).unwrap() {
161            Ok(LiteralValue::Date(dt.date()))
162        } else {
163            Ok(LiteralValue::DateTime(dt))
164        }
165    }
166
167    /// Checked convenience wrapper using the implicit Excel-1900 system.
168    pub fn try_from_serial_number(serial: f64) -> Result<Self, ExcelError> {
169        Self::try_from_serial_number_for(DateSystem::Excel1900, serial)
170    }
171
172    /// Compatibility wrapper using the historical implicit Excel-1900 system.
173    ///
174    /// Inputs outside Excel's calendar domain retain the legacy common
175    /// behavior. New code should use [`Self::try_from_serial_number`] or
176    /// [`Self::try_from_serial_number_for`] for checked conversion.
177    pub fn from_serial_number(serial: f64) -> Self {
178        let dt = crate::serial_to_datetime(serial);
179        if dt.time() == NaiveTime::from_hms_opt(0, 0, 0).unwrap() {
180            LiteralValue::Date(dt.date())
181        } else {
182            LiteralValue::DateTime(dt)
183        }
184    }
185
186    pub fn is_truthy(&self) -> bool {
187        match self {
188            LiteralValue::Boolean(b) => *b,
189            LiteralValue::Int(i) => *i != 0,
190            LiteralValue::Number(n) => *n != 0.0,
191            LiteralValue::Text(s) => !s.is_empty(),
192            LiteralValue::Array(arr) => !arr.is_empty(),
193            LiteralValue::Date(_) => true,
194            LiteralValue::DateTime(_) => true,
195            LiteralValue::Time(_) => true,
196            LiteralValue::Duration(_) => true,
197            LiteralValue::Error(_) => false,
198            LiteralValue::Empty => false,
199            LiteralValue::Pending => false,
200        }
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use chrono::NaiveDate;
208
209    #[test]
210    fn literal_temporal_values_use_the_selected_date_system() {
211        let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
212        let value = LiteralValue::Date(date);
213        assert_eq!(
214            value.as_serial_number_for(DateSystem::Excel1900),
215            Some(45_306.0)
216        );
217        assert_eq!(
218            value.as_serial_number_for(DateSystem::Excel1904),
219            Some(43_844.0)
220        );
221        assert_eq!(value.as_serial_number(), Some(45_306.0));
222    }
223
224    #[test]
225    fn literal_serial_construction_is_checked_and_system_aware() {
226        let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
227        for (system, serial) in [
228            (DateSystem::Excel1900, 45_306.0),
229            (DateSystem::Excel1904, 43_844.0),
230        ] {
231            assert_eq!(
232                LiteralValue::try_from_serial_number_for(system, serial).unwrap(),
233                LiteralValue::Date(date)
234            );
235        }
236        assert!(LiteralValue::try_from_serial_number_for(DateSystem::Excel1900, -1.0).is_err());
237        assert_eq!(
238            LiteralValue::from_serial_number(45_306.0),
239            LiteralValue::Date(date)
240        );
241        assert!(LiteralValue::try_from_serial_number(-1.0).is_err());
242        assert_eq!(
243            LiteralValue::from_serial_number(-1.0),
244            LiteralValue::Date(NaiveDate::from_ymd_opt(1899, 12, 30).unwrap())
245        );
246    }
247}