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#[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>>), Date(chrono::NaiveDate), DateTime(chrono::NaiveDateTime), Time(chrono::NaiveTime), Duration(chrono::Duration), Empty, Pending, 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 pub fn coerce_to_single_value(&self) -> Result<LiteralValue, ValueError> {
98 match self {
99 LiteralValue::Array(arr) => {
100 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) } else {
107 Err(ValueError::ImplicitIntersection(
108 "#LiteralValue! Implicit intersection failed".to_string(),
109 ))
110 }
111 }
112 _ => Ok(self.clone()),
113 }
114 }
115
116 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 pub fn as_serial_number(&self) -> Option<f64> {
132 self.as_serial_number_for(DateSystem::Excel1900)
133 }
134
135 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 pub fn try_from_serial_number(serial: f64) -> Result<Self, ExcelError> {
152 Self::try_from_serial_number_for(DateSystem::Excel1900, serial)
153 }
154
155 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}