formualizer_eval/builtins/datetime/
date_value.rs1use super::serial::{date_to_serial, time_to_fraction};
4use crate::args::ArgSchema;
5use crate::function::Function;
6use crate::traits::{ArgumentHandle, FunctionContext};
7use chrono::NaiveDate;
8use formualizer_common::{ExcelError, LiteralValue};
9use formualizer_macros::func_caps;
10
11#[derive(Debug)]
13pub struct DateValueFn;
14
15impl Function for DateValueFn {
16 func_caps!(PURE);
17
18 fn name(&self) -> &'static str {
19 "DATEVALUE"
20 }
21
22 fn min_args(&self) -> usize {
23 1
24 }
25
26 fn arg_schema(&self) -> &'static [ArgSchema] {
27 use std::sync::LazyLock;
28 static ONE: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| vec![ArgSchema::any()]);
30 &ONE[..]
31 }
32
33 fn eval_scalar<'a, 'b>(
34 &self,
35 args: &'a [ArgumentHandle<'a, 'b>],
36 _ctx: &dyn FunctionContext,
37 ) -> Result<LiteralValue, ExcelError> {
38 let date_text = match args[0].value()?.as_ref() {
39 LiteralValue::Text(s) => s.clone(),
40 LiteralValue::Error(e) => return Err(e.clone()),
41 other => {
42 return Err(ExcelError::new_value()
43 .with_message(format!("DATEVALUE expects text, got {other:?}")));
44 }
45 };
46
47 let formats = [
50 "%Y-%m-%d", "%m/%d/%Y", "%d/%m/%Y", "%Y/%m/%d", "%B %d, %Y", "%b %d, %Y", "%d-%b-%Y", "%d %B %Y", ];
59
60 for fmt in &formats {
61 if let Ok(date) = NaiveDate::parse_from_str(&date_text, fmt) {
62 return Ok(LiteralValue::Number(date_to_serial(&date)));
63 }
64 }
65
66 Err(ExcelError::new_value()
67 .with_message("DATEVALUE could not parse date text in supported formats"))
68 }
69}
70
71#[derive(Debug)]
73pub struct TimeValueFn;
74
75impl Function for TimeValueFn {
76 func_caps!(PURE);
77
78 fn name(&self) -> &'static str {
79 "TIMEVALUE"
80 }
81
82 fn min_args(&self) -> usize {
83 1
84 }
85
86 fn arg_schema(&self) -> &'static [ArgSchema] {
87 use std::sync::LazyLock;
88 static ONE: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| vec![ArgSchema::any()]);
89 &ONE[..]
90 }
91
92 fn eval_scalar<'a, 'b>(
93 &self,
94 args: &'a [ArgumentHandle<'a, 'b>],
95 _ctx: &dyn FunctionContext,
96 ) -> Result<LiteralValue, ExcelError> {
97 let time_text = match args[0].value()?.as_ref() {
98 LiteralValue::Text(s) => s.clone(),
99 LiteralValue::Error(e) => return Err(e.clone()),
100 other => {
101 return Err(ExcelError::new_value()
102 .with_message(format!("TIMEVALUE expects text, got {other:?}")));
103 }
104 };
105
106 let formats = [
108 "%H:%M:%S", "%H:%M", "%I:%M:%S %p", "%I:%M %p", ];
113
114 for fmt in &formats {
115 if let Ok(time) = chrono::NaiveTime::parse_from_str(&time_text, fmt) {
116 return Ok(LiteralValue::Number(time_to_fraction(&time)));
117 }
118 }
119
120 Err(ExcelError::new_value()
121 .with_message("TIMEVALUE could not parse time text in supported formats"))
122 }
123}
124
125pub fn register_builtins() {
126 use std::sync::Arc;
127 crate::function_registry::register_function(Arc::new(DateValueFn));
128 crate::function_registry::register_function(Arc::new(TimeValueFn));
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::test_workbook::TestWorkbook;
135 use formualizer_parse::parser::{ASTNode, ASTNodeType};
136 use std::sync::Arc;
137
138 fn lit(v: LiteralValue) -> ASTNode {
139 ASTNode::new(ASTNodeType::Literal(v), None)
140 }
141
142 #[test]
143 fn test_datevalue_formats() {
144 let wb = TestWorkbook::new().with_function(Arc::new(DateValueFn));
145 let ctx = wb.interpreter();
146 let f = ctx.context.get_function("", "DATEVALUE").unwrap();
147
148 let date_str = lit(LiteralValue::Text("2024-01-15".into()));
150 let result = f
151 .dispatch(
152 &[ArgumentHandle::new(&date_str, &ctx)],
153 &ctx.function_context(None),
154 )
155 .unwrap();
156 assert!(matches!(result, LiteralValue::Number(_)));
157
158 let date_str = lit(LiteralValue::Text("01/15/2024".into()));
160 let result = f
161 .dispatch(
162 &[ArgumentHandle::new(&date_str, &ctx)],
163 &ctx.function_context(None),
164 )
165 .unwrap();
166 assert!(matches!(result, LiteralValue::Number(_)));
167 }
168
169 #[test]
170 fn test_timevalue_formats() {
171 let wb = TestWorkbook::new().with_function(Arc::new(TimeValueFn));
172 let ctx = wb.interpreter();
173 let f = ctx.context.get_function("", "TIMEVALUE").unwrap();
174
175 let time_str = lit(LiteralValue::Text("14:30:00".into()));
177 let result = f
178 .dispatch(
179 &[ArgumentHandle::new(&time_str, &ctx)],
180 &ctx.function_context(None),
181 )
182 .unwrap();
183 match result {
184 LiteralValue::Number(n) => {
185 assert!((n - 0.6041666667).abs() < 1e-9);
187 }
188 _ => panic!("TIMEVALUE should return a number"),
189 }
190
191 let time_str = lit(LiteralValue::Text("02:30 PM".into()));
193 let result = f
194 .dispatch(
195 &[ArgumentHandle::new(&time_str, &ctx)],
196 &ctx.function_context(None),
197 )
198 .unwrap();
199 match result {
200 LiteralValue::Number(n) => {
201 assert!((n - 0.6041666667).abs() < 1e-9);
202 }
203 _ => panic!("TIMEVALUE should return a number"),
204 }
205 }
206}