Skip to main content

reifydb_function/datetime/
age.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (c) 2025 ReifyDB
3
4use reifydb_core::value::column::data::ColumnData;
5use reifydb_type::value::{container::temporal::TemporalContainer, date::Date, duration::Duration, r#type::Type};
6
7use crate::{ScalarFunction, ScalarFunctionContext, error::ScalarFunctionError, propagate_options};
8
9pub struct DateTimeAge;
10
11impl DateTimeAge {
12	pub fn new() -> Self {
13		Self
14	}
15}
16
17impl ScalarFunction for DateTimeAge {
18	fn scalar(&self, ctx: ScalarFunctionContext) -> crate::error::ScalarFunctionResult<ColumnData> {
19		if let Some(result) = propagate_options(self, &ctx) {
20			return result;
21		}
22		let columns = ctx.columns;
23		let row_count = ctx.row_count;
24
25		if columns.len() != 2 {
26			return Err(ScalarFunctionError::ArityMismatch {
27				function: ctx.fragment.clone(),
28				expected: 2,
29				actual: columns.len(),
30			});
31		}
32
33		let col1 = columns.get(0).unwrap();
34		let col2 = columns.get(1).unwrap();
35
36		match (col1.data(), col2.data()) {
37			(ColumnData::DateTime(container1), ColumnData::DateTime(container2)) => {
38				let mut container = TemporalContainer::with_capacity(row_count);
39
40				for i in 0..row_count {
41					match (container1.get(i), container2.get(i)) {
42						(Some(dt1), Some(dt2)) => {
43							// Extract time nanos since midnight
44							let nanos1 = dt1.time().to_nanos_since_midnight() as i64;
45							let nanos2 = dt2.time().to_nanos_since_midnight() as i64;
46							let mut nanos_diff = nanos1 - nanos2;
47							let mut days_borrow: i32 = 0;
48
49							if nanos_diff < 0 {
50								days_borrow = 1;
51								nanos_diff += 86_400_000_000_000;
52							}
53
54							// Extract date parts
55							let date1 = dt1.date();
56							let date2 = dt2.date();
57
58							let y1 = date1.year();
59							let m1 = date1.month() as i32;
60							let day1 = date1.day() as i32;
61
62							let y2 = date2.year();
63							let m2 = date2.month() as i32;
64							let day2 = date2.day() as i32;
65
66							let mut years = y1 - y2;
67							let mut months = m1 - m2;
68							let mut days = day1 - day2 - days_borrow;
69
70							if days < 0 {
71								months -= 1;
72								let borrow_month = if m1 - 1 < 1 {
73									12
74								} else {
75									m1 - 1
76								};
77								let borrow_year = if m1 - 1 < 1 {
78									y1 - 1
79								} else {
80									y1
81								};
82								days += Date::days_in_month(
83									borrow_year,
84									borrow_month as u32,
85								) as i32;
86							}
87
88							if months < 0 {
89								years -= 1;
90								months += 12;
91							}
92
93							let total_months = years * 12 + months;
94							container.push(Duration::new(total_months, days, nanos_diff));
95						}
96						_ => container.push_default(),
97					}
98				}
99
100				Ok(ColumnData::Duration(container))
101			}
102			(ColumnData::DateTime(_), other) => Err(ScalarFunctionError::InvalidArgumentType {
103				function: ctx.fragment.clone(),
104				argument_index: 1,
105				expected: vec![Type::DateTime],
106				actual: other.get_type(),
107			}),
108			(other, _) => Err(ScalarFunctionError::InvalidArgumentType {
109				function: ctx.fragment.clone(),
110				argument_index: 0,
111				expected: vec![Type::DateTime],
112				actual: other.get_type(),
113			}),
114		}
115	}
116
117	fn return_type(&self, _input_types: &[Type]) -> Type {
118		Type::Duration
119	}
120}