calamine/
datatype.rs

1use std::fmt;
2
3#[cfg(feature = "dates")]
4use once_cell::sync::OnceCell;
5use serde::de::Visitor;
6use serde::{self, Deserialize};
7
8use super::CellErrorType;
9
10#[cfg(feature = "dates")]
11static EXCEL_EPOCH: OnceCell<chrono::NaiveDateTime> = OnceCell::new();
12
13#[cfg(feature = "dates")]
14const MS_MULTIPLIER: f64 = 24f64 * 60f64 * 60f64 * 1e+3f64;
15
16/// An enum to represent all different data types that can appear as
17/// a value in a worksheet cell
18#[derive(Debug, Clone, PartialEq, Default)]
19pub enum DataType {
20    /// Signed integer
21    Int(i64),
22    /// Float
23    Float(f64),
24    /// String
25    String(String),
26    /// Boolean
27    Bool(bool),
28    /// Date or Time
29    DateTime(f64),
30    /// Duration
31    Duration(f64),
32    /// Date, Time or DateTime in ISO 8601
33    DateTimeIso(String),
34    /// Duration in ISO 8601
35    DurationIso(String),
36    /// Error
37    Error(CellErrorType),
38    /// Empty cell
39    #[default]
40    Empty,
41}
42
43impl DataType {
44    /// Assess if datatype is empty
45    pub fn is_empty(&self) -> bool {
46        *self == DataType::Empty
47    }
48    /// Assess if datatype is a int
49    pub fn is_int(&self) -> bool {
50        matches!(*self, DataType::Int(_))
51    }
52    /// Assess if datatype is a float
53    pub fn is_float(&self) -> bool {
54        matches!(*self, DataType::Float(_))
55    }
56    /// Assess if datatype is a bool
57    pub fn is_bool(&self) -> bool {
58        matches!(*self, DataType::Bool(_))
59    }
60    /// Assess if datatype is a string
61    pub fn is_string(&self) -> bool {
62        matches!(*self, DataType::String(_))
63    }
64
65    /// Try getting int value
66    pub fn get_int(&self) -> Option<i64> {
67        if let DataType::Int(v) = self {
68            Some(*v)
69        } else {
70            None
71        }
72    }
73    /// Try getting float value
74    pub fn get_float(&self) -> Option<f64> {
75        if let DataType::Float(v) = self {
76            Some(*v)
77        } else {
78            None
79        }
80    }
81    /// Try getting bool value
82    pub fn get_bool(&self) -> Option<bool> {
83        if let DataType::Bool(v) = self {
84            Some(*v)
85        } else {
86            None
87        }
88    }
89    /// Try getting string value
90    pub fn get_string(&self) -> Option<&str> {
91        if let DataType::String(v) = self {
92            Some(&**v)
93        } else {
94            None
95        }
96    }
97
98    /// Try converting data type into a string
99    pub fn as_string(&self) -> Option<String> {
100        match self {
101            DataType::Float(v) => Some(v.to_string()),
102            DataType::Int(v) => Some(v.to_string()),
103            DataType::String(v) => Some(v.clone()),
104            _ => None,
105        }
106    }
107    /// Try converting data type into an int
108    pub fn as_i64(&self) -> Option<i64> {
109        match self {
110            DataType::Int(v) => Some(*v),
111            DataType::Float(v) => Some(*v as i64),
112            DataType::String(v) => v.parse::<i64>().ok(),
113            _ => None,
114        }
115    }
116    /// Try converting data type into a float
117    pub fn as_f64(&self) -> Option<f64> {
118        match self {
119            DataType::Int(v) => Some(*v as f64),
120            DataType::Float(v) => Some(*v),
121            DataType::String(v) => v.parse::<f64>().ok(),
122            _ => None,
123        }
124    }
125    /// Try converting data type into a date
126    #[cfg(feature = "dates")]
127    pub fn as_date(&self) -> Option<chrono::NaiveDate> {
128        use std::str::FromStr;
129        match self {
130            DataType::DateTimeIso(s) => self
131                .as_datetime()
132                .map(|dt| dt.date())
133                .or_else(|| chrono::NaiveDate::from_str(s).ok()),
134            _ => self.as_datetime().map(|dt| dt.date()),
135        }
136    }
137
138    /// Try converting data type into a time
139    #[cfg(feature = "dates")]
140    pub fn as_time(&self) -> Option<chrono::NaiveTime> {
141        use std::str::FromStr;
142        match self {
143            DataType::DateTimeIso(s) => self
144                .as_datetime()
145                .map(|dt| dt.time())
146                .or_else(|| chrono::NaiveTime::from_str(s).ok()),
147            DataType::DurationIso(s) => chrono::NaiveTime::parse_from_str(s, "PT%HH%MM%S%.fS").ok(),
148            _ => self.as_datetime().map(|dt| dt.time()),
149        }
150    }
151
152    /// Try converting data type into a duration
153    #[cfg(feature = "dates")]
154    pub fn as_duration(&self) -> Option<chrono::Duration> {
155        use chrono::Timelike;
156
157        match self {
158            DataType::Duration(days) => {
159                let ms = days * MS_MULTIPLIER;
160                Some(chrono::Duration::milliseconds(ms.round() as i64))
161            }
162            // need replace in the future to smth like chrono::Duration::from_str()
163            // https://github.com/chronotope/chrono/issues/579
164            DataType::DurationIso(_) => self.as_time().map(|t| {
165                chrono::Duration::nanoseconds(
166                    t.num_seconds_from_midnight() as i64 * 1_000_000_000 + t.nanosecond() as i64,
167                )
168            }),
169            _ => None,
170        }
171    }
172
173    /// Try converting data type into a datetime
174    #[cfg(feature = "dates")]
175    pub fn as_datetime(&self) -> Option<chrono::NaiveDateTime> {
176        use std::str::FromStr;
177
178        match self {
179            DataType::Int(x) => {
180                let days = x - 25569;
181                let secs = days * 86400;
182                chrono::NaiveDateTime::from_timestamp_opt(secs, 0)
183            }
184            DataType::Float(f) | DataType::DateTime(f) => {
185                let excel_epoch = EXCEL_EPOCH.get_or_init(|| {
186                    chrono::NaiveDate::from_ymd_opt(1899, 12, 30)
187                        .unwrap()
188                        .and_hms_opt(0, 0, 0)
189                        .unwrap()
190                });
191                let f = if *f >= 60.0 { *f } else { *f + 1.0 };
192                let ms = f * MS_MULTIPLIER;
193                let excel_duration = chrono::Duration::milliseconds(ms.round() as i64);
194                excel_epoch.checked_add_signed(excel_duration)
195            }
196            DataType::DateTimeIso(s) => chrono::NaiveDateTime::from_str(s).ok(),
197            _ => None,
198        }
199    }
200}
201
202impl PartialEq<&str> for DataType {
203    fn eq(&self, other: &&str) -> bool {
204        match *self {
205            DataType::String(ref s) if s == other => true,
206            _ => false,
207        }
208    }
209}
210
211impl PartialEq<str> for DataType {
212    fn eq(&self, other: &str) -> bool {
213        matches!(*self, DataType::String(ref s) if s == other)
214    }
215}
216
217impl PartialEq<f64> for DataType {
218    fn eq(&self, other: &f64) -> bool {
219        matches!(*self, DataType::Float(ref s) if *s == *other)
220    }
221}
222
223impl PartialEq<bool> for DataType {
224    fn eq(&self, other: &bool) -> bool {
225        matches!(*self, DataType::Bool(ref s) if *s == *other)
226    }
227}
228
229impl PartialEq<i64> for DataType {
230    fn eq(&self, other: &i64) -> bool {
231        matches!(*self, DataType::Int(ref s) if *s == *other)
232    }
233}
234
235impl fmt::Display for DataType {
236    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
237        match *self {
238            DataType::Int(ref e) => write!(f, "{}", e),
239            DataType::Float(ref e) => write!(f, "{}", e),
240            DataType::String(ref e) => write!(f, "{}", e),
241            DataType::Bool(ref e) => write!(f, "{}", e),
242            DataType::DateTime(ref e) => write!(f, "{}", e),
243            DataType::Duration(ref e) => write!(f, "{}", e),
244            DataType::DateTimeIso(ref e) => write!(f, "{}", e),
245            DataType::DurationIso(ref e) => write!(f, "{}", e),
246            DataType::Error(ref e) => write!(f, "{}", e),
247            DataType::Empty => Ok(()),
248        }
249    }
250}
251
252impl<'de> Deserialize<'de> for DataType {
253    #[inline]
254    fn deserialize<D>(deserializer: D) -> Result<DataType, D::Error>
255    where
256        D: serde::Deserializer<'de>,
257    {
258        struct DataTypeVisitor;
259
260        impl<'de> Visitor<'de> for DataTypeVisitor {
261            type Value = DataType;
262
263            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
264                formatter.write_str("any valid JSON value")
265            }
266
267            #[inline]
268            fn visit_bool<E>(self, value: bool) -> Result<DataType, E> {
269                Ok(DataType::Bool(value))
270            }
271
272            #[inline]
273            fn visit_i64<E>(self, value: i64) -> Result<DataType, E> {
274                Ok(DataType::Int(value))
275            }
276
277            #[inline]
278            fn visit_u64<E>(self, value: u64) -> Result<DataType, E> {
279                Ok(DataType::Int(value as i64))
280            }
281
282            #[inline]
283            fn visit_f64<E>(self, value: f64) -> Result<DataType, E> {
284                Ok(DataType::Float(value))
285            }
286
287            #[inline]
288            fn visit_str<E>(self, value: &str) -> Result<DataType, E>
289            where
290                E: serde::de::Error,
291            {
292                self.visit_string(String::from(value))
293            }
294
295            #[inline]
296            fn visit_string<E>(self, value: String) -> Result<DataType, E> {
297                Ok(DataType::String(value))
298            }
299
300            #[inline]
301            fn visit_none<E>(self) -> Result<DataType, E> {
302                Ok(DataType::Empty)
303            }
304
305            #[inline]
306            fn visit_some<D>(self, deserializer: D) -> Result<DataType, D::Error>
307            where
308                D: serde::Deserializer<'de>,
309            {
310                Deserialize::deserialize(deserializer)
311            }
312
313            #[inline]
314            fn visit_unit<E>(self) -> Result<DataType, E> {
315                Ok(DataType::Empty)
316            }
317        }
318
319        deserializer.deserialize_any(DataTypeVisitor)
320    }
321}
322
323macro_rules! define_from {
324    ($variant:path, $ty:ty) => {
325        impl From<$ty> for DataType {
326            fn from(v: $ty) -> Self {
327                $variant(v)
328            }
329        }
330    };
331}
332
333define_from!(DataType::Int, i64);
334define_from!(DataType::Float, f64);
335define_from!(DataType::String, String);
336define_from!(DataType::Bool, bool);
337define_from!(DataType::Error, CellErrorType);
338
339impl<'a> From<&'a str> for DataType {
340    fn from(v: &'a str) -> Self {
341        DataType::String(String::from(v))
342    }
343}
344
345impl From<()> for DataType {
346    fn from(_: ()) -> Self {
347        DataType::Empty
348    }
349}
350
351impl<T> From<Option<T>> for DataType
352where
353    DataType: From<T>,
354{
355    fn from(v: Option<T>) -> Self {
356        match v {
357            Some(v) => From::from(v),
358            None => DataType::Empty,
359        }
360    }
361}
362
363#[cfg(all(test, feature = "dates"))]
364mod date_tests {
365    use super::*;
366
367    #[test]
368    fn test_dates() {
369        use chrono::{Duration, NaiveDate, NaiveDateTime, NaiveTime};
370
371        let unix_epoch = DataType::Float(25569.);
372        assert_eq!(
373            unix_epoch.as_datetime(),
374            Some(NaiveDateTime::new(
375                NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(),
376                NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
377            ))
378        );
379
380        // test for https://github.com/tafia/calamine/issues/251
381        let unix_epoch_precision = DataType::Float(44484.7916666667);
382        assert_eq!(
383            unix_epoch_precision.as_datetime(),
384            Some(NaiveDateTime::new(
385                NaiveDate::from_ymd_opt(2021, 10, 15).unwrap(),
386                NaiveTime::from_hms_opt(19, 0, 0).unwrap(),
387            ))
388        );
389
390        // test rounding
391        assert_eq!(
392            DataType::Float(0.18737500000000001).as_time(),
393            Some(NaiveTime::from_hms_milli_opt(4, 29, 49, 200).unwrap())
394        );
395        assert_eq!(
396            DataType::Float(0.25951736111111101).as_time(),
397            Some(NaiveTime::from_hms_milli_opt(6, 13, 42, 300).unwrap())
398        );
399
400        // test overflow
401        assert_eq!(DataType::Float(1e20).as_time(), None);
402
403        let unix_epoch_15h30m = DataType::Float(25569.645833333333333);
404        let chrono_dt = NaiveDateTime::new(
405            NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(),
406            NaiveTime::from_hms_opt(15, 30, 0).unwrap(),
407        );
408        let micro = Duration::microseconds(1);
409        assert!(unix_epoch_15h30m.as_datetime().unwrap() - chrono_dt < micro);
410    }
411
412    #[test]
413    fn test_int_dates() {
414        use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
415
416        let unix_epoch = DataType::Int(25569);
417        assert_eq!(
418            unix_epoch.as_datetime(),
419            Some(NaiveDateTime::new(
420                NaiveDate::from_ymd_opt(1970, 1, 1).unwrap(),
421                NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
422            ))
423        );
424
425        let time = DataType::Int(44060);
426        assert_eq!(
427            time.as_datetime(),
428            Some(NaiveDateTime::new(
429                NaiveDate::from_ymd_opt(2020, 8, 17).unwrap(),
430                NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
431            ))
432        );
433    }
434}
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439
440    #[test]
441    fn test_partial_eq() {
442        assert_eq!(DataType::String("value".to_string()), "value");
443        assert_eq!(DataType::String("value".to_string()), "value"[..]);
444        assert_eq!(DataType::Float(100.0), 100.0f64);
445        assert_eq!(DataType::Bool(true), true);
446        assert_eq!(DataType::Int(100), 100i64);
447    }
448}