mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -06:00
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user