Merge pull request #742 from marcoarnulfo:fix/edate-eomonth-1900

This commit is contained in:
Brandon Thomas
2026-10-13 01:39:43 -04:00
+30 -9
View File
@@ -1,6 +1,6 @@
//! Date and time functions. //! Date and time functions.
use gridcraft_core::date::{DateTime, datetime_from_serial, days_in_month, days_in_month_in, is_leap, serial_from_ymd, serial_in_system}; use gridcraft_core::date::{DateTime, datetime_from_serial, days_in_month, is_leap, serial_from_ymd, serial_in_system};
use gridcraft_core::parse::parse_input; use gridcraft_core::parse::parse_input;
use gridcraft_core::{CellError, DateSystem, Value}; use gridcraft_core::{CellError, DateSystem, Value};
@@ -164,7 +164,9 @@ fn edate(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
if !(1900..=9999).contains(&y) { if !(1900..=9999).contains(&y) {
return Err(CellError::Num); return Err(CellError::Num);
} }
let dd = (d.day.max(1) as i64).min(days_in_month_in(sys, y as i32, mo as u32) as i64); // Excel keeps serial 0's day 0 and gives February 1900 its real 28 days here, though the
// sheet's calendar has a 29 February 1900 (serial 60).
let dd = (d.day as i64).min(days_in_month(y as i32, mo as u32) as i64);
num_val(ymd_serial(sys, y, mo, dd)? as f64) num_val(ymd_serial(sys, y, mo, dd)? as f64)
} }
@@ -177,7 +179,8 @@ fn eomonth(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
if !(1900..=9999).contains(&y) { if !(1900..=9999).contains(&y) {
return Err(CellError::Num); return Err(CellError::Num);
} }
num_val(ymd_serial(sys, y, mo, days_in_month_in(sys, y as i32, mo as u32) as i64)? as f64) // Like EDATE, February 1900 ends on the 28th (serial 59).
num_val(ymd_serial(sys, y, mo, days_in_month(y as i32, mo as u32) as i64)? as f64)
} }
fn datedif(a: &[Arg], c: &mut dyn Ctx) -> R<Value> { fn datedif(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
@@ -680,17 +683,35 @@ mod tests {
close(ev("DATEVALUE", vec![t("2/29/1900")]), 60.0); close(ev("DATEVALUE", vec![t("2/29/1900")]), 60.0);
close(ev("DATEVALUE", vec![t("3/1/1900")]), 61.0); close(ev("DATEVALUE", vec![t("3/1/1900")]), 61.0);
is_err(ev("DATEVALUE", vec![t("2/29/1901")]), CellError::Value); is_err(ev("DATEVALUE", vec![t("2/29/1901")]), CellError::Value);
// EDATE and EOMONTH land on it as the end of February 1900. // EDATE and EOMONTH read it as 29 February, but end February 1900 on the 28th.
close(ev("EOMONTH", vec![n(d(1900.0, 1.0, 15.0)), n(1.0)]), 60.0); close(ev("EOMONTH", vec![n(d(1900.0, 1.0, 15.0)), n(1.0)]), 59.0);
close(ev("EOMONTH", vec![n(60.0), n(0.0)]), 60.0); close(ev("EOMONTH", vec![n(60.0), n(0.0)]), 59.0);
close(ev("EOMONTH", vec![n(d(1900.0, 3.0, 15.0)), n(-1.0)]), 60.0); close(ev("EOMONTH", vec![n(d(1900.0, 3.0, 15.0)), n(-1.0)]), 59.0);
close(ev("EOMONTH", vec![n(60.0), n(12.0)]), d(1901.0, 2.0, 28.0)); close(ev("EOMONTH", vec![n(60.0), n(12.0)]), d(1901.0, 2.0, 28.0));
close(ev("EDATE", vec![n(d(1900.0, 1.0, 31.0)), n(1.0)]), 60.0); close(ev("EDATE", vec![n(d(1900.0, 1.0, 31.0)), n(1.0)]), 59.0);
close(ev("EDATE", vec![n(d(1900.0, 3.0, 31.0)), n(-1.0)]), 60.0); close(ev("EDATE", vec![n(d(1900.0, 3.0, 31.0)), n(-1.0)]), 59.0);
close(ev("EDATE", vec![n(60.0), n(1.0)]), d(1900.0, 3.0, 29.0)); close(ev("EDATE", vec![n(60.0), n(1.0)]), d(1900.0, 3.0, 29.0));
close(ev("EDATE", vec![n(60.0), n(12.0)]), d(1901.0, 2.0, 28.0)); close(ev("EDATE", vec![n(60.0), n(12.0)]), d(1901.0, 2.0, 28.0));
} }
#[test]
fn edate_eomonth_early_1900() {
// Excel 365: serial 0 is 0 January 1900 and February 1900 has 28 days here.
close(ev("EDATE", vec![n(0.0), n(0.0)]), 0.0);
close(ev("EDATE", vec![n(0.0), n(1.0)]), 31.0);
close(ev("EDATE", vec![n(0.0), n(12.0)]), 366.0);
close(ev("EOMONTH", vec![n(0.0), n(1.0)]), 59.0);
close(ev("EDATE", vec![n(31.0), n(1.0)]), 59.0);
close(ev("EOMONTH", vec![n(59.0), n(0.0)]), 59.0);
// Unchanged, and the same as Excel.
close(ev("EOMONTH", vec![n(0.0), n(0.0)]), 31.0);
is_err(ev("EOMONTH", vec![n(0.0), n(-1.0)]), CellError::Num);
close(ev("EDATE", vec![n(59.0), n(1.0)]), 88.0);
close(ev("EDATE", vec![n(60.0), n(1.0)]), 89.0);
close(ev("EDATE", vec![n(61.0), n(-1.0)]), 32.0);
close(ev("EOMONTH", vec![n(31.0), n(0.0)]), 31.0);
}
#[test] #[test]
fn datedif_units() { fn datedif_units() {
let s = n(d(2001.0, 6.0, 1.0)); let s = n(d(2001.0, 6.0, 1.0));