ganit_core/eval/functions/date/datedif/
mod.rs1use chrono::{Datelike, NaiveDate};
2use crate::eval::coercion::to_number;
3use crate::eval::functions::check_arity;
4use crate::eval::functions::date::serial::serial_to_date;
5use crate::types::{ErrorKind, Value};
6
7pub fn datedif_fn(args: &[Value]) -> Value {
9 if let Some(e) = check_arity(args, 3, 3) {
10 return e;
11 }
12 let start_serial = match to_number(args[0].clone()) { Ok(n) => n, Err(e) => return e };
13 let end_serial = match to_number(args[1].clone()) { Ok(n) => n, Err(e) => return e };
14
15 let unit = match &args[2] {
16 Value::Text(s) => s.to_uppercase(),
17 _ => return Value::Error(ErrorKind::Num),
18 };
19
20 let start = match serial_to_date(start_serial) {
21 Some(d) => d,
22 None => return Value::Error(ErrorKind::Num),
23 };
24 let end = match serial_to_date(end_serial) {
25 Some(d) => d,
26 None => return Value::Error(ErrorKind::Num),
27 };
28
29 if start > end {
30 return Value::Error(ErrorKind::Num);
31 }
32
33 match unit.as_str() {
34 "Y" => {
35 let years = end.year() - start.year();
36 let had_anniversary = (end.month(), end.day()) >= (start.month(), start.day());
37 Value::Number(if had_anniversary { years } else { years - 1 } as f64)
38 }
39 "M" => {
40 let months = (end.year() - start.year()) * 12
41 + (end.month() as i32 - start.month() as i32);
42 let had_day_pass = end.day() >= start.day();
43 Value::Number(if had_day_pass { months } else { months - 1 } as f64)
44 }
45 "D" => {
46 Value::Number((end - start).num_days() as f64)
47 }
48 "MD" => {
49 let total_months = (end.year() - start.year()) * 12
51 + (end.month() as i32 - start.month() as i32);
52 let had_day_pass = end.day() >= start.day();
53 let complete_months = if had_day_pass { total_months } else { total_months - 1 };
54 let adjusted_start = add_months(start, complete_months);
55 Value::Number((end - adjusted_start).num_days() as f64)
56 }
57 "YM" => {
58 let total_months = (end.year() - start.year()) * 12
59 + (end.month() as i32 - start.month() as i32);
60 let had_day_pass = end.day() >= start.day();
61 let complete_months = if had_day_pass { total_months } else { total_months - 1 };
62 Value::Number((complete_months % 12) as f64)
63 }
64 "YD" => {
65 let same_year_end = NaiveDate::from_ymd_opt(start.year(), end.month(), end.day())
67 .or_else(|| NaiveDate::from_ymd_opt(start.year(), end.month() + 1, 1))
68 .unwrap();
69 let days = if same_year_end >= start {
70 (same_year_end - start).num_days()
71 } else {
72 let next_year_end = NaiveDate::from_ymd_opt(start.year() + 1, end.month(), end.day())
73 .or_else(|| NaiveDate::from_ymd_opt(start.year() + 1, end.month() + 1, 1))
74 .unwrap();
75 (next_year_end - start).num_days()
76 };
77 Value::Number(days as f64)
78 }
79 _ => Value::Error(ErrorKind::Num),
80 }
81}
82
83fn add_months(date: NaiveDate, months: i32) -> NaiveDate {
85 let total_months = date.year() * 12 + date.month() as i32 - 1 + months;
86 let year = total_months / 12;
87 let month = (total_months % 12 + 1) as u32;
88 NaiveDate::from_ymd_opt(year, month, date.day())
89 .or_else(|| NaiveDate::from_ymd_opt(year, month + 1, 1))
90 .unwrap()
91}
92
93#[cfg(test)]
94mod tests;