formualizer_eval/builtins/datetime/
date_parts.rs1use super::serial::{serial_to_date, serial_to_datetime};
4use crate::args::ArgSchema;
5use crate::function::Function;
6use crate::traits::{ArgumentHandle, FunctionContext};
7use chrono::{Datelike, Timelike};
8use formualizer_common::{ExcelError, LiteralValue};
9use formualizer_macros::func_caps;
10
11fn coerce_to_serial(arg: &ArgumentHandle) -> Result<f64, ExcelError> {
12 let v = arg.value()?;
13 match v.as_ref() {
14 LiteralValue::Number(f) => Ok(*f),
15 LiteralValue::Int(i) => Ok(*i as f64),
16 LiteralValue::Text(s) => s.parse::<f64>().map_err(|_| {
17 ExcelError::new_value().with_message("Date/time serial is not a valid number")
18 }),
19 LiteralValue::Boolean(b) => Ok(if *b { 1.0 } else { 0.0 }),
20 LiteralValue::Empty => Ok(0.0),
21 LiteralValue::Error(e) => Err(e.clone()),
22 _ => Err(ExcelError::new_value()
23 .with_message("Date/time functions expect numeric or text-numeric serials")),
24 }
25}
26
27#[derive(Debug)]
29pub struct YearFn;
30
31impl Function for YearFn {
32 func_caps!(PURE);
33
34 fn name(&self) -> &'static str {
35 "YEAR"
36 }
37
38 fn min_args(&self) -> usize {
39 1
40 }
41
42 fn arg_schema(&self) -> &'static [ArgSchema] {
43 use std::sync::LazyLock;
44 static ONE: LazyLock<Vec<ArgSchema>> =
45 LazyLock::new(|| vec![ArgSchema::number_lenient_scalar()]);
46 &ONE[..]
47 }
48
49 fn eval_scalar<'a, 'b>(
50 &self,
51 args: &'a [ArgumentHandle<'a, 'b>],
52 _ctx: &dyn FunctionContext,
53 ) -> Result<LiteralValue, ExcelError> {
54 let serial = coerce_to_serial(&args[0])?;
55 let date = serial_to_date(serial)?;
56 Ok(LiteralValue::Int(date.year() as i64))
57 }
58}
59
60#[derive(Debug)]
62pub struct MonthFn;
63
64impl Function for MonthFn {
65 func_caps!(PURE);
66
67 fn name(&self) -> &'static str {
68 "MONTH"
69 }
70
71 fn min_args(&self) -> usize {
72 1
73 }
74
75 fn arg_schema(&self) -> &'static [ArgSchema] {
76 use std::sync::LazyLock;
77 static ONE: LazyLock<Vec<ArgSchema>> =
78 LazyLock::new(|| vec![ArgSchema::number_lenient_scalar()]);
79 &ONE[..]
80 }
81
82 fn eval_scalar<'a, 'b>(
83 &self,
84 args: &'a [ArgumentHandle<'a, 'b>],
85 _ctx: &dyn FunctionContext,
86 ) -> Result<LiteralValue, ExcelError> {
87 let serial = coerce_to_serial(&args[0])?;
88 let date = serial_to_date(serial)?;
89 Ok(LiteralValue::Int(date.month() as i64))
90 }
91}
92
93#[derive(Debug)]
95pub struct DayFn;
96
97impl Function for DayFn {
98 func_caps!(PURE);
99
100 fn name(&self) -> &'static str {
101 "DAY"
102 }
103
104 fn min_args(&self) -> usize {
105 1
106 }
107
108 fn arg_schema(&self) -> &'static [ArgSchema] {
109 use std::sync::LazyLock;
110 static ONE: LazyLock<Vec<ArgSchema>> =
111 LazyLock::new(|| vec![ArgSchema::number_lenient_scalar()]);
112 &ONE[..]
113 }
114
115 fn eval_scalar<'a, 'b>(
116 &self,
117 args: &'a [ArgumentHandle<'a, 'b>],
118 _ctx: &dyn FunctionContext,
119 ) -> Result<LiteralValue, ExcelError> {
120 let serial = coerce_to_serial(&args[0])?;
121 let date = serial_to_date(serial)?;
122 Ok(LiteralValue::Int(date.day() as i64))
123 }
124}
125
126#[derive(Debug)]
128pub struct HourFn;
129
130impl Function for HourFn {
131 func_caps!(PURE);
132
133 fn name(&self) -> &'static str {
134 "HOUR"
135 }
136
137 fn min_args(&self) -> usize {
138 1
139 }
140
141 fn arg_schema(&self) -> &'static [ArgSchema] {
142 use std::sync::LazyLock;
143 static ONE: LazyLock<Vec<ArgSchema>> =
144 LazyLock::new(|| vec![ArgSchema::number_lenient_scalar()]);
145 &ONE[..]
146 }
147
148 fn eval_scalar<'a, 'b>(
149 &self,
150 args: &'a [ArgumentHandle<'a, 'b>],
151 _ctx: &dyn FunctionContext,
152 ) -> Result<LiteralValue, ExcelError> {
153 let serial = coerce_to_serial(&args[0])?;
154
155 let time_fraction = if serial < 1.0 { serial } else { serial.fract() };
157
158 let hours = (time_fraction * 24.0) as i64;
160 Ok(LiteralValue::Int(hours))
161 }
162}
163
164#[derive(Debug)]
166pub struct MinuteFn;
167
168impl Function for MinuteFn {
169 func_caps!(PURE);
170
171 fn name(&self) -> &'static str {
172 "MINUTE"
173 }
174
175 fn min_args(&self) -> usize {
176 1
177 }
178
179 fn arg_schema(&self) -> &'static [ArgSchema] {
180 use std::sync::LazyLock;
181 static ONE: LazyLock<Vec<ArgSchema>> =
182 LazyLock::new(|| vec![ArgSchema::number_lenient_scalar()]);
183 &ONE[..]
184 }
185
186 fn eval_scalar<'a, 'b>(
187 &self,
188 args: &'a [ArgumentHandle<'a, 'b>],
189 _ctx: &dyn FunctionContext,
190 ) -> Result<LiteralValue, ExcelError> {
191 let serial = coerce_to_serial(&args[0])?;
192
193 let datetime = serial_to_datetime(serial)?;
195 Ok(LiteralValue::Int(datetime.minute() as i64))
196 }
197}
198
199#[derive(Debug)]
201pub struct SecondFn;
202
203impl Function for SecondFn {
204 func_caps!(PURE);
205
206 fn name(&self) -> &'static str {
207 "SECOND"
208 }
209
210 fn min_args(&self) -> usize {
211 1
212 }
213
214 fn arg_schema(&self) -> &'static [ArgSchema] {
215 use std::sync::LazyLock;
216 static ONE: LazyLock<Vec<ArgSchema>> =
217 LazyLock::new(|| vec![ArgSchema::number_lenient_scalar()]);
218 &ONE[..]
219 }
220
221 fn eval_scalar<'a, 'b>(
222 &self,
223 args: &'a [ArgumentHandle<'a, 'b>],
224 _ctx: &dyn FunctionContext,
225 ) -> Result<LiteralValue, ExcelError> {
226 let serial = coerce_to_serial(&args[0])?;
227
228 let datetime = serial_to_datetime(serial)?;
230 Ok(LiteralValue::Int(datetime.second() as i64))
231 }
232}
233
234pub fn register_builtins() {
235 use std::sync::Arc;
236 crate::function_registry::register_function(Arc::new(YearFn));
237 crate::function_registry::register_function(Arc::new(MonthFn));
238 crate::function_registry::register_function(Arc::new(DayFn));
239 crate::function_registry::register_function(Arc::new(HourFn));
240 crate::function_registry::register_function(Arc::new(MinuteFn));
241 crate::function_registry::register_function(Arc::new(SecondFn));
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::test_workbook::TestWorkbook;
248 use formualizer_parse::parser::{ASTNode, ASTNodeType};
249 use std::sync::Arc;
250
251 fn lit(v: LiteralValue) -> ASTNode {
252 ASTNode::new(ASTNodeType::Literal(v), None)
253 }
254
255 #[test]
256 fn test_year_month_day() {
257 let wb = TestWorkbook::new()
258 .with_function(Arc::new(YearFn))
259 .with_function(Arc::new(MonthFn))
260 .with_function(Arc::new(DayFn));
261 let ctx = wb.interpreter();
262
263 let serial = lit(LiteralValue::Number(44927.0));
266
267 let year_fn = ctx.context.get_function("", "YEAR").unwrap();
268 let result = year_fn
269 .dispatch(
270 &[ArgumentHandle::new(&serial, &ctx)],
271 &ctx.function_context(None),
272 )
273 .unwrap();
274 assert_eq!(result, LiteralValue::Int(2023));
275
276 let month_fn = ctx.context.get_function("", "MONTH").unwrap();
277 let result = month_fn
278 .dispatch(
279 &[ArgumentHandle::new(&serial, &ctx)],
280 &ctx.function_context(None),
281 )
282 .unwrap();
283 assert_eq!(result, LiteralValue::Int(1));
284
285 let day_fn = ctx.context.get_function("", "DAY").unwrap();
286 let result = day_fn
287 .dispatch(
288 &[ArgumentHandle::new(&serial, &ctx)],
289 &ctx.function_context(None),
290 )
291 .unwrap();
292 assert_eq!(result, LiteralValue::Int(1));
293 }
294
295 #[test]
296 fn test_hour_minute_second() {
297 let wb = TestWorkbook::new()
298 .with_function(Arc::new(HourFn))
299 .with_function(Arc::new(MinuteFn))
300 .with_function(Arc::new(SecondFn));
301 let ctx = wb.interpreter();
302
303 let serial = lit(LiteralValue::Number(0.5));
305
306 let hour_fn = ctx.context.get_function("", "HOUR").unwrap();
307 let result = hour_fn
308 .dispatch(
309 &[ArgumentHandle::new(&serial, &ctx)],
310 &ctx.function_context(None),
311 )
312 .unwrap();
313 assert_eq!(result, LiteralValue::Int(12));
314
315 let minute_fn = ctx.context.get_function("", "MINUTE").unwrap();
316 let result = minute_fn
317 .dispatch(
318 &[ArgumentHandle::new(&serial, &ctx)],
319 &ctx.function_context(None),
320 )
321 .unwrap();
322 assert_eq!(result, LiteralValue::Int(0));
323
324 let second_fn = ctx.context.get_function("", "SECOND").unwrap();
325 let result = second_fn
326 .dispatch(
327 &[ArgumentHandle::new(&serial, &ctx)],
328 &ctx.function_context(None),
329 )
330 .unwrap();
331 assert_eq!(result, LiteralValue::Int(0));
332
333 let time_serial = lit(LiteralValue::Number(0.6463541667));
335
336 let hour_result = hour_fn
337 .dispatch(
338 &[ArgumentHandle::new(&time_serial, &ctx)],
339 &ctx.function_context(None),
340 )
341 .unwrap();
342 assert_eq!(hour_result, LiteralValue::Int(15));
343
344 let minute_result = minute_fn
345 .dispatch(
346 &[ArgumentHandle::new(&time_serial, &ctx)],
347 &ctx.function_context(None),
348 )
349 .unwrap();
350 assert_eq!(minute_result, LiteralValue::Int(30));
351
352 let second_result = second_fn
353 .dispatch(
354 &[ArgumentHandle::new(&time_serial, &ctx)],
355 &ctx.function_context(None),
356 )
357 .unwrap();
358 assert_eq!(second_result, LiteralValue::Int(45));
359 }
360}