From c6adf8449be356aff804a7fc0fe2017c18c81801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Mon, 30 Dec 2024 12:50:33 +0100 Subject: [PATCH] FIX: Dates are only valid up to the last day of 9999 --- base/src/constants.rs | 5 +++-- base/src/formatter/dates.rs | 12 ++++++------ base/src/functions/date_and_time.rs | 29 ++++++++++++++++++++++++++++ base/src/test/mod.rs | 1 + base/src/test/test_fn_day.rs | 15 ++++++++++++++ xlsx/tests/docs/DAY.xlsx | Bin 9585 -> 10801 bytes 6 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 base/src/test/test_fn_day.rs 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 0b5cba8fde808061aacc50e338d318f7e9525da9..f131f04f004e61b576639d30357baa07634b3963 100644 GIT binary patch delta 4853 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