Read date text arguments in the workbook date system (#1038)

This commit is contained in:
MarekSpodBiedry
2026-10-13 18:06:29 +02:00
parent 84acf6293b
commit e440c8bdc9
+52 -36
View File
@@ -13,16 +13,16 @@ const ND: &[bool] = &[true, true, false];
const NDI: &[bool] = &[true, true, true, false];
/// A date serial argument (truncated to whole days when `whole`). Out of range → `#NUM!`.
fn serial_arg(a: &[Arg], i: usize) -> R<f64> {
let n = num(a, i)?;
fn serial_arg(a: &[Arg], i: usize, sys: DateSystem) -> R<f64> {
let n = scalar(arg(a, i)?).to_number_in(sys)?;
if !(0.0..MAX_SERIAL + 1.0).contains(&n) {
return Err(CellError::Num);
}
Ok(n)
}
fn day_arg(a: &[Arg], i: usize) -> R<i64> {
Ok(serial_arg(a, i)?.floor() as i64)
fn day_arg(a: &[Arg], i: usize, sys: DateSystem) -> R<i64> {
Ok(serial_arg(a, i, sys)?.floor() as i64)
}
fn dt(sys: DateSystem, serial: f64) -> R<DateTime> {
@@ -66,8 +66,9 @@ fn time(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
}
fn part(a: &[Arg], c: &mut dyn Ctx, f: fn(&DateTime) -> f64) -> R<Value> {
let s = serial_arg(a, 0)?;
num_val(f(&dt(c.date_system(), s.floor())?))
let sys = c.date_system();
let s = serial_arg(a, 0, sys)?;
num_val(f(&dt(sys, s.floor())?))
}
fn year(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
@@ -81,20 +82,20 @@ fn day(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
}
/// Seconds into the day, rounded to the nearest second.
fn day_seconds(a: &[Arg]) -> R<i64> {
let s = serial_arg(a, 0)?;
fn day_seconds(a: &[Arg], sys: DateSystem) -> R<i64> {
let s = serial_arg(a, 0, sys)?;
let secs = ((s - s.floor()) * 86400.0).round() as i64;
Ok(if secs >= 86400 { 0 } else { secs })
}
fn hour(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
num_val((day_seconds(a)? / 3600) as f64)
fn hour(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
num_val((day_seconds(a, c.date_system())? / 3600) as f64)
}
fn minute(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
num_val((day_seconds(a)? / 60 % 60) as f64)
fn minute(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
num_val((day_seconds(a, c.date_system())? / 60 % 60) as f64)
}
fn second(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
num_val((day_seconds(a)? % 60) as f64)
fn second(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
num_val((day_seconds(a, c.date_system())? % 60) as f64)
}
fn now(_a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
@@ -105,9 +106,10 @@ fn today(_a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
}
fn weekday(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
let s = day_arg(a, 0)?;
let sys = c.date_system();
let s = day_arg(a, 0, sys)?;
let t = to_int(opt_num(a, 1, 1.0)?)?;
let wd = weekday0(c.date_system(), s) as i64;
let wd = weekday0(sys, s) as i64;
let r = match t {
1 | 17 => wd + 1,
2 | 11 => (wd + 6) % 7 + 1,
@@ -128,7 +130,7 @@ fn isoweek(sys: DateSystem, s: i64) -> R<i64> {
fn weeknum(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
let sys = c.date_system();
let s = day_arg(a, 0)?;
let s = day_arg(a, 0, sys)?;
let t = to_int(opt_num(a, 1, 1.0)?)?;
let start = match t {
1 | 17 => 0,
@@ -144,7 +146,8 @@ fn weeknum(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
}
fn isoweeknum(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
num_val(isoweek(c.date_system(), day_arg(a, 0)?)? as f64)
let sys = c.date_system();
num_val(isoweek(sys, day_arg(a, 0, sys)?)? as f64)
}
fn months_arg(a: &[Arg], i: usize) -> R<i64> {
@@ -157,7 +160,7 @@ fn months_arg(a: &[Arg], i: usize) -> R<i64> {
fn edate(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
let sys = c.date_system();
let d = dt(sys, day_arg(a, 0)? as f64)?;
let d = dt(sys, day_arg(a, 0, sys)? as f64)?;
let m = months_arg(a, 1)?;
let total = d.year as i64 * 12 + d.month as i64 - 1 + m;
let (y, mo) = (total.div_euclid(12), total.rem_euclid(12) + 1);
@@ -172,7 +175,7 @@ fn edate(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
fn eomonth(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
let sys = c.date_system();
let d = dt(sys, day_arg(a, 0)? as f64)?;
let d = dt(sys, day_arg(a, 0, sys)? as f64)?;
let m = months_arg(a, 1)?;
let total = d.year as i64 * 12 + d.month as i64 - 1 + m;
let (y, mo) = (total.div_euclid(12), total.rem_euclid(12) + 1);
@@ -185,8 +188,8 @@ fn eomonth(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
fn datedif(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
let sys = c.date_system();
let s = day_arg(a, 0)?;
let e = day_arg(a, 1)?;
let s = day_arg(a, 0, sys)?;
let e = day_arg(a, 1, sys)?;
let unit = text(a, 2)?.to_ascii_uppercase();
if s > e {
return Err(CellError::Num);
@@ -247,8 +250,9 @@ fn timevalue(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
num_val(n - n.floor())
}
fn days(a: &[Arg], _c: &mut dyn Ctx) -> R<Value> {
num_val((day_arg(a, 0)? - day_arg(a, 1)?) as f64)
fn days(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
let sys = c.date_system();
num_val((day_arg(a, 0, sys)? - day_arg(a, 1, sys)?) as f64)
}
fn last_of_feb(d: &DateTime) -> bool {
@@ -257,8 +261,8 @@ fn last_of_feb(d: &DateTime) -> bool {
fn days360(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
let sys = c.date_system();
let d1 = dt(sys, day_arg(a, 0)? as f64)?;
let d2 = dt(sys, day_arg(a, 1)? as f64)?;
let d1 = dt(sys, day_arg(a, 0, sys)? as f64)?;
let d2 = dt(sys, day_arg(a, 1, sys)? as f64)?;
let european = opt_bool(a, 2, false)?;
num_val(days360_core(&d1, &d2, european) as f64)
}
@@ -313,7 +317,7 @@ fn weekend_mask(v: Option<&Arg>) -> R<[bool; 7]> {
}
}
fn holidays(v: Option<&Arg>) -> R<Vec<i64>> {
fn holidays(v: Option<&Arg>, sys: DateSystem) -> R<Vec<i64>> {
let mut out = Vec::new();
if let Some(a) = v {
for x in as_array(&a.value).iter() {
@@ -321,7 +325,7 @@ fn holidays(v: Option<&Arg>) -> R<Vec<i64>> {
Value::Empty => {}
Value::Error(e) => return Err(*e),
other => {
let n = other.to_number()?;
let n = other.to_number_in(sys)?;
if !(0.0..MAX_SERIAL + 1.0).contains(&n) {
return Err(CellError::Num);
}
@@ -353,9 +357,9 @@ fn count_workdays(sys: DateSystem, a: i64, b: i64, mask: &[bool; 7]) -> i64 {
fn networkdays_core(a: &[Arg], c: &mut dyn Ctx, mask: [bool; 7], hol: Option<&Arg>) -> R<Value> {
let sys = c.date_system();
let s = day_arg(a, 0)?;
let e = day_arg(a, 1)?;
let hs = holidays(hol)?;
let s = day_arg(a, 0, sys)?;
let e = day_arg(a, 1, sys)?;
let hs = holidays(hol, sys)?;
let (lo, hi, sign) = if s <= e { (s, e, 1) } else { (e, s, -1) };
let mut n = count_workdays(sys, lo, hi, &mask);
n -= hs.iter().filter(|&&h| h >= lo && h <= hi && !mask[weekday0(sys, h)]).count() as i64;
@@ -387,13 +391,13 @@ fn advance(sys: DateSystem, start: i64, n: i64, dir: i64, mask: &[bool; 7], per_
fn workday_core(a: &[Arg], c: &mut dyn Ctx, mask: [bool; 7], hol: Option<&Arg>) -> R<Value> {
let sys = c.date_system();
let start = day_arg(a, 0)?;
let start = day_arg(a, 0, sys)?;
let days = num(a, 1)?.trunc();
if days.abs() > 1e7 {
return Err(CellError::Num);
}
let days = days as i64;
let hs = holidays(hol)?;
let hs = holidays(hol, sys)?;
let per_week = mask.iter().filter(|w| !**w).count() as i64;
if per_week == 0 {
return Err(CellError::Value);
@@ -476,10 +480,11 @@ fn feb29_between(sys: DateSystem, s: i64, e: i64, d1: &DateTime, d2: &DateTime)
}
fn yearfrac(a: &[Arg], c: &mut dyn Ctx) -> R<Value> {
let s = day_arg(a, 0)?;
let e = day_arg(a, 1)?;
let sys = c.date_system();
let s = day_arg(a, 0, sys)?;
let e = day_arg(a, 1, sys)?;
let basis = if has(a, 2) { to_int(num(a, 2)?)? } else { 0 };
num_val(yearfrac_core(c.date_system(), s, e, basis)?)
num_val(yearfrac_core(sys, s, e, basis)?)
}
pub(crate) fn specs() -> Vec<FnSpec> {
@@ -832,4 +837,15 @@ mod tests {
close(crate::call(spec, &[t("2/29/1904")], &mut ctx), 59.0);
is_err(crate::call(spec, &[t("2/29/1900")], &mut ctx), CellError::Value);
}
#[test]
fn date_text_arguments_use_the_workbook_date_system() {
let mut ctx = TestCtx { sys: gridcraft_core::DateSystem::D1904, ..TestCtx::default() };
close(crate::call(crate::lookup("YEAR").unwrap(), &[t("1/1/2020")], &mut ctx), 2020.0);
close(crate::call(crate::lookup("MONTH").unwrap(), &[t("2/29/1904")], &mut ctx), 2.0);
close(crate::call(crate::lookup("DAY").unwrap(), &[t("2/29/1904")], &mut ctx), 29.0);
close(crate::call(crate::lookup("DAYS").unwrap(), &[t("1/2/2020"), t("1/1/2020")], &mut ctx), 1.0);
let networkdays = crate::lookup("NETWORKDAYS").unwrap();
close(crate::call(networkdays, &[t("1/6/2020"), t("1/10/2020"), t("1/8/2020")], &mut ctx), 4.0);
}
}