diff --git a/examples/excel_to_csv.rs b/examples/excel_to_csv.rs index 2c595ce3..8e6d6e85 100644 --- a/examples/excel_to_csv.rs +++ b/examples/excel_to_csv.rs @@ -38,9 +38,8 @@ fn write_range(dest: &mut W, range: &Range) -> std::io::Result<( Data::String(ref s) | Data::DateTimeIso(ref s) | Data::DurationIso(ref s) => { write!(dest, "{}", s) } - Data::Float(ref f) | Data::DateTime(ref f) | Data::Duration(ref f) => { - write!(dest, "{}", f) - } + Data::Float(ref f) => write!(dest, "{}", f), + Data::DateTime(ref d) => write!(dest, "{}", d.as_f64()), Data::Int(ref i) => write!(dest, "{}", i), Data::Error(ref e) => write!(dest, "{:?}", e), Data::Bool(ref b) => write!(dest, "{}", b), diff --git a/src/datatype.rs b/src/datatype.rs index 6d268f24..e7ac200c 100644 --- a/src/datatype.rs +++ b/src/datatype.rs @@ -10,6 +10,10 @@ use super::CellErrorType; #[cfg(feature = "dates")] static EXCEL_EPOCH: OnceCell = OnceCell::new(); +#[cfg(feature = "dates")] +/// https://learn.microsoft.com/en-us/office/troubleshoot/excel/1900-and-1904-date-system +const EXCEL_1900_1904_DIFF: f64 = 1462.; + #[cfg(feature = "dates")] const MS_MULTIPLIER: f64 = 24f64 * 60f64 * 60f64 * 1e+3f64; @@ -26,9 +30,7 @@ pub enum Data { /// Boolean Bool(bool), /// Date or Time - DateTime(f64), - /// Duration - Duration(f64), + DateTime(ExcelDateTime), /// Date, Time or DateTime in ISO 8601 DateTimeIso(String), /// Duration in ISO 8601 @@ -59,11 +61,6 @@ impl DataType for Data { matches!(*self, Data::String(_)) } - #[cfg(feature = "dates")] - fn is_duration(&self) -> bool { - matches!(*self, Data::Duration(_)) - } - #[cfg(feature = "dates")] fn is_duration_iso(&self) -> bool { matches!(*self, Data::DurationIso(_)) @@ -108,100 +105,53 @@ impl DataType for Data { } } - fn as_string(&self) -> Option { + #[cfg(feature = "dates")] + fn get_datetime(&self) -> Option { match self { - Data::Float(v) => Some(v.to_string()), - Data::Int(v) => Some(v.to_string()), - Data::String(v) => Some(v.clone()), + Data::DateTime(v) => Some(*v), _ => None, } } - fn as_i64(&self) -> Option { + #[cfg(feature = "dates")] + fn get_datetime_iso(&self) -> Option<&str> { match self { - Data::Int(v) => Some(*v), - Data::Float(v) => Some(*v as i64), - Data::String(v) => v.parse::().ok(), + Data::DateTimeIso(v) => Some(&**v), _ => None, } } - fn as_f64(&self) -> Option { - match self { - Data::Int(v) => Some(*v as f64), - Data::Float(v) => Some(*v), - Data::String(v) => v.parse::().ok(), - _ => None, - } - } #[cfg(feature = "dates")] - fn as_date(&self) -> Option { - use std::str::FromStr; + fn get_duration_iso(&self) -> Option<&str> { match self { - Data::DateTimeIso(s) => self - .as_datetime() - .map(|dt| dt.date()) - .or_else(|| chrono::NaiveDate::from_str(s).ok()), - _ => self.as_datetime().map(|dt| dt.date()), + Data::DurationIso(v) => Some(&**v), + _ => None, } } - #[cfg(feature = "dates")] - fn as_time(&self) -> Option { - use std::str::FromStr; + fn as_string(&self) -> Option { match self { - Data::DateTimeIso(s) => self - .as_datetime() - .map(|dt| dt.time()) - .or_else(|| chrono::NaiveTime::from_str(s).ok()), - Data::DurationIso(s) => chrono::NaiveTime::parse_from_str(s, "PT%HH%MM%S%.fS").ok(), - _ => self.as_datetime().map(|dt| dt.time()), + Data::Float(v) => Some(v.to_string()), + Data::Int(v) => Some(v.to_string()), + Data::String(v) => Some(v.clone()), + _ => None, } } - #[cfg(feature = "dates")] - fn as_duration(&self) -> Option { - use chrono::Timelike; - + fn as_i64(&self) -> Option { match self { - Data::Duration(days) => { - let ms = days * MS_MULTIPLIER; - Some(chrono::Duration::milliseconds(ms.round() as i64)) - } - // need replace in the future to smth like chrono::Duration::from_str() - // https://github.com/chronotope/chrono/issues/579 - Data::DurationIso(_) => self.as_time().map(|t| { - chrono::Duration::nanoseconds( - t.num_seconds_from_midnight() as i64 * 1_000_000_000 + t.nanosecond() as i64, - ) - }), + Data::Int(v) => Some(*v), + Data::Float(v) => Some(*v as i64), + Data::String(v) => v.parse::().ok(), _ => None, } } - #[cfg(feature = "dates")] - fn as_datetime(&self) -> Option { - use std::str::FromStr; - + fn as_f64(&self) -> Option { match self { - Data::Int(x) => { - let days = x - 25569; - let secs = days * 86400; - chrono::NaiveDateTime::from_timestamp_opt(secs, 0) - } - Data::Float(f) | Data::DateTime(f) => { - let excel_epoch = EXCEL_EPOCH.get_or_init(|| { - chrono::NaiveDate::from_ymd_opt(1899, 12, 30) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap() - }); - let f = if *f >= 60.0 { *f } else { *f + 1.0 }; - let ms = f * MS_MULTIPLIER; - let excel_duration = chrono::Duration::milliseconds(ms.round() as i64); - excel_epoch.checked_add_signed(excel_duration) - } - Data::DateTimeIso(s) => chrono::NaiveDateTime::from_str(s).ok(), + Data::Int(v) => Some(*v as f64), + Data::Float(v) => Some(*v), + Data::String(v) => v.parse::().ok(), _ => None, } } @@ -248,7 +198,6 @@ impl fmt::Display for Data { Data::String(ref e) => write!(f, "{}", e), Data::Bool(ref e) => write!(f, "{}", e), Data::DateTime(ref e) => write!(f, "{}", e), - Data::Duration(ref e) => write!(f, "{}", e), Data::DateTimeIso(ref e) => write!(f, "{}", e), Data::DurationIso(ref e) => write!(f, "{}", e), Data::Error(ref e) => write!(f, "{}", e), @@ -383,9 +332,7 @@ pub enum DataRef<'a> { /// Boolean Bool(bool), /// Date or Time - DateTime(f64), - /// Duration - Duration(f64), + DateTime(ExcelDateTime), /// Date, Time or DateTime in ISO 8601 DateTimeIso(String), /// Duration in ISO 8601 @@ -418,11 +365,6 @@ impl DataType for DataRef<'_> { matches!(*self, DataRef::String(_) | DataRef::SharedString(_)) } - #[cfg(feature = "dates")] - fn is_duration(&self) -> bool { - matches!(*self, DataRef::Duration(_)) - } - #[cfg(feature = "dates")] fn is_duration_iso(&self) -> bool { matches!(*self, DataRef::DurationIso(_)) @@ -470,6 +412,30 @@ impl DataType for DataRef<'_> { } } + #[cfg(feature = "dates")] + fn get_datetime(&self) -> Option { + match self { + DataRef::DateTime(v) => Some(*v), + _ => None, + } + } + + #[cfg(feature = "dates")] + fn get_datetime_iso(&self) -> Option<&str> { + match self { + DataRef::DateTimeIso(v) => Some(&**v), + _ => None, + } + } + + #[cfg(feature = "dates")] + fn get_duration_iso(&self) -> Option<&str> { + match self { + DataRef::DurationIso(v) => Some(&**v), + _ => None, + } + } + fn as_string(&self) -> Option { match self { DataRef::Float(v) => Some(v.to_string()), @@ -499,78 +465,6 @@ impl DataType for DataRef<'_> { _ => None, } } - - #[cfg(feature = "dates")] - fn as_date(&self) -> Option { - use std::str::FromStr; - match self { - DataRef::DateTimeIso(s) => self - .as_datetime() - .map(|dt| dt.date()) - .or_else(|| chrono::NaiveDate::from_str(s).ok()), - _ => self.as_datetime().map(|dt| dt.date()), - } - } - - #[cfg(feature = "dates")] - fn as_time(&self) -> Option { - use std::str::FromStr; - match self { - DataRef::DateTimeIso(s) => self - .as_datetime() - .map(|dt| dt.time()) - .or_else(|| chrono::NaiveTime::from_str(s).ok()), - DataRef::DurationIso(s) => chrono::NaiveTime::parse_from_str(s, "PT%HH%MM%S%.fS").ok(), - _ => self.as_datetime().map(|dt| dt.time()), - } - } - - #[cfg(feature = "dates")] - fn as_duration(&self) -> Option { - use chrono::Timelike; - - match self { - DataRef::Duration(days) => { - let ms = days * MS_MULTIPLIER; - Some(chrono::Duration::milliseconds(ms.round() as i64)) - } - // need replace in the future to smth like chrono::Duration::from_str() - // https://github.com/chronotope/chrono/issues/579 - DataRef::DurationIso(_) => self.as_time().map(|t| { - chrono::Duration::nanoseconds( - t.num_seconds_from_midnight() as i64 * 1_000_000_000 + t.nanosecond() as i64, - ) - }), - _ => None, - } - } - - #[cfg(feature = "dates")] - fn as_datetime(&self) -> Option { - use std::str::FromStr; - - match self { - DataRef::Int(x) => { - let days = x - 25569; - let secs = days * 86400; - chrono::NaiveDateTime::from_timestamp_opt(secs, 0) - } - DataRef::Float(f) | DataRef::DateTime(f) => { - let excel_epoch = EXCEL_EPOCH.get_or_init(|| { - chrono::NaiveDate::from_ymd_opt(1899, 12, 30) - .unwrap() - .and_hms_opt(0, 0, 0) - .unwrap() - }); - let f = if *f >= 60.0 { *f } else { *f + 1.0 }; - let ms = f * MS_MULTIPLIER; - let excel_duration = chrono::Duration::milliseconds(ms.round() as i64); - excel_epoch.checked_add_signed(excel_duration) - } - DataRef::DateTimeIso(s) => chrono::NaiveDateTime::from_str(s).ok(), - _ => None, - } - } } /// A trait to represent all different data types that can appear as @@ -591,10 +485,6 @@ pub trait DataType { /// Assess if datatype is a string fn is_string(&self) -> bool; - /// Assess if datatype is a duration - #[cfg(feature = "dates")] - fn is_duration(&self) -> bool; - /// Assess if datatype is an ISO8601 duration #[cfg(feature = "dates")] fn is_duration_iso(&self) -> bool; @@ -619,6 +509,18 @@ pub trait DataType { /// Try getting string value fn get_string(&self) -> Option<&str>; + /// Try getting datetime value + #[cfg(feature = "dates")] + fn get_datetime(&self) -> Option; + + /// Try getting datetime ISO8601 value + #[cfg(feature = "dates")] + fn get_datetime_iso(&self) -> Option<&str>; + + /// Try getting duration ISO8601 value + #[cfg(feature = "dates")] + fn get_duration_iso(&self) -> Option<&str>; + /// Try converting data type into a string fn as_string(&self) -> Option; @@ -630,19 +532,76 @@ pub trait DataType { /// Try converting data type into a date #[cfg(feature = "dates")] - fn as_date(&self) -> Option; + fn as_date(&self) -> Option { + use std::str::FromStr; + if self.is_datetime_iso() { + self.as_datetime().map(|dt| dt.date()).or_else(|| { + self.get_datetime_iso() + .map(|s| chrono::NaiveDate::from_str(&s).ok()) + .flatten() + }) + } else { + self.as_datetime().map(|dt| dt.date()) + } + } /// Try converting data type into a time #[cfg(feature = "dates")] - fn as_time(&self) -> Option; + fn as_time(&self) -> Option { + use std::str::FromStr; + if self.is_datetime_iso() { + self.as_datetime().map(|dt| dt.time()).or_else(|| { + self.get_datetime_iso() + .map(|s| chrono::NaiveTime::from_str(&s).ok()) + .flatten() + }) + } else if self.is_duration_iso() { + self.get_duration_iso() + .map(|s| chrono::NaiveTime::parse_from_str(&s, "PT%HH%MM%S%.fS").ok()) + .flatten() + } else { + self.as_datetime().map(|dt| dt.time()) + } + } /// Try converting data type into a duration #[cfg(feature = "dates")] - fn as_duration(&self) -> Option; + fn as_duration(&self) -> Option { + use chrono::Timelike; + + if self.is_datetime() { + self.get_datetime().map(|dt| dt.as_duration()).flatten() + } else if self.is_duration_iso() { + // need replace in the future to smth like chrono::Duration::from_str() + // https://github.com/chronotope/chrono/issues/579 + self.as_time().map(|t| { + chrono::Duration::nanoseconds( + t.num_seconds_from_midnight() as i64 * 1_000_000_000 + t.nanosecond() as i64, + ) + }) + } else { + None + } + } /// Try converting data type into a datetime #[cfg(feature = "dates")] - fn as_datetime(&self) -> Option; + fn as_datetime(&self) -> Option { + use std::str::FromStr; + + if self.is_int() || self.is_float() { + self.as_f64() + .map(|f| ExcelDateTime::from_value_only(f).as_datetime()) + } else if self.is_datetime() { + self.get_datetime().map(|d| d.as_datetime()) + } else if self.is_datetime_iso() { + self.get_datetime_iso() + .map(|s| chrono::NaiveDateTime::from_str(&s).ok()) + } else { + None + } + .flatten() + } } impl<'a> From> for Data { @@ -654,7 +613,6 @@ impl<'a> From> for Data { DataRef::SharedString(v) => Data::String(v.into()), DataRef::Bool(v) => Data::Bool(v), DataRef::DateTime(v) => Data::DateTime(v), - DataRef::Duration(v) => Data::Duration(v), DataRef::DateTimeIso(v) => Data::DateTimeIso(v), DataRef::DurationIso(v) => Data::DurationIso(v), DataRef::Error(v) => Data::Error(v), @@ -663,6 +621,103 @@ impl<'a> From> for Data { } } +/// Excel datetime type. Possible: date, time, datetime, duration. +/// At this time we can only determine datetime (date and time are datetime too) and duration. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum ExcelDateTimeType { + /// DateTime + DateTime, + /// TimeDelta (Duration) + TimeDelta, +} + +/// Structure for Excel date and time representation. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ExcelDateTime { + value: f64, + datetime_type: ExcelDateTimeType, + is_1904: bool, +} + +impl ExcelDateTime { + /// Creates a new `ExcelDateTime` + pub fn new(value: f64, datetime_type: ExcelDateTimeType, is_1904: bool) -> Self { + ExcelDateTime { + value, + datetime_type, + is_1904, + } + } + + /// Is used only for converting excel value to chrono + #[cfg(feature = "dates")] + fn from_value_only(value: f64) -> Self { + ExcelDateTime { + value, + ..Default::default() + } + } + + /// True if excel datetime has duration format ([hh]:mm:ss, for example) + #[cfg(feature = "dates")] + pub fn is_duration(&self) -> bool { + matches!(self.datetime_type, ExcelDateTimeType::TimeDelta) + } + + /// True if excel datetime has datetime format (not duration) + #[cfg(feature = "dates")] + pub fn is_datetime(&self) -> bool { + matches!(self.datetime_type, ExcelDateTimeType::DateTime) + } + + /// Converting data type into a float + pub fn as_f64(&self) -> f64 { + self.value + } + + /// Try converting data type into a duration + #[cfg(feature = "dates")] + pub fn as_duration(&self) -> Option { + let ms = self.value * MS_MULTIPLIER; + Some(chrono::Duration::milliseconds(ms.round() as i64)) + } + + /// Try converting data type into a datetime + #[cfg(feature = "dates")] + pub fn as_datetime(&self) -> Option { + let excel_epoch = EXCEL_EPOCH.get_or_init(|| { + chrono::NaiveDate::from_ymd_opt(1899, 12, 30) + .unwrap() + .and_time(chrono::NaiveTime::MIN) + }); + let f = if self.is_1904 { + self.value + EXCEL_1900_1904_DIFF + } else { + self.value + }; + let f = if f >= 60.0 { f } else { f + 1.0 }; + let ms = f * MS_MULTIPLIER; + let excel_duration = chrono::Duration::milliseconds(ms.round() as i64); + excel_epoch.checked_add_signed(excel_duration) + } +} + +impl Default for ExcelDateTime { + fn default() -> Self { + ExcelDateTime { + value: 0., + datetime_type: ExcelDateTimeType::DateTime, + is_1904: false, + } + } +} + +impl fmt::Display for ExcelDateTime { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> { + write!(f, "{}", self.value) + } +} + #[cfg(all(test, feature = "dates"))] mod date_tests { use super::*; diff --git a/src/de.rs b/src/de.rs index de4df9dc..34c0c94d 100644 --- a/src/de.rs +++ b/src/de.rs @@ -560,8 +560,7 @@ impl<'a, 'de> serde::Deserializer<'de> for DataDeserializer<'a> { Data::Bool(v) => visitor.visit_bool(*v), Data::Int(v) => visitor.visit_i64(*v), Data::Empty => visitor.visit_unit(), - Data::DateTime(v) => visitor.visit_f64(*v), - Data::Duration(v) => visitor.visit_f64(*v), + Data::DateTime(v) => visitor.visit_f64(v.as_f64()), Data::DateTimeIso(v) => visitor.visit_str(v), Data::DurationIso(v) => visitor.visit_str(v), Data::Error(ref err) => Err(DeError::CellError { @@ -582,7 +581,6 @@ impl<'a, 'de> serde::Deserializer<'de> for DataDeserializer<'a> { Data::Int(v) => visitor.visit_str(&v.to_string()), Data::Bool(v) => visitor.visit_str(&v.to_string()), Data::DateTime(v) => visitor.visit_str(&v.to_string()), - Data::Duration(v) => visitor.visit_str(&v.to_string()), Data::DateTimeIso(v) => visitor.visit_str(v), Data::DurationIso(v) => visitor.visit_str(v), Data::Error(ref err) => Err(DeError::CellError { @@ -635,8 +633,7 @@ impl<'a, 'de> serde::Deserializer<'de> for DataDeserializer<'a> { Data::Empty => visitor.visit_bool(false), Data::Float(v) => visitor.visit_bool(*v != 0.), Data::Int(v) => visitor.visit_bool(*v != 0), - Data::DateTime(v) => visitor.visit_bool(*v != 0.), - Data::Duration(v) => visitor.visit_bool(*v != 0.), + Data::DateTime(v) => visitor.visit_bool(v.as_f64() != 0.), Data::DateTimeIso(_) => visitor.visit_bool(true), Data::DurationIso(_) => visitor.visit_bool(true), Data::Error(ref err) => Err(DeError::CellError { diff --git a/src/formats.rs b/src/formats.rs index 90fc0cf3..347fcb73 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -1,7 +1,4 @@ -use crate::{datatype::DataRef, Data}; - -/// https://learn.microsoft.com/en-us/office/troubleshoot/excel/1900-and-1904-date-system -static EXCEL_1900_1904_DIFF: i64 = 1462; +use crate::datatype::{Data, DataRef, ExcelDateTime, ExcelDateTimeType}; #[derive(Debug, Clone, Copy, PartialEq)] pub enum CellFormat { @@ -91,14 +88,16 @@ pub fn builtin_format_by_code(code: u16) -> CellFormat { // convert i64 to date, if format == Date pub fn format_excel_i64(value: i64, format: Option<&CellFormat>, is_1904: bool) -> Data { match format { - Some(CellFormat::DateTime) => Data::DateTime( - (if is_1904 { - value + EXCEL_1900_1904_DIFF - } else { - value - }) as f64, - ), - Some(CellFormat::TimeDelta) => Data::Duration(value as f64), + Some(CellFormat::DateTime) => Data::DateTime(ExcelDateTime::new( + value as f64, + ExcelDateTimeType::DateTime, + is_1904, + )), + Some(CellFormat::TimeDelta) => Data::DateTime(ExcelDateTime::new( + value as f64, + ExcelDateTimeType::TimeDelta, + is_1904, + )), _ => Data::Int(value), } } @@ -111,12 +110,16 @@ pub fn format_excel_f64_ref<'a>( is_1904: bool, ) -> DataRef<'static> { match format { - Some(CellFormat::DateTime) => DataRef::DateTime(if is_1904 { - value + EXCEL_1900_1904_DIFF as f64 - } else { - value - }), - Some(CellFormat::TimeDelta) => DataRef::Duration(value), + Some(CellFormat::DateTime) => DataRef::DateTime(ExcelDateTime::new( + value, + ExcelDateTimeType::DateTime, + is_1904, + )), + Some(CellFormat::TimeDelta) => DataRef::DateTime(ExcelDateTime::new( + value, + ExcelDateTimeType::TimeDelta, + is_1904, + )), _ => DataRef::Float(value), } } diff --git a/src/lib.rs b/src/lib.rs index 44c5537c..2db5c967 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,7 +83,7 @@ use std::ops::{Index, IndexMut}; use std::path::Path; pub use crate::auto::{open_workbook_auto, open_workbook_auto_from_rs, Sheets}; -pub use crate::datatype::{Data, DataRef, DataType}; +pub use crate::datatype::{Data, DataRef, DataType, ExcelDateTime, ExcelDateTimeType}; pub use crate::de::{DeError, RangeDeserializer, RangeDeserializerBuilder, ToCellDeserializer}; pub use crate::errors::Error; pub use crate::ods::{Ods, OdsError}; diff --git a/tests/test.rs b/tests/test.rs index eabe36e6..fdb6829a 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,9 +1,7 @@ -use calamine::Data::{ - Bool, DateTime, DateTimeIso, Duration, DurationIso, Empty, Error, Float, String, -}; +use calamine::Data::{Bool, DateTime, DateTimeIso, DurationIso, Empty, Error, Float, String}; use calamine::{ - open_workbook, open_workbook_auto, DataType, Ods, Reader, Sheet, SheetType, SheetVisible, Xls, - Xlsb, Xlsx, + open_workbook, open_workbook_auto, DataType, ExcelDateTime, ExcelDateTimeType, Ods, Reader, + Sheet, SheetType, SheetVisible, Xls, Xlsb, Xlsx, }; use calamine::{CellErrorType::*, Data}; use std::io::Cursor; @@ -749,8 +747,22 @@ fn date_xls() { let mut xls: Xls<_> = open_workbook(&path).unwrap(); let range = xls.worksheet_range_at(0).unwrap().unwrap(); - assert_eq!(range.get_value((0, 0)), Some(&DateTime(44197.0))); - assert_eq!(range.get_value((2, 0)), Some(&Duration(10.632060185185185))); + assert_eq!( + range.get_value((0, 0)), + Some(&DateTime(ExcelDateTime::new( + 44197.0, + ExcelDateTimeType::DateTime, + false + ))) + ); + assert_eq!( + range.get_value((2, 0)), + Some(&DateTime(ExcelDateTime::new( + 10.632060185185185, + ExcelDateTimeType::TimeDelta, + false + ))) + ); #[cfg(feature = "dates")] { @@ -773,8 +785,22 @@ fn date_xls_1904() { let mut xls: Xls<_> = open_workbook(&path).unwrap(); let range = xls.worksheet_range_at(0).unwrap().unwrap(); - assert_eq!(range.get_value((0, 0)), Some(&DateTime(44197.0))); - assert_eq!(range.get_value((2, 0)), Some(&Duration(10.632060185185185))); + assert_eq!( + range.get_value((0, 0)), + Some(&DateTime(ExcelDateTime::new( + 42735.0, + ExcelDateTimeType::DateTime, + true + ))) + ); + assert_eq!( + range.get_value((2, 0)), + Some(&DateTime(ExcelDateTime::new( + 10.632060185185185, + ExcelDateTimeType::TimeDelta, + true + ))) + ); #[cfg(feature = "dates")] { @@ -797,8 +823,22 @@ fn date_xlsx() { let mut xls: Xlsx<_> = open_workbook(&path).unwrap(); let range = xls.worksheet_range_at(0).unwrap().unwrap(); - assert_eq!(range.get_value((0, 0)), Some(&DateTime(44197.0))); - assert_eq!(range.get_value((2, 0)), Some(&Duration(10.6320601851852))); + assert_eq!( + range.get_value((0, 0)), + Some(&DateTime(ExcelDateTime::new( + 44197.0, + ExcelDateTimeType::DateTime, + false + ))) + ); + assert_eq!( + range.get_value((2, 0)), + Some(&DateTime(ExcelDateTime::new( + 10.6320601851852, + ExcelDateTimeType::TimeDelta, + false + ))) + ); #[cfg(feature = "dates")] { @@ -821,8 +861,22 @@ fn date_xlsx_1904() { let mut xls: Xlsx<_> = open_workbook(&path).unwrap(); let range = xls.worksheet_range_at(0).unwrap().unwrap(); - assert_eq!(range.get_value((0, 0)), Some(&DateTime(44197.0))); - assert_eq!(range.get_value((2, 0)), Some(&Duration(10.6320601851852))); + assert_eq!( + range.get_value((0, 0)), + Some(&DateTime(ExcelDateTime::new( + 42735.0, + ExcelDateTimeType::DateTime, + true + ))) + ); + assert_eq!( + range.get_value((2, 0)), + Some(&DateTime(ExcelDateTime::new( + 10.6320601851852, + ExcelDateTimeType::TimeDelta, + true + ))) + ); #[cfg(feature = "dates")] { @@ -939,8 +993,22 @@ fn date_xlsb() { let mut xls: Xlsb<_> = open_workbook(&path).unwrap(); let range = xls.worksheet_range_at(0).unwrap().unwrap(); - assert_eq!(range.get_value((0, 0)), Some(&DateTime(44197.0))); - assert_eq!(range.get_value((2, 0)), Some(&Duration(10.6320601851852))); + assert_eq!( + range.get_value((0, 0)), + Some(&DateTime(ExcelDateTime::new( + 44197.0, + ExcelDateTimeType::DateTime, + false + ))) + ); + assert_eq!( + range.get_value((2, 0)), + Some(&DateTime(ExcelDateTime::new( + 10.6320601851852, + ExcelDateTimeType::TimeDelta, + false + ))) + ); #[cfg(feature = "dates")] { @@ -963,8 +1031,22 @@ fn date_xlsb_1904() { let mut xls: Xlsb<_> = open_workbook(&path).unwrap(); let range = xls.worksheet_range_at(0).unwrap().unwrap(); - assert_eq!(range.get_value((0, 0)), Some(&DateTime(44197.0))); - assert_eq!(range.get_value((2, 0)), Some(&Duration(10.6320601851852))); + assert_eq!( + range.get_value((0, 0)), + Some(&DateTime(ExcelDateTime::new( + 42735.0, + ExcelDateTimeType::DateTime, + true + ))) + ); + assert_eq!( + range.get_value((2, 0)), + Some(&DateTime(ExcelDateTime::new( + 10.6320601851852, + ExcelDateTimeType::TimeDelta, + true + ))) + ); #[cfg(feature = "dates")] {