#![allow(clippy::unwrap_used)]
use crate::test::util::new_empty_model;
const MIDNIGHT: &str = "0"; const NOON: &str = "0.5"; const TIME_14_30: &str = "0.604166667"; const TIME_14_30_45: &str = "0.6046875"; const TIME_14_30_59: &str = "0.604849537"; const TIME_23_59_59: &str = "0.999988426";
const TIME_2_24_AM: &str = "0.1"; const TIME_2_PM: &str = "0.583333333"; const TIME_6_45_PM: &str = "0.78125"; const TIME_6_35_AM: &str = "0.274305556"; const TIME_2_30_AM: &str = "0.104166667"; const TIME_1_AM: &str = "0.041666667"; const TIME_9_PM: &str = "0.875"; const TIME_2_AM: &str = "0.083333333"; const TIME_00_00_01: &str = "0.000011574";
fn test_time_expressions<'a>(expressions: &[(&str, &str)]) -> crate::model::Model<'a> {
let mut model = new_empty_model();
for (cell, formula) in expressions {
model._set(cell, formula);
}
model.evaluate();
model
}
fn test_component_extraction(time_value: &str) -> (String, String, String) {
let model = test_time_expressions(&[
("A1", &format!("=HOUR({time_value})")),
("B1", &format!("=MINUTE({time_value})")),
("C1", &format!("=SECOND({time_value})")),
]);
(
model._get_text("A1").to_string(),
model._get_text("B1").to_string(),
model._get_text("C1").to_string(),
)
}
#[test]
fn test_excel_timevalue_compatibility() {
let model = test_time_expressions(&[
("A1", "=TIMEVALUE(\"2:24 AM\")"), ("A2", "=TIMEVALUE(\"2 PM\")"), ("A3", "=TIMEVALUE(\"6:45 PM\")"), ("A4", "=TIMEVALUE(\"18:45\")"), ("B1", "=TIMEVALUE(\"22-Aug-2011 6:35 AM\")"), ("B2", "=TIMEVALUE(\"2023-01-01 14:30:00\")"), ("C1", "=TIMEVALUE(\"12:00 AM\")"), ("C2", "=TIMEVALUE(\"12:00 PM\")"), ("C3", "=TIMEVALUE(\"11:59:59 PM\")"), ("D1", "=TIMEVALUE(\"1 AM\")"), ("D2", "=TIMEVALUE(\"9 PM\")"), ("D3", "=TIMEVALUE(\"12 AM\")"), ("D4", "=TIMEVALUE(\"12 PM\")"), ]);
assert_eq!(model._get_text("A1"), *TIME_2_24_AM); assert_eq!(model._get_text("A2"), *TIME_2_PM); assert_eq!(model._get_text("A3"), *TIME_6_45_PM); assert_eq!(model._get_text("A4"), *TIME_6_45_PM);
assert_eq!(model._get_text("B1"), *TIME_6_35_AM); assert_eq!(model._get_text("B2"), *TIME_14_30);
assert_eq!(model._get_text("C1"), *MIDNIGHT); assert_eq!(model._get_text("C2"), *NOON); assert_eq!(model._get_text("C3"), *TIME_23_59_59);
assert_eq!(model._get_text("D1"), *TIME_1_AM); assert_eq!(model._get_text("D2"), *TIME_9_PM); assert_eq!(model._get_text("D3"), *MIDNIGHT); assert_eq!(model._get_text("D4"), *NOON); }
#[test]
fn test_time_function_basic_cases() {
let model = test_time_expressions(&[
("A1", "=TIME(0,0,0)"), ("A2", "=TIME(12,0,0)"), ("A3", "=TIME(14,30,0)"), ("A4", "=TIME(23,59,59)"), ]);
assert_eq!(model._get_text("A1"), *MIDNIGHT);
assert_eq!(model._get_text("A2"), *NOON);
assert_eq!(model._get_text("A3"), *TIME_14_30);
assert_eq!(model._get_text("A4"), *TIME_23_59_59);
}
#[test]
fn test_time_function_normalization() {
let model = test_time_expressions(&[
("A1", "=TIME(25,0,0)"), ("A2", "=TIME(48,0,0)"), ("A3", "=TIME(0,90,0)"), ("A4", "=TIME(0,0,90)"), ("A5", "=TIME(14.9,30.9,59.9)"), ]);
assert_eq!(model._get_text("A1"), *TIME_1_AM); assert_eq!(model._get_text("A2"), *MIDNIGHT); assert_eq!(model._get_text("A3"), *"0.0625"); assert_eq!(model._get_text("A4"), *"0.001041667"); assert_eq!(model._get_text("A5"), *TIME_14_30_59); }
#[test]
fn test_time_function_precision_edge_cases() {
let model = test_time_expressions(&[
("A1", "=TIME(14,30,45.999)"), ("A2", "=SECOND(TIME(14,30,45.999))"), ("B1", "=TIME(999,999,999)"), ("B2", "=HOUR(999.5)"), ("B3", "=MINUTE(999.75)"), ("C1", "=TIME(24,60,60)"), ("C2", "=HOUR(0.999999999)"), ("C3", "=MINUTE(0.999999999)"), ("C4", "=SECOND(0.999999999)"), ("D1", "=TIME(23,59,59.999)"), ("D2", "=TIME(0,0,0.001)"), ]);
assert_eq!(model._get_text("A2"), *"45");
assert_eq!(model._get_text("B2"), *"12");
assert_eq!(model._get_text("C1"), *"0.042361111"); assert_eq!(model._get_text("C2"), *"23");
let result_d1 = model._get_text("D1").parse::<f64>().unwrap();
assert!(result_d1 < 1.0 && result_d1 > 0.999); }
#[test]
fn test_time_function_errors() {
let model = test_time_expressions(&[
("A1", "=TIME()"), ("A2", "=TIME(12)"), ("A3", "=TIME(12,30,0,0)"), ("B1", "=TIME(-1,0,0)"), ("B2", "=TIME(0,-1,0)"), ("B3", "=TIME(0,0,-1)"), ]);
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("B1"), *"#NUM!");
assert_eq!(model._get_text("B2"), *"#NUM!");
assert_eq!(model._get_text("B3"), *"#NUM!");
}
#[test]
fn test_timevalue_function_formats() {
let model = test_time_expressions(&[
("A1", "=TIMEVALUE(\"14:30\")"),
("A2", "=TIMEVALUE(\"14:30:45\")"),
("A3", "=TIMEVALUE(\"00:00:00\")"),
("B1", "=TIMEVALUE(\"2:30 PM\")"),
("B2", "=TIMEVALUE(\"2:30 AM\")"),
("B3", "=TIMEVALUE(\"12:00 PM\")"), ("B4", "=TIMEVALUE(\"12:00 AM\")"), ("B5", "=TIMEVALUE(\"2 PM\")"),
("B6", "=TIMEVALUE(\"2 AM\")"),
("B7", "=TIMEVALUE(\"2 pm\")"),
("B8", "=TIMEVALUE(\"2 pM\")"),
("B9", "=TIMEVALUE(\"2 Am\")"),
("B10", "=TIMEVALUE(\"2 am\")"),
("C1", "=TIMEVALUE(\"2023-01-01 14:30:00\")"),
("C2", "=TIMEVALUE(\"2023-01-01T14:30:00\")"),
("D1", "=TIMEVALUE(\" 14:30 \")"),
("D2", "=TIMEVALUE(\"14:30 \")"),
("D3", "=TIMEVALUE(\" 14:30\")"),
("E1", "=TIMEVALUE(\"2 pm \")"),
("E2", "=TIMEVALUE(\" 2 pm\")"),
("E3", "=TIMEVALUE(\"2 am\")"),
("E4", "=TIMEVALUE(\" 2 am \")"),
("F1", "=TIMEVALUE(\"2 am\")"),
("F2", "=TIMEVALUE(\"2 PM.\")"),
("F3", "=TIMEVALUE(\"2 p.m.\")"),
]);
assert_eq!(model._get_text("A1"), *TIME_14_30);
assert_eq!(model._get_text("A2"), *TIME_14_30_45);
assert_eq!(model._get_text("A3"), *MIDNIGHT);
assert_eq!(model._get_text("B1"), *TIME_14_30); assert_eq!(model._get_text("B2"), *TIME_2_30_AM); assert_eq!(model._get_text("B3"), *NOON); assert_eq!(model._get_text("B4"), *MIDNIGHT);
assert_eq!(model._get_text("B5"), *TIME_2_PM); assert_eq!(model._get_text("B6"), *TIME_2_AM); assert_eq!(model._get_text("B7"), *TIME_2_PM); assert_eq!(model._get_text("B8"), *TIME_2_PM); assert_eq!(model._get_text("B9"), *TIME_2_AM); assert_eq!(model._get_text("B10"), *TIME_2_AM);
assert_eq!(model._get_text("C1"), *TIME_14_30);
assert_eq!(model._get_text("C2"), *TIME_14_30);
assert_eq!(model._get_text("D1"), *TIME_14_30);
assert_eq!(model._get_text("D2"), *TIME_14_30);
assert_eq!(model._get_text("D3"), *TIME_14_30);
assert_eq!(model._get_text("E1"), *TIME_2_PM); assert_eq!(model._get_text("E2"), *TIME_2_PM); assert_eq!(model._get_text("E3"), *TIME_2_AM); assert_eq!(model._get_text("E4"), *TIME_2_AM);
assert_eq!(model._get_text("F1"), *TIME_2_AM); assert_eq!(model._get_text("F2"), *TIME_2_PM); assert_eq!(model._get_text("F3"), *"#VALUE!"); }
#[test]
fn test_timevalue_function_errors() {
let model = test_time_expressions(&[
("A1", "=TIMEVALUE()"), ("A2", "=TIMEVALUE(\"14:30\", \"x\")"), ("B1", "=TIMEVALUE(\"invalid\")"), ("B2", "=TIMEVALUE(\"25:00\")"), ("B3", "=TIMEVALUE(\"14:70\")"), ("B4", "=TIMEVALUE(\"\")"), ("B5", "=TIMEVALUE(\"2PM\")"), ]);
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("B1"), *"#VALUE!");
assert_eq!(model._get_text("B2"), *"#VALUE!");
assert_eq!(model._get_text("B3"), *"#VALUE!");
assert_eq!(model._get_text("B4"), *"#VALUE!");
assert_eq!(model._get_text("B5"), *"#VALUE!"); }
#[test]
fn test_time_component_extraction_comprehensive() {
let test_cases = [
(MIDNIGHT, ("0", "0", "0")), (NOON, ("12", "0", "0")), (TIME_14_30, ("14", "30", "0")), (TIME_23_59_59, ("23", "59", "59")), ];
for (time_value, expected) in test_cases {
let (hour, minute, second) = test_component_extraction(time_value);
assert_eq!(hour, expected.0, "Hour mismatch for {time_value}");
assert_eq!(minute, expected.1, "Minute mismatch for {time_value}");
assert_eq!(second, expected.2, "Second mismatch for {time_value}");
}
let (hour, minute, second) = test_component_extraction("1.5"); assert_eq!(
(hour, minute, second),
("12".to_string(), "0".to_string(), "0".to_string())
);
let (hour, minute, second) = test_component_extraction("100.604166667"); assert_eq!(
(hour, minute, second),
("14".to_string(), "30".to_string(), "0".to_string())
);
let (hour, _, _) = test_component_extraction("0.041666666"); assert_eq!(hour, "0");
let (hour, _, _) = test_component_extraction("0.041666667"); assert_eq!(hour, "1");
let (hour, _, _) = test_component_extraction("0.041666668"); assert_eq!(hour, "1");
let (hour, minute, second) = test_component_extraction("1000000.25"); assert_eq!(
(hour, minute, second),
("6".to_string(), "0".to_string(), "0".to_string())
);
}
#[test]
fn test_time_component_function_errors() {
let model = test_time_expressions(&[
("A1", "=HOUR()"), ("A2", "=MINUTE()"), ("A3", "=SECOND()"), ("A4", "=HOUR(1, 2)"), ("A5", "=MINUTE(1, 2)"), ("A6", "=SECOND(1, 2)"), ("B1", "=HOUR(-0.5)"), ("B2", "=MINUTE(-1)"), ("B3", "=SECOND(-1)"), ("B4", "=HOUR(-0.000001)"), ("B5", "=MINUTE(-0.000001)"), ("B6", "=SECOND(-0.000001)"), ]);
assert_eq!(model._get_text("A1"), *"#ERROR!");
assert_eq!(model._get_text("A2"), *"#ERROR!");
assert_eq!(model._get_text("A3"), *"#ERROR!");
assert_eq!(model._get_text("A4"), *"#ERROR!");
assert_eq!(model._get_text("A5"), *"#ERROR!");
assert_eq!(model._get_text("A6"), *"#ERROR!");
assert_eq!(model._get_text("B1"), *"#NUM!");
assert_eq!(model._get_text("B2"), *"#NUM!");
assert_eq!(model._get_text("B3"), *"#NUM!");
assert_eq!(model._get_text("B4"), *"#NUM!");
assert_eq!(model._get_text("B5"), *"#NUM!");
assert_eq!(model._get_text("B6"), *"#NUM!");
}
#[test]
fn test_time_functions_integration() {
let model = test_time_expressions(&[
("A1", "=TIME(14,30,45)"),
("A2", "=TIMEVALUE(\"14:30:45\")"),
("B1", "=HOUR(A1)"),
("B2", "=MINUTE(A1)"),
("B3", "=SECOND(A1)"),
("C1", "=HOUR(A2)"),
("C2", "=MINUTE(A2)"),
("C3", "=SECOND(A2)"),
("D1", "=TIME(14,0,0)"), ("E1", "=HOUR(D1)"), ("E2", "=MINUTE(D1)"), ("E3", "=SECOND(D1)"), ]);
assert_eq!(model._get_text("A1"), model._get_text("A2"));
assert_eq!(model._get_text("B1"), *"14");
assert_eq!(model._get_text("B2"), *"30");
assert_eq!(model._get_text("B3"), *"45");
assert_eq!(model._get_text("C1"), *"14");
assert_eq!(model._get_text("C2"), *"30");
assert_eq!(model._get_text("C3"), *"45");
assert_eq!(model._get_text("E1"), *"14");
assert_eq!(model._get_text("E2"), *"0");
assert_eq!(model._get_text("E3"), *"0");
}
#[test]
fn test_time_function_extreme_values() {
let model = test_time_expressions(&[
("A1", "=TIME(999999.9, 999999.9, 999999.9)"), ("A2", "=TIME(1e6, 1e6, 1e6)"), ("A3", "=TIME(0.000001, 0.000001, 0.000001)"), ("B1", "=HOUR(999999.999)"), ("B2", "=MINUTE(999999.999)"),
("B3", "=SECOND(999999.999)"),
("C1", "=HOUR(1.0)"),
("C2", "=MINUTE(1.0)"),
("C3", "=SECOND(1.0)"),
("D1", "=HOUR(0.999999999999)"), ("D2", "=MINUTE(0.999999999999)"),
("D3", "=SECOND(0.999999999999)"),
]);
let result_a1 = model._get_text("A1").parse::<f64>().unwrap();
assert!(
(0.0..1.0).contains(&result_a1),
"Result should be valid time fraction"
);
let hour_b1 = model._get_text("B1").parse::<i32>().unwrap();
assert!((0..=23).contains(&hour_b1), "Hour should be 0-23");
assert_eq!(model._get_text("C1"), *"0");
assert_eq!(model._get_text("C2"), *"0");
assert_eq!(model._get_text("C3"), *"0");
let hour_d1 = model._get_text("D1").parse::<i32>().unwrap();
assert!((0..=23).contains(&hour_d1), "Hour should be 0-23");
}
#[test]
fn test_timevalue_malformed_but_parseable() {
let model = test_time_expressions(&[
("A1", "=TIMEVALUE(\"14:30:00.123\")"), ("A2", "=TIMEVALUE(\"14:30:00.999\")"), ("A3", "=TIMEVALUE(\"02:30:00\")"), ("A4", "=TIMEVALUE(\"2:05:00\")"), ("B1", "=TIMEVALUE(\"11:59:59 PM\")"), ("B2", "=TIMEVALUE(\"12:00:01 AM\")"), ("B3", "=TIMEVALUE(\"12:00:01 PM\")"), ("B4", "=TIMEVALUE(\"11:59:59 AM\")"), ("C1", "=TIMEVALUE(\"2023-12-31T23:59:59\")"), ("C2", "=TIMEVALUE(\"2023-01-01 00:00:01\")"), ("D1", "=TIMEVALUE(\"24:00:00\")"), ("D2", "=TIMEVALUE(\"23:60:00\")"), ("D3", "=TIMEVALUE(\"23:59:60\")"), ]);
assert_eq!(model._get_text("A1"), *"#VALUE!");
assert_eq!(model._get_text("A2"), *"#VALUE!");
assert_eq!(model._get_text("A3"), *TIME_2_30_AM);
let result_b1 = model._get_text("B1").parse::<f64>().unwrap();
assert!(
result_b1 > 0.99 && result_b1 < 1.0,
"11:59:59 PM should be very close to 1.0"
);
let result_b2 = model._get_text("B2").parse::<f64>().unwrap();
assert!(
result_b2 > 0.0 && result_b2 < 0.01,
"12:00:01 AM should be very close to 0.0"
);
assert_eq!(model._get_text("C1"), *TIME_23_59_59); assert_eq!(model._get_text("C2"), *TIME_00_00_01);
assert_eq!(model._get_text("D1"), *"0"); assert_eq!(model._get_text("D2"), *"0"); assert_eq!(model._get_text("D3"), *"0"); }
#[test]
fn test_performance_stress_with_extreme_values() {
let model = test_time_expressions(&[
("A1", "=TIME(2147483647, 0, 0)"), ("A2", "=TIME(0, 2147483647, 0)"), ("A3", "=TIME(0, 0, 2147483647)"), ("B1", "=HOUR(1e15)"), ("B2", "=MINUTE(1e15)"),
("B3", "=SECOND(1e15)"),
("C1", "=HOUR(1.7976931348623157e+308)"), ("C2", "=HOUR(2.2250738585072014e-308)"), ("D1", "=TIME(1000000, 1000000, 1000000)"), ("D2", "=HOUR(D1)"), ("D3", "=MINUTE(D1)"),
("D4", "=SECOND(D1)"),
]);
for cell in ["A1", "A2", "A3", "B1", "B2", "B3", "D1", "D2", "D3", "D4"] {
let result = model._get_text(cell);
assert!(
result != *"#ERROR!" && result != *"#NUM!" && result != *"#VALUE!",
"Cell {cell} should not error with extreme values: {result}",
);
}
let hour_b1 = model._get_text("B1").parse::<i32>().unwrap();
let minute_b2 = model._get_text("B2").parse::<i32>().unwrap();
let second_b3 = model._get_text("B3").parse::<i32>().unwrap();
assert!((0..=23).contains(&hour_b1));
assert!((0..=59).contains(&minute_b2));
assert!((0..=59).contains(&second_b3));
let time_d1 = model._get_text("D1").parse::<f64>().unwrap();
assert!(
(0.0..1.0).contains(&time_d1),
"TIME result should be valid fraction"
);
}