Merge pull request #599 from MarekSpodBiedry:issue-565
This commit is contained in:
commit
8d3e440a8b
|
|
@ -103,6 +103,14 @@ pub fn days_in_month_in(sys: DateSystem, y: i32, m: u32) -> u32 {
|
|||
if sys == DateSystem::D1900 && y == 1900 && m == 2 { 29 } else { days_in_month(y, m) }
|
||||
}
|
||||
|
||||
/// Last serial that is a valid date in a date system: 9999-12-31 (2958465 in the 1900 system, 2957003 in the 1904 system).
|
||||
pub fn max_serial(sys: DateSystem) -> i64 {
|
||||
match sys {
|
||||
DateSystem::D1900 => 2_958_465,
|
||||
DateSystem::D1904 => 2_958_465 - 1462,
|
||||
}
|
||||
}
|
||||
|
||||
/// Serial for a date. Month and day overflow roll over like `DATE()` (month 13 = January next
|
||||
/// year, day 0 = last day of the previous month), in the date system's own calendar: in the 1900
|
||||
/// system February 1900 has 29 days, so `(1900, 2, 29)` and `(1900, 3, 0)` are serial 60.
|
||||
|
|
@ -125,7 +133,7 @@ pub fn serial_from_ymd(sys: DateSystem, year: i64, month: i64, day: i64) -> Opti
|
|||
DateSystem::D1904 => first - UNIX_1904_01_01,
|
||||
};
|
||||
let serial = first.checked_add(day)?.checked_sub(1)?;
|
||||
if !(0..=2_958_465).contains(&serial) {
|
||||
if !(0..=max_serial(sys)).contains(&serial) {
|
||||
return None;
|
||||
}
|
||||
Some(serial as f64)
|
||||
|
|
@ -133,7 +141,7 @@ pub fn serial_from_ymd(sys: DateSystem, year: i64, month: i64, day: i64) -> Opti
|
|||
|
||||
/// Date/time from a serial. `None` for negative or too-large serials.
|
||||
pub fn datetime_from_serial(sys: DateSystem, serial: f64) -> Option<DateTime> {
|
||||
if !serial.is_finite() || !(0.0..2_958_466.0).contains(&serial) {
|
||||
if !serial.is_finite() || !(0.0..max_serial(sys) as f64 + 1.0).contains(&serial) {
|
||||
return None;
|
||||
}
|
||||
let mut day = serial.floor() as i64;
|
||||
|
|
@ -205,6 +213,10 @@ mod tests {
|
|||
assert_eq!(serial_from_ymd(s, 2000, 1, 1), Some(36526.0));
|
||||
assert_eq!(serial_from_ymd(s, 2026, 10, 7), Some(46302.0));
|
||||
assert_eq!(serial_from_ymd(s, 9999, 12, 31), Some(2958465.0));
|
||||
assert_eq!(serial_from_ymd(DateSystem::D1904, 9999, 12, 31), Some(2957003.0));
|
||||
assert_eq!(serial_from_ymd(DateSystem::D1904, 9999, 12, 32), None);
|
||||
assert!(datetime_from_serial(DateSystem::D1904, 2957003.5).is_some());
|
||||
assert!(datetime_from_serial(DateSystem::D1904, 2957004.0).is_none());
|
||||
assert_eq!(serial_from_ymd(s, 2020, 13, 1), serial_from_ymd(s, 2021, 1, 1));
|
||||
assert_eq!(serial_from_ymd(s, 2020, 3, 0), serial_from_ymd(s, 2020, 2, 29));
|
||||
assert_eq!(serial_from_ymd(s, 120, 1, 1), serial_from_ymd(s, 2020, 1, 1));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Rendering a value with a parsed section.
|
||||
|
||||
use gridcraft_core::DateSystem;
|
||||
use gridcraft_core::date::{MONTHS, WEEKDAYS, datetime_from_serial};
|
||||
use gridcraft_core::date::{MONTHS, WEEKDAYS, datetime_from_serial, max_serial};
|
||||
|
||||
use crate::decimal::{decimal_digits, fixed, fixed_from_digits, format_general_fit};
|
||||
use crate::parse::{DatePart, Section, Tok};
|
||||
|
|
@ -58,7 +58,7 @@ pub(crate) fn render_general(sec: &Section, v: f64, width: usize) -> Out {
|
|||
|
||||
/// Renders a date/time. `None` when the serial is outside the supported range.
|
||||
pub(crate) fn render_date(sec: &Section, v: f64, sys: DateSystem) -> Option<Out> {
|
||||
if !v.is_finite() || !(0.0..2_958_466.0).contains(&v) {
|
||||
if !v.is_finite() || !(0.0..max_serial(sys) as f64 + 1.0).contains(&v) {
|
||||
return None;
|
||||
}
|
||||
let sub_digits = sec.toks.iter().filter_map(|t| if let Tok::Date(DatePart::SubSec(n)) = t { Some(*n) } else { None }).max().unwrap_or(0).min(3);
|
||||
|
|
|
|||
|
|
@ -247,6 +247,11 @@ fn dates_and_times() {
|
|||
assert_eq!(f("m/d/yyyy", 60.0), "2/29/1900");
|
||||
assert_eq!(f("m/d/yyyy", 0.0), "1/0/1900");
|
||||
assert_eq!(format_value(&Value::Number(0.0), &NumberFormat::parse("m/d/yyyy"), DateSystem::D1904).text, "1/1/1904");
|
||||
let fmt = NumberFormat::parse("m/d/yyyy");
|
||||
assert_eq!(format_value(&Value::Number(2957003.5), &fmt, DateSystem::D1904).text, "12/31/9999");
|
||||
assert_eq!(format_value(&Value::Number(2957004.0), &fmt, DateSystem::D1904).text, "########");
|
||||
assert_eq!(format_value(&Value::Number(2958465.0), &fmt, DateSystem::D1900).text, "12/31/9999");
|
||||
assert_eq!(format_value(&Value::Number(2958466.0), &fmt, DateSystem::D1900).text, "########");
|
||||
assert_eq!(f("m/d/yyyy", -1.0), "########");
|
||||
assert_eq!(f("m/d/yyyy", 3e6), "########");
|
||||
assert_eq!(f("[$-409]m/d/yyyy", 46302.0), "10/7/2026");
|
||||
|
|
@ -339,6 +344,8 @@ fn text_fn() {
|
|||
assert_eq!(text_function(&Value::Empty, "0.00", S), Ok("0.00".into()));
|
||||
assert_eq!(text_function(&Value::Number(5.0), "", S), Ok("".into()));
|
||||
assert_eq!(text_function(&Value::Number(-1.0), "m/d/yyyy", S), Err(CellError::Value));
|
||||
assert_eq!(text_function(&Value::Number(2957003.0), "m/d/yyyy", DateSystem::D1904), Ok("12/31/9999".into()));
|
||||
assert_eq!(text_function(&Value::Number(2957004.0), "m/d/yyyy", DateSystem::D1904), Err(CellError::Value));
|
||||
assert_eq!(text_function(&Value::Bool(true), "0", S), Ok("TRUE".into()));
|
||||
// Booleans go through the text section as TRUE/FALSE (observed Excel results).
|
||||
let cases: &[(bool, &str, &str)] = &[
|
||||
|
|
|
|||
Loading…
Reference in New Issue