Shared Rust kernels
ported/date_kernels
All modules · quoted at commit 9e1884369a67 of DnaCalc/DnaCalc. Comments are the source's own.
days_from_civil · 9 lines · calc-func/src/ported/date_kernels.rs on GitHubfn days_from_civil(year: i64, month: i64, day: i64) -> i64 {
let year = year - i64::from(month <= 2);
let era = if year >= 0 { year } else { year - 399 } / 400;
let yoe = year - era * 400;
let mp = month + if month > 2 { -3 } else { 9 };
let doy = (153 * mp + 2) / 5 + day - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146097 + doe - 719468
}
Used by 25 functions.
civil_from_days · 12 lines · calc-func/src/ported/date_kernels.rs on GitHubfn civil_from_days(z: i64) -> (i64, i64, i64) {
let z = z + 719468;
let era = if z >= 0 { z } else { z - 146096 } / 146097;
let doe = z - era * 146097;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = mp + if mp < 10 { 3 } else { -9 };
(y + i64::from(m <= 2), m, d)
}
Used by 23 functions.
serial_unbounded_1900 · 12 lines · calc-func/src/ported/date_kernels.rs on GitHub/// The 1900 serial of a date, with Excel's 29 February 1900 and day 0 of January 1900, for any
/// day of the month (a day past the month's end runs into the next month).
pub(crate) fn serial_unbounded_1900(year: i64, month: i64, day: i64) -> i64 {
let base = days_from_civil(1899, 12, 31);
let month_start = days_from_civil(year, month, 1) - base;
let month_start = if month_start >= 60 {
month_start + 1
} else {
month_start
};
month_start + day - 1
}
Used by 17 functions.
ymd_1900 · 11 lines · calc-func/src/ported/date_kernels.rs on GitHub/// The year, month and day of a whole 1900 serial (0 is 0 January 1900, 60 is 29 February 1900).
pub(crate) fn ymd_1900(serial: i64) -> (i64, i64, i64) {
if serial == 0 {
return (1900, 1, 0);
}
if serial == 60 {
return (1900, 2, 29);
}
let adjusted = if serial > 60 { serial - 1 } else { serial };
civil_from_days(days_from_civil(1899, 12, 31) + adjusted)
}
Used by 23 functions.
is_leap · 3 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) fn is_leap(year: i64) -> bool {
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
Used by 19 functions.
days_in_month · 14 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) fn days_in_month(year: i64, month: i64) -> i64 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
if is_leap(year) {
29
} else {
28
}
}
_ => 30,
}
}
Used by 19 functions.
serial_to_ymd · 11 lines · calc-func/src/ported/date_kernels.rs on GitHub/// The year, month and day of a 1900 serial, rounded to the nearest second first.
pub(crate) fn serial_to_ymd(serial: f64) -> Result<(i64, i64, i64), W> {
if !serial.is_finite() || serial < 0.0 {
return Err(W::Num);
}
let rounded = serial + 0.5 / 86_400.0;
if rounded >= (MAX_SERIAL_1900 + 1) as f64 {
return Err(W::Num);
}
Ok(ymd_1900(rounded.floor() as i64))
}
Used by 3 functions.
hms · 13 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) fn hms(serial: f64) -> Result<(f64, f64, f64), W> {
let rounded = serial + 0.5 / 86_400.0;
if !serial.is_finite() || serial < 0.0 || rounded >= (MAX_SERIAL_1900 + 1) as f64 {
return Err(W::Num);
}
let fractional = rounded - rounded.floor();
let hours = fractional * 24.0;
let hour = hours.floor();
let minutes = (hours - hour) * 60.0;
let minute = minutes.floor();
let second = ((minutes - minute) * 60.0).floor().rem_euclid(60.0);
Ok((hour, minute, second))
}
Used by 3 functions.
days · 7 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) fn days(end_serial: f64, start_serial: f64) -> Result<f64, W> {
let range = 0.0..(MAX_SERIAL_1900 + 1) as f64;
if !range.contains(&end_serial) || !range.contains(&start_serial) {
return Err(W::Num);
}
Ok(end_serial.trunc() - start_serial.trunc())
}
Used by 5 functions.
whole_serial · 6 lines · calc-func/src/ported/date_kernels.rs on GitHubfn whole_serial(n: f64) -> Result<i64, W> {
if !(0.0..(MAX_SERIAL_1900 + 1) as f64).contains(&n) {
return Err(W::Num);
}
Ok(n.trunc() as i64)
}
Used by 23 functions.
whole · 6 lines · calc-func/src/ported/date_kernels.rs on GitHubfn whole(n: f64) -> Result<i64, W> {
if !n.is_finite() {
return Err(W::Num);
}
Ok(n.trunc() as i64)
}
Used by 24 functions.
shifted_month · 8 lines · calc-func/src/ported/date_kernels.rs on GitHubfn shifted_month(year: i64, month: i64, offset: i64) -> Result<(i64, i64), W> {
let month_index = year
.checked_mul(12)
.and_then(|v| v.checked_add(month - 1))
.and_then(|v| v.checked_add(offset))
.ok_or(W::Num)?;
Ok((month_index.div_euclid(12), month_index.rem_euclid(12) + 1))
}
Used by 2 functions.
weekday_sunday_one_based · 3 lines · calc-func/src/ported/date_kernels.rs on GitHubfn weekday_sunday_one_based(serial: i64) -> i64 {
(serial - 1).rem_euclid(7) + 1
}
Used by 25 functions.
remap_weekday · 12 lines · calc-func/src/ported/date_kernels.rs on GitHubfn remap_weekday(sunday_one_based: i64, return_type: i64) -> Option<f64> {
match return_type {
1 => Some(sunday_one_based as f64),
2 | 11 => Some(((sunday_one_based + 5) % 7 + 1) as f64),
3 => Some(((sunday_one_based + 5) % 7) as f64),
12..=17 => {
let start_day = (return_type - 10).rem_euclid(7) + 1;
Some(((sunday_one_based - start_day).rem_euclid(7) + 1) as f64)
}
_ => None,
}
}
Used by 21 functions.
weekday_of · 3 lines · calc-func/src/ported/date_kernels.rs on GitHubfn weekday_of(serial: i64, return_type: i64) -> Result<f64, W> {
remap_weekday(weekday_sunday_one_based(serial), return_type).ok_or(W::Num)
}
Used by 21 functions.
weekday · 8 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) fn weekday(serial: f64, return_type: Option<f64>) -> Result<f64, W> {
if serial < 0.0 {
return Err(W::Num);
}
let serial = whole_serial(serial + 0.5 / 86_400.0)?;
let return_type = whole(date_integer_floor(return_type.unwrap_or(1.0)))?;
weekday_of(serial, return_type)
}
Used by 20 functions.
iso_weeknum_serial · 10 lines · calc-func/src/ported/date_kernels.rs on GitHubfn iso_weeknum_serial(serial: i64) -> Result<f64, W> {
let weekday_monday = weekday_of(serial, 2)? as i64;
let thursday = serial + (4 - weekday_monday);
let (iso_year, _, _) =
ymd_from_excel_serial(DateSystem::System1900, thursday as f64).ok_or(W::Num)?;
let jan4 = serial_unbounded_1900(iso_year, 1, 4);
let jan4_weekday = weekday_of(jan4, 2)? as i64;
let first_monday = jan4 - (jan4_weekday - 1);
Ok(((serial - first_monday).div_euclid(7) + 1) as f64)
}
Used by 2 functions.
ymd_of · 10 lines · calc-func/src/ported/date_kernels.rs on GitHubfn ymd_of(serial: f64) -> Result<(i64, i64, i64), W> {
if !serial.is_finite() {
return Err(W::Num);
}
let whole = serial.trunc() as i64;
if whole < 0 {
return Err(W::Num);
}
Ok(ymd_1900(whole))
}
Used by 2 functions.
WeekendMask::saturday_sunday · 5 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) const fn saturday_sunday() -> Self {
Self {
monday_first: [false, false, false, false, false, true, true],
}
}
Used by 4 functions.
WeekendMask::from_number · 20 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) fn from_number(code: i64) -> Option<Self> {
let monday_first = match code {
1 => [false, false, false, false, false, true, true],
2 => [true, false, false, false, false, false, true],
3 => [true, true, false, false, false, false, false],
4 => [false, true, true, false, false, false, false],
5 => [false, false, true, true, false, false, false],
6 => [false, false, false, true, true, false, false],
7 => [false, false, false, false, true, true, false],
11 => [false, false, false, false, false, false, true],
12 => [true, false, false, false, false, false, false],
13 => [false, true, false, false, false, false, false],
14 => [false, false, true, false, false, false, false],
15 => [false, false, false, true, false, false, false],
16 => [false, false, false, false, true, false, false],
17 => [false, false, false, false, false, true, false],
_ => return None,
};
Some(Self { monday_first })
}
Used by 4 functions.
WeekendMask::from_mask_text · 11 lines · calc-func/src/ported/date_kernels.rs on GitHub/// A mask of seven 0 and 1 characters, Monday first.
pub(crate) fn from_mask_text(text: &str) -> Option<Self> {
if text.len() != 7 || !text.chars().all(|ch| ch == '0' || ch == '1') {
return None;
}
let mut monday_first = [false; 7];
for (idx, ch) in text.chars().enumerate() {
monday_first[idx] = ch == '1';
}
Some(Self { monday_first })
}
Used by 4 functions.
WeekendMask::is_all_days · 3 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) fn is_all_days(&self) -> bool {
self.monday_first.iter().all(|bit| *bit)
}
Used by 4 functions.
WeekendMask::contains_serial · 4 lines · calc-func/src/ported/date_kernels.rs on GitHubfn contains_serial(&self, serial: i64) -> bool {
let monday_idx = ((weekday_sunday_one_based(serial) + 5).rem_euclid(7)) as usize;
self.monday_first[monday_idx]
}
Used by 4 functions.
serial_from_number · 10 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) fn serial_from_number(value: f64) -> Result<i64, W> {
if !value.is_finite() {
return Err(W::Num);
}
let serial = value.trunc() as i64;
if !(0..=MAX_SERIAL_1900).contains(&serial) {
return Err(W::Num);
}
Ok(serial)
}
Used by 4 functions.
is_business_day · 3 lines · calc-func/src/ported/date_kernels.rs on GitHubfn is_business_day(serial: i64, weekend: WeekendMask, holidays: &BTreeSet<i64>) -> bool {
!weekend.contains_serial(serial) && !holidays.contains(&serial)
}
Used by 4 functions.
workday · 30 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) fn workday(
start_serial: f64,
days: f64,
weekend: WeekendMask,
holidays: &BTreeSet<i64>,
) -> Result<f64, W> {
if weekend.is_all_days() {
return Err(W::Value);
}
let start = serial_from_number(start_serial)?;
if !days.is_finite() {
return Err(W::Num);
}
let mut remaining = days.floor() as i64;
if remaining == 0 {
return Ok(start as f64);
}
let step = if remaining > 0 { 1 } else { -1 };
let mut current = start;
while remaining != 0 {
current += step;
if !(0..=MAX_SERIAL_1900).contains(¤t) {
return Err(W::Num);
}
if is_business_day(current, weekend, holidays) {
remaining -= step;
}
}
Ok(current as f64)
}
Used by 4 functions.
networkdays · 34 lines · calc-func/src/ported/date_kernels.rs on GitHubpub(crate) fn networkdays(
start_serial: f64,
end_serial: f64,
weekend: WeekendMask,
holidays: &BTreeSet<i64>,
) -> Result<f64, W> {
let start = serial_from_number(start_serial)?;
let end = serial_from_number(end_serial)?;
if weekend.is_all_days() {
return Ok(0.0);
}
let (lo, hi, sign) = if start <= end {
(start, end, 1.0)
} else {
(end, start, -1.0)
};
// Whole weeks hold the same business days, so only the remainder is walked.
let per_week = (0..7)
.filter(|&offset| !weekend.contains_serial(lo + offset))
.count() as i64;
let span = hi - lo + 1;
let weeks = span / 7;
let mut count = weeks * per_week;
for serial in lo + weeks * 7..=hi {
if !weekend.contains_serial(serial) {
count += 1;
}
}
count -= holidays
.range(lo..=hi)
.filter(|&&serial| !weekend.contains_serial(serial))
.count() as i64;
Ok(sign * count as f64)
}
Used by 2 functions.