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)]
41pub struct DateValueFn;
42
43impl Function for DateValueFn {
54 func_caps!(PURE);
55
56 fn name(&self) -> &'static str {
57 "DATEVALUE"
58 }
59
60 fn min_args(&self) -> usize {
61 1
62 }
63
64 fn arg_schema(&self) -> &'static [ArgSchema] {
65 use std::sync::LazyLock;
66 static ONE: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| vec![ArgSchema::any()]);
68 &ONE[..]
69 }
70
71 fn eval<'a, 'b, 'c>(
72 &self,
73 args: &'c [ArgumentHandle<'a, 'b>],
74 _ctx: &dyn FunctionContext<'b>,
75 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
76 let date_text = match args[0].value()?.into_literal() {
77 LiteralValue::Text(s) => s,
78 LiteralValue::Error(e) => {
79 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
80 }
81 other => {
82 return Err(ExcelError::new_value()
83 .with_message(format!("DATEVALUE expects text, got {other:?}")));
84 }
85 };
86
87 let formats = [
90 "%Y-%m-%d", "%m/%d/%Y", "%d/%m/%Y", "%Y/%m/%d", "%B %d, %Y", "%b %d, %Y", "%d-%b-%Y", "%d %B %Y", ];
99
100 for fmt in &formats {
101 if let Ok(date) = NaiveDate::parse_from_str(&date_text, fmt) {
102 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
103 date_to_serial(&date),
104 )));
105 }
106 }
107
108 Err(ExcelError::new_value()
109 .with_message("DATEVALUE could not parse date text in supported formats"))
110 }
111}
112
113#[derive(Debug)]
143pub struct TimeValueFn;
144
145impl Function for TimeValueFn {
156 func_caps!(PURE);
157
158 fn name(&self) -> &'static str {
159 "TIMEVALUE"
160 }
161
162 fn min_args(&self) -> usize {
163 1
164 }
165
166 fn arg_schema(&self) -> &'static [ArgSchema] {
167 use std::sync::LazyLock;
168 static ONE: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| vec![ArgSchema::any()]);
169 &ONE[..]
170 }
171
172 fn eval<'a, 'b, 'c>(
173 &self,
174 args: &'c [ArgumentHandle<'a, 'b>],
175 _ctx: &dyn FunctionContext<'b>,
176 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
177 let time_text = match args[0].value()?.into_literal() {
178 LiteralValue::Text(s) => s,
179 LiteralValue::Error(e) => {
180 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
181 }
182 other => {
183 return Err(ExcelError::new_value()
184 .with_message(format!("TIMEVALUE expects text, got {other:?}")));
185 }
186 };
187
188 let formats = [
190 "%H:%M:%S", "%H:%M", "%I:%M:%S %p", "%I:%M %p", ];
195
196 for fmt in &formats {
197 if let Ok(time) = chrono::NaiveTime::parse_from_str(&time_text, fmt) {
198 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
199 time_to_fraction(&time),
200 )));
201 }
202 }
203
204 Err(ExcelError::new_value()
205 .with_message("TIMEVALUE could not parse time text in supported formats"))
206 }
207}
208
209pub fn register_builtins() {
210 use std::sync::Arc;
211 crate::function_registry::register_function(Arc::new(DateValueFn));
212 crate::function_registry::register_function(Arc::new(TimeValueFn));
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::test_workbook::TestWorkbook;
219 use formualizer_parse::parser::{ASTNode, ASTNodeType};
220 use std::sync::Arc;
221
222 fn lit(v: LiteralValue) -> ASTNode {
223 ASTNode::new(ASTNodeType::Literal(v), None)
224 }
225
226 #[test]
227 fn test_datevalue_formats() {
228 let wb = TestWorkbook::new().with_function(Arc::new(DateValueFn));
229 let ctx = wb.interpreter();
230 let f = ctx.context.get_function("", "DATEVALUE").unwrap();
231
232 let date_str = lit(LiteralValue::Text("2024-01-15".into()));
234 let result = f
235 .dispatch(
236 &[ArgumentHandle::new(&date_str, &ctx)],
237 &ctx.function_context(None),
238 )
239 .unwrap()
240 .into_literal();
241 assert!(matches!(result, LiteralValue::Number(_)));
242
243 let date_str = lit(LiteralValue::Text("01/15/2024".into()));
245 let result = f
246 .dispatch(
247 &[ArgumentHandle::new(&date_str, &ctx)],
248 &ctx.function_context(None),
249 )
250 .unwrap()
251 .into_literal();
252 assert!(matches!(result, LiteralValue::Number(_)));
253 }
254
255 #[test]
256 fn test_timevalue_formats() {
257 let wb = TestWorkbook::new().with_function(Arc::new(TimeValueFn));
258 let ctx = wb.interpreter();
259 let f = ctx.context.get_function("", "TIMEVALUE").unwrap();
260
261 let time_str = lit(LiteralValue::Text("14:30:00".into()));
263 let result = f
264 .dispatch(
265 &[ArgumentHandle::new(&time_str, &ctx)],
266 &ctx.function_context(None),
267 )
268 .unwrap()
269 .into_literal();
270 match result {
271 LiteralValue::Number(n) => {
272 assert!((n - 0.6041666667).abs() < 1e-9);
274 }
275 _ => panic!("TIMEVALUE should return a number"),
276 }
277
278 let time_str = lit(LiteralValue::Text("02:30 PM".into()));
280 let result = f
281 .dispatch(
282 &[ArgumentHandle::new(&time_str, &ctx)],
283 &ctx.function_context(None),
284 )
285 .unwrap()
286 .into_literal();
287 match result {
288 LiteralValue::Number(n) => {
289 assert!((n - 0.6041666667).abs() < 1e-9);
290 }
291 _ => panic!("TIMEVALUE should return a number"),
292 }
293 }
294}