diff --git a/crates/functions/src/date.rs b/crates/functions/src/date.rs index 67c56999e..006bc1148 100644 --- a/crates/functions/src/date.rs +++ b/crates/functions/src/date.rs @@ -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 { - let n = num(a, i)?; +fn serial_arg(a: &[Arg], i: usize, sys: DateSystem) -> R { + 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 { - Ok(serial_arg(a, i)?.floor() as i64) +fn day_arg(a: &[Arg], i: usize, sys: DateSystem) -> R { + Ok(serial_arg(a, i, sys)?.floor() as i64) } fn dt(sys: DateSystem, serial: f64) -> R { @@ -66,8 +66,9 @@ fn time(a: &[Arg], _c: &mut dyn Ctx) -> R { } fn part(a: &[Arg], c: &mut dyn Ctx, f: fn(&DateTime) -> f64) -> R { - 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 { @@ -81,20 +82,20 @@ fn day(a: &[Arg], c: &mut dyn Ctx) -> R { } /// Seconds into the day, rounded to the nearest second. -fn day_seconds(a: &[Arg]) -> R { - let s = serial_arg(a, 0)?; +fn day_seconds(a: &[Arg], sys: DateSystem) -> R { + 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 { - num_val((day_seconds(a)? / 3600) as f64) +fn hour(a: &[Arg], c: &mut dyn Ctx) -> R { + num_val((day_seconds(a, c.date_system())? / 3600) as f64) } -fn minute(a: &[Arg], _c: &mut dyn Ctx) -> R { - num_val((day_seconds(a)? / 60 % 60) as f64) +fn minute(a: &[Arg], c: &mut dyn Ctx) -> R { + num_val((day_seconds(a, c.date_system())? / 60 % 60) as f64) } -fn second(a: &[Arg], _c: &mut dyn Ctx) -> R { - num_val((day_seconds(a)? % 60) as f64) +fn second(a: &[Arg], c: &mut dyn Ctx) -> R { + num_val((day_seconds(a, c.date_system())? % 60) as f64) } fn now(_a: &[Arg], c: &mut dyn Ctx) -> R { @@ -105,9 +106,10 @@ fn today(_a: &[Arg], c: &mut dyn Ctx) -> R { } fn weekday(a: &[Arg], c: &mut dyn Ctx) -> R { - 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 { fn weeknum(a: &[Arg], c: &mut dyn Ctx) -> R { 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 { } fn isoweeknum(a: &[Arg], c: &mut dyn Ctx) -> R { - 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 { @@ -157,7 +160,7 @@ fn months_arg(a: &[Arg], i: usize) -> R { fn edate(a: &[Arg], c: &mut dyn Ctx) -> R { 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 { fn eomonth(a: &[Arg], c: &mut dyn Ctx) -> R { 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 { fn datedif(a: &[Arg], c: &mut dyn Ctx) -> R { 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 { num_val(n - n.floor()) } -fn days(a: &[Arg], _c: &mut dyn Ctx) -> R { - num_val((day_arg(a, 0)? - day_arg(a, 1)?) as f64) +fn days(a: &[Arg], c: &mut dyn Ctx) -> R { + 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 { 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> { +fn holidays(v: Option<&Arg>, sys: DateSystem) -> R> { 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> { 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 { 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 { 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 { - 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 { @@ -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); + } }