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<'a, 'b, 'c>(
34 &self,
35 args: &'c [ArgumentHandle<'a, 'b>],
36 _ctx: &dyn FunctionContext<'b>,
37 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
38 let date_text = match args[0].value()?.into_literal() {
39 LiteralValue::Text(s) => s,
40 LiteralValue::Error(e) => {
41 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
42 }
43 other => {
44 return Err(ExcelError::new_value()
45 .with_message(format!("DATEVALUE expects text, got {other:?}")));
46 }
47 };
48
49 let formats = [
52 "%Y-%m-%d", "%m/%d/%Y", "%d/%m/%Y", "%Y/%m/%d", "%B %d, %Y", "%b %d, %Y", "%d-%b-%Y", "%d %B %Y", ];
61
62 for fmt in &formats {
63 if let Ok(date) = NaiveDate::parse_from_str(&date_text, fmt) {
64 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
65 date_to_serial(&date),
66 )));
67 }
68 }
69
70 Err(ExcelError::new_value()
71 .with_message("DATEVALUE could not parse date text in supported formats"))
72 }
73}
74
75#[derive(Debug)]
77pub struct TimeValueFn;
78
79impl Function for TimeValueFn {
80 func_caps!(PURE);
81
82 fn name(&self) -> &'static str {
83 "TIMEVALUE"
84 }
85
86 fn min_args(&self) -> usize {
87 1
88 }
89
90 fn arg_schema(&self) -> &'static [ArgSchema] {
91 use std::sync::LazyLock;
92 static ONE: LazyLock<Vec<ArgSchema>> = LazyLock::new(|| vec![ArgSchema::any()]);
93 &ONE[..]
94 }
95
96 fn eval<'a, 'b, 'c>(
97 &self,
98 args: &'c [ArgumentHandle<'a, 'b>],
99 _ctx: &dyn FunctionContext<'b>,
100 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
101 let time_text = match args[0].value()?.into_literal() {
102 LiteralValue::Text(s) => s,
103 LiteralValue::Error(e) => {
104 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
105 }
106 other => {
107 return Err(ExcelError::new_value()
108 .with_message(format!("TIMEVALUE expects text, got {other:?}")));
109 }
110 };
111
112 let formats = [
114 "%H:%M:%S", "%H:%M", "%I:%M:%S %p", "%I:%M %p", ];
119
120 for fmt in &formats {
121 if let Ok(time) = chrono::NaiveTime::parse_from_str(&time_text, fmt) {
122 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
123 time_to_fraction(&time),
124 )));
125 }
126 }
127
128 Err(ExcelError::new_value()
129 .with_message("TIMEVALUE could not parse time text in supported formats"))
130 }
131}
132
133pub fn register_builtins() {
134 use std::sync::Arc;
135 crate::function_registry::register_function(Arc::new(DateValueFn));
136 crate::function_registry::register_function(Arc::new(TimeValueFn));
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::test_workbook::TestWorkbook;
143 use formualizer_parse::parser::{ASTNode, ASTNodeType};
144 use std::sync::Arc;
145
146 fn lit(v: LiteralValue) -> ASTNode {
147 ASTNode::new(ASTNodeType::Literal(v), None)
148 }
149
150 #[test]
151 fn test_datevalue_formats() {
152 let wb = TestWorkbook::new().with_function(Arc::new(DateValueFn));
153 let ctx = wb.interpreter();
154 let f = ctx.context.get_function("", "DATEVALUE").unwrap();
155
156 let date_str = lit(LiteralValue::Text("2024-01-15".into()));
158 let result = f
159 .dispatch(
160 &[ArgumentHandle::new(&date_str, &ctx)],
161 &ctx.function_context(None),
162 )
163 .unwrap()
164 .into_literal();
165 assert!(matches!(result, LiteralValue::Number(_)));
166
167 let date_str = lit(LiteralValue::Text("01/15/2024".into()));
169 let result = f
170 .dispatch(
171 &[ArgumentHandle::new(&date_str, &ctx)],
172 &ctx.function_context(None),
173 )
174 .unwrap()
175 .into_literal();
176 assert!(matches!(result, LiteralValue::Number(_)));
177 }
178
179 #[test]
180 fn test_timevalue_formats() {
181 let wb = TestWorkbook::new().with_function(Arc::new(TimeValueFn));
182 let ctx = wb.interpreter();
183 let f = ctx.context.get_function("", "TIMEVALUE").unwrap();
184
185 let time_str = lit(LiteralValue::Text("14:30:00".into()));
187 let result = f
188 .dispatch(
189 &[ArgumentHandle::new(&time_str, &ctx)],
190 &ctx.function_context(None),
191 )
192 .unwrap()
193 .into_literal();
194 match result {
195 LiteralValue::Number(n) => {
196 assert!((n - 0.6041666667).abs() < 1e-9);
198 }
199 _ => panic!("TIMEVALUE should return a number"),
200 }
201
202 let time_str = lit(LiteralValue::Text("02:30 PM".into()));
204 let result = f
205 .dispatch(
206 &[ArgumentHandle::new(&time_str, &ctx)],
207 &ctx.function_context(None),
208 )
209 .unwrap()
210 .into_literal();
211 match result {
212 LiteralValue::Number(n) => {
213 assert!((n - 0.6041666667).abs() < 1e-9);
214 }
215 _ => panic!("TIMEVALUE should return a number"),
216 }
217 }
218}