diff --git a/base/src/constants.rs b/base/src/constants.rs index 53b13f2..3ecc065 100644 --- a/base/src/constants.rs +++ b/base/src/constants.rs @@ -21,7 +21,8 @@ pub(crate) const EXCEL_DATE_BASE: i32 = 693_594; // However, it uses a different numbering scheme for dates // that are before 1900-01-01. // So for now we will simply not support dates before 1900-01-01. -pub(crate) const EXCEL_DATE_MIN: i32 = 2; +pub(crate) const MINIMUM_DATE_SERIAL_NUMBER: i32 = 2; // Excel can handle dates until the year 9999-12-31 -pub(crate) const EXCEL_DATE_MAX: i32 = 2_958_465; +// 2958465 is the number of days from 1900-01-01 to 9999-12-31 +pub(crate) const MAXIMUM_DATE_SERIAL_NUMBER: i32 = 2_958_465; diff --git a/base/src/formatter/dates.rs b/base/src/formatter/dates.rs index f4595c1..eb72ad2 100644 --- a/base/src/formatter/dates.rs +++ b/base/src/formatter/dates.rs @@ -5,8 +5,8 @@ use chrono::Months; use chrono::NaiveDate; use crate::constants::EXCEL_DATE_BASE; -use crate::constants::EXCEL_DATE_MAX; -use crate::constants::EXCEL_DATE_MIN; +use crate::constants::MAXIMUM_DATE_SERIAL_NUMBER; +use crate::constants::MINIMUM_DATE_SERIAL_NUMBER; #[inline] fn convert_to_serial_number(date: NaiveDate) -> i32 { @@ -14,8 +14,8 @@ fn convert_to_serial_number(date: NaiveDate) -> i32 { } fn is_date_within_range(date: NaiveDate) -> bool { - convert_to_serial_number(date) >= EXCEL_DATE_MIN - && convert_to_serial_number(date) <= EXCEL_DATE_MAX + convert_to_serial_number(date) >= MINIMUM_DATE_SERIAL_NUMBER + && convert_to_serial_number(date) <= MAXIMUM_DATE_SERIAL_NUMBER } pub fn from_excel_date(days: i64) -> NaiveDate { @@ -132,11 +132,11 @@ mod tests { fn test_max_and_min_dates() { assert_eq!( permissive_date_to_serial_number(31, 12, 9999), - Ok(EXCEL_DATE_MAX), + Ok(MAXIMUM_DATE_SERIAL_NUMBER), ); assert_eq!( permissive_date_to_serial_number(1, 1, 1900), - Ok(EXCEL_DATE_MIN), + Ok(MINIMUM_DATE_SERIAL_NUMBER), ); } } diff --git a/base/src/functions/date_and_time.rs b/base/src/functions/date_and_time.rs index c50775a..d93d938 100644 --- a/base/src/functions/date_and_time.rs +++ b/base/src/functions/date_and_time.rs @@ -3,6 +3,7 @@ use chrono::Datelike; use chrono::Months; use chrono::Timelike; +use crate::constants::MAXIMUM_DATE_SERIAL_NUMBER; use crate::expressions::types::CellReferenceIndex; use crate::formatter::dates::date_to_serial_number; use crate::formatter::dates::permissive_date_to_serial_number; @@ -32,6 +33,13 @@ impl Model { } Err(s) => return s, }; + if serial_number > MAXIMUM_DATE_SERIAL_NUMBER as i64 { + return CalcResult::Error { + error: Error::NUM, + origin: cell, + message: "Function DAY parameter 1 value is too large.".to_string(), + }; + } let date = from_excel_date(serial_number); let day = date.day() as f64; CalcResult::Number(day) @@ -56,6 +64,13 @@ impl Model { } Err(s) => return s, }; + if serial_number > MAXIMUM_DATE_SERIAL_NUMBER as i64 { + return CalcResult::Error { + error: Error::NUM, + origin: cell, + message: "Function DAY parameter 1 value is too large.".to_string(), + }; + } let date = from_excel_date(serial_number); let month = date.month() as f64; CalcResult::Number(month) @@ -80,6 +95,13 @@ impl Model { } Err(s) => return s, }; + if serial_number > MAXIMUM_DATE_SERIAL_NUMBER as i64 { + return CalcResult::Error { + error: Error::NUM, + origin: cell, + message: "Function DAY parameter 1 value is too large.".to_string(), + }; + } let months = match self.get_number_no_bools(&args[1], cell) { Ok(c) => { @@ -178,6 +200,13 @@ impl Model { } Err(s) => return s, }; + if serial_number > MAXIMUM_DATE_SERIAL_NUMBER as i64 { + return CalcResult::Error { + error: Error::NUM, + origin: cell, + message: "Function DAY parameter 1 value is too large.".to_string(), + }; + } let date = from_excel_date(serial_number); let year = date.year() as f64; CalcResult::Number(year) diff --git a/base/src/test/mod.rs b/base/src/test/mod.rs index 7e76f92..80d1c2b 100644 --- a/base/src/test/mod.rs +++ b/base/src/test/mod.rs @@ -13,6 +13,7 @@ mod test_fn_averageifs; mod test_fn_choose; mod test_fn_concatenate; mod test_fn_count; +mod test_fn_day; mod test_fn_exact; mod test_fn_financial; mod test_fn_formulatext; diff --git a/base/src/test/test_fn_day.rs b/base/src/test/test_fn_day.rs new file mode 100644 index 0000000..3ea034c --- /dev/null +++ b/base/src/test/test_fn_day.rs @@ -0,0 +1,15 @@ +#![allow(clippy::unwrap_used)] + +use crate::test::util::new_empty_model; + +#[test] +fn test_fn_date_arguments() { + let mut model = new_empty_model(); + + model._set("A1", "=DAY(95051806)"); + model._set("A2", "=DAY(2958465)"); + model.evaluate(); + + assert_eq!(model._get_text("A1"), *"#NUM!"); + assert_eq!(model._get_text("A2"), *"31"); +} diff --git a/xlsx/tests/docs/DAY.xlsx b/xlsx/tests/docs/DAY.xlsx index 0b5cba8..f131f04 100644 Binary files a/xlsx/tests/docs/DAY.xlsx and b/xlsx/tests/docs/DAY.xlsx differ