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    /// Coerce
97    pub fn coerce_to_single_value(&self) -> Result<LiteralValue, ValueError> {
98        match self {
99            LiteralValue::Array(arr) => {
100                // Excel's implicit intersection or single LiteralValue coercion logic here
101                // Simplest: take top-left or return #LiteralValue! if not 1x1
102                if arr.len() == 1 && arr[0].len() == 1 {
103                    Ok(arr[0][0].clone())
104                } else if arr.is_empty() || arr[0].is_empty() {
105                    Ok(LiteralValue::Empty) // Or maybe error?
106                } else {
107                    Err(ValueError::ImplicitIntersection(
108                        "#LiteralValue! Implicit intersection failed".to_string(),
109                    ))
110                }
111            }
112            _ => Ok(self.clone()),
113        }
114    }
115
116    /// Convert this value to a serial number in the selected date system.
117    pub fn as_serial_number_for(&self, system: DateSystem) -> Option<f64> {
118        match self {
119            LiteralValue::Date(d) => Some(crate::date_to_serial_for(system, d)),
120            LiteralValue::DateTime(dt) => Some(crate::datetime_to_serial_for(system, dt)),
121            LiteralValue::Time(t) => Some(crate::time_to_fraction(t)),
122            LiteralValue::Duration(d) => Some(d.num_seconds() as f64 / 86_400.0),
123            LiteralValue::Int(i) => Some(*i as f64),
124            LiteralValue::Number(n) => Some(*n),
125            LiteralValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
126            _ => None,
127        }
128    }
129
130    /// Compatibility wrapper using the historical implicit Excel-1900 system.
131    pub fn as_serial_number(&self) -> Option<f64> {
132        self.as_serial_number_for(DateSystem::Excel1900)
133    }
134
135    /// Build the appropriate temporal value from an Excel serial number.
136    ///
137    /// This conversion uses representable `chrono` calendar values. In the
138    /// Excel-1900 system, phantom serial 60 therefore becomes 1900-02-28 and
139    /// re-encodes as serial 59. Use the display-parts API when the fictitious
140    /// 1900-02-29 must remain distinguishable.
141    pub fn try_from_serial_number_for(system: DateSystem, serial: f64) -> Result<Self, ExcelError> {
142        let dt = crate::try_serial_to_datetime_for(system, serial)?;
143        if dt.time() == NaiveTime::from_hms_opt(0, 0, 0).unwrap() {
144            Ok(LiteralValue::Date(dt.date()))
145        } else {
146            Ok(LiteralValue::DateTime(dt))
147        }
148    }
149
150    /// Checked convenience wrapper using the implicit Excel-1900 system.
151    pub fn try_from_serial_number(serial: f64) -> Result<Self, ExcelError> {
152        Self::try_from_serial_number_for(DateSystem::Excel1900, serial)
153    }
154
155    /// Compatibility wrapper using the historical implicit Excel-1900 system.
156    ///
157    /// Inputs outside Excel's calendar domain retain the legacy common
158    /// behavior. New code should use [`Self::try_from_serial_number`] or
159    /// [`Self::try_from_serial_number_for`] for checked conversion.
160    pub fn from_serial_number(serial: f64) -> Self {
161        let dt = crate::serial_to_datetime(serial);
162        if dt.time() == NaiveTime::from_hms_opt(0, 0, 0).unwrap() {
163            LiteralValue::Date(dt.date())
164        } else {
165            LiteralValue::DateTime(dt)
166        }
167    }
168
169    pub fn is_truthy(&self) -> bool {
170        match self {
171            LiteralValue::Boolean(b) => *b,
172            LiteralValue::Int(i) => *i != 0,
173            LiteralValue::Number(n) => *n != 0.0,
174            LiteralValue::Text(s) => !s.is_empty(),
175            LiteralValue::Array(arr) => !arr.is_empty(),
176            LiteralValue::Date(_) => true,
177            LiteralValue::DateTime(_) => true,
178            LiteralValue::Time(_) => true,
179            LiteralValue::Duration(_) => true,
180            LiteralValue::Error(_) => false,
181            LiteralValue::Empty => false,
182            LiteralValue::Pending => false,
183        }
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use chrono::NaiveDate;
191
192    #[test]
193    fn literal_temporal_values_use_the_selected_date_system() {
194        let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
195        let value = LiteralValue::Date(date);
196        assert_eq!(
197            value.as_serial_number_for(DateSystem::Excel1900),
198            Some(45_306.0)
199        );
200        assert_eq!(
201            value.as_serial_number_for(DateSystem::Excel1904),
202            Some(43_844.0)
203        );
204        assert_eq!(value.as_serial_number(), Some(45_306.0));
205    }
206
207    #[test]
208    fn literal_serial_construction_is_checked_and_system_aware() {
209        let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
210        for (system, serial) in [
211            (DateSystem::Excel1900, 45_306.0),
212            (DateSystem::Excel1904, 43_844.0),
213        ] {
214            assert_eq!(
215                LiteralValue::try_from_serial_number_for(system, serial).unwrap(),
216                LiteralValue::Date(date)
217            );
218        }
219        assert!(LiteralValue::try_from_serial_number_for(DateSystem::Excel1900, -1.0).is_err());
220        assert_eq!(
221            LiteralValue::from_serial_number(45_306.0),
222            LiteralValue::Date(date)
223        );
224        assert!(LiteralValue::try_from_serial_number(-1.0).is_err());
225        assert_eq!(
226            LiteralValue::from_serial_number(-1.0),
227            LiteralValue::Date(NaiveDate::from_ymd_opt(1899, 12, 30).unwrap())
228        );
229    }
230}