1use chrono::{Duration as ChronoDur, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
2use std::{
3 fmt::{self, Display},
4 hash::{Hash, Hasher},
5};
6
7use crate::ExcelError;
8
9pub fn datetime_to_serial(dt: &NaiveDateTime) -> f64 {
18 let mut days = (dt.date() - EXCEL_EPOCH).num_days();
20 if days >= 60 {
21 days += 1;
22 }
23
24 let secs_in_day = dt.time().num_seconds_from_midnight() as f64;
25 days as f64 + secs_in_day / 86_400.0
26}
27
28pub fn serial_to_datetime(serial: f64) -> NaiveDateTime {
29 let days = serial.trunc() as i64;
31 let frac_secs = (serial.fract() * 86_400.0).round() as i64; let base_date = if days < 60 {
36 EXCEL_EPOCH
37 } else {
38 EXCEL_EPOCH + ChronoDur::days(1)
39 };
40
41 let date = base_date + ChronoDur::days(days);
42 let time =
43 NaiveTime::from_num_seconds_from_midnight_opt((frac_secs.rem_euclid(86_400)) as u32, 0)
44 .unwrap();
45 date.and_time(time)
46}
47
48const EXCEL_EPOCH: NaiveDate = NaiveDate::from_ymd_opt(1899, 12, 30).unwrap();
49
50#[derive(Debug, Clone, PartialEq)]
53pub enum LiteralValue {
54 Int(i64),
55 Number(f64),
56 Text(String),
57 Boolean(bool),
58 Array(Vec<Vec<LiteralValue>>), Date(chrono::NaiveDate), DateTime(chrono::NaiveDateTime), Time(chrono::NaiveTime), Duration(chrono::Duration), Empty, Pending, Error(ExcelError),
67}
68
69impl Hash for LiteralValue {
70 fn hash<H: Hasher>(&self, state: &mut H) {
71 match self {
72 LiteralValue::Int(i) => i.hash(state),
73 LiteralValue::Number(n) => n.to_bits().hash(state),
74 LiteralValue::Text(s) => s.hash(state),
75 LiteralValue::Boolean(b) => b.hash(state),
76 LiteralValue::Array(a) => a.hash(state),
77 LiteralValue::Date(d) => d.hash(state),
78 LiteralValue::DateTime(dt) => dt.hash(state),
79 LiteralValue::Time(t) => t.hash(state),
80 LiteralValue::Duration(d) => d.hash(state),
81 LiteralValue::Empty => state.write_u8(0),
82 LiteralValue::Pending => state.write_u8(1),
83 LiteralValue::Error(e) => e.hash(state),
84 }
85 }
86}
87
88impl Eq for LiteralValue {}
89
90impl Display for LiteralValue {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 LiteralValue::Int(i) => write!(f, "{i}"),
94 LiteralValue::Number(n) => write!(f, "{n}"),
95 LiteralValue::Text(s) => write!(f, "{s}"),
96 LiteralValue::Boolean(b) => write!(f, "{b}"),
97 LiteralValue::Error(e) => write!(f, "{e}"),
98 LiteralValue::Array(a) => write!(f, "{a:?}"),
99 LiteralValue::Date(d) => write!(f, "{d}"),
100 LiteralValue::DateTime(dt) => write!(f, "{dt}"),
101 LiteralValue::Time(t) => write!(f, "{t}"),
102 LiteralValue::Duration(d) => write!(f, "{d}"),
103 LiteralValue::Empty => write!(f, ""),
104 LiteralValue::Pending => write!(f, "Pending"),
105 }
106 }
107}
108
109#[derive(Debug, Clone, PartialEq)]
110pub enum ValueError {
111 ImplicitIntersection(String),
112}
113
114impl LiteralValue {
115 pub fn coerce_to_single_value(&self) -> Result<LiteralValue, ValueError> {
117 match self {
118 LiteralValue::Array(arr) => {
119 if arr.len() == 1 && arr[0].len() == 1 {
122 Ok(arr[0][0].clone())
123 } else if arr.is_empty() || arr[0].is_empty() {
124 Ok(LiteralValue::Empty) } else {
126 Err(ValueError::ImplicitIntersection(
127 "#LiteralValue! Implicit intersection failed".to_string(),
128 ))
129 }
130 }
131 _ => Ok(self.clone()),
132 }
133 }
134
135 pub fn as_serial_number(&self) -> Option<f64> {
136 match self {
137 LiteralValue::Date(d) => {
138 let dt = d.and_time(NaiveTime::from_hms_opt(0, 0, 0).unwrap());
139 Some(datetime_to_serial(&dt))
140 }
141 LiteralValue::DateTime(dt) => Some(datetime_to_serial(dt)),
142 LiteralValue::Time(t) => Some(t.num_seconds_from_midnight() as f64 / 86_400.0),
143 LiteralValue::Duration(d) => Some(d.num_seconds() as f64 / 86_400.0),
144 LiteralValue::Int(i) => Some(*i as f64),
145 LiteralValue::Number(n) => Some(*n),
146 LiteralValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
147 _ => None,
148 }
149 }
150
151 pub fn from_serial_number(serial: f64) -> Self {
154 let dt = serial_to_datetime(serial);
155 if dt.time() == NaiveTime::from_hms_opt(0, 0, 0).unwrap() {
156 LiteralValue::Date(dt.date())
157 } else {
158 LiteralValue::DateTime(dt)
159 }
160 }
161
162 pub fn is_truthy(&self) -> bool {
163 match self {
164 LiteralValue::Boolean(b) => *b,
165 LiteralValue::Int(i) => *i != 0,
166 LiteralValue::Number(n) => *n != 0.0,
167 LiteralValue::Text(s) => !s.is_empty(),
168 LiteralValue::Array(arr) => !arr.is_empty(),
169 LiteralValue::Date(_) => true,
170 LiteralValue::DateTime(_) => true,
171 LiteralValue::Time(_) => true,
172 LiteralValue::Duration(_) => true,
173 LiteralValue::Error(_) => false,
174 LiteralValue::Empty => false,
175 LiteralValue::Pending => false,
176 }
177 }
178}