formualizer_eval/builtins/datetime/today_now.rs
1//! TODAY and NOW volatile functions
2
3use super::serial::{date_to_serial_for, datetime_to_serial_for};
4use crate::function::Function;
5use crate::traits::{ArgumentHandle, FunctionContext};
6use formualizer_common::{ExcelError, LiteralValue};
7use formualizer_macros::func_caps;
8
9/// Returns the current date as a volatile serial value.
10///
11/// # Remarks
12/// - `TODAY` is volatile and recalculates each time the workbook recalculates.
13/// - The result is an integer date serial with no time fraction.
14/// - Serial output respects the active workbook date system (`1900` or `1904`).
15///
16/// # Examples
17/// ```yaml,sandbox
18/// title: "TODAY has no time fraction"
19/// formula: "=TODAY()=INT(TODAY())"
20/// expected: true
21/// ```
22///
23/// ```yaml,sandbox
24/// title: "Date arithmetic with TODAY"
25/// formula: "=TODAY()+7-TODAY()"
26/// expected: 7
27/// ```
28///
29/// ```yaml,docs
30/// related:
31/// - NOW
32/// - DATE
33/// - WORKDAY
34/// faq:
35/// - q: "Will TODAY include a time-of-day fraction?"
36/// a: "No. TODAY always returns an integer serial date, so its fractional part is always 0."
37/// ```
38#[derive(Debug)]
39pub struct TodayFn;
40
41/// [formualizer-docgen:schema:start]
42/// Name: TODAY
43/// Type: TodayFn
44/// Min args: 0
45/// Max args: 0
46/// Variadic: false
47/// Signature: TODAY()
48/// Arg schema: []
49/// Caps: VOLATILE
50/// [formualizer-docgen:schema:end]
51impl Function for TodayFn {
52 func_caps!(VOLATILE);
53
54 fn name(&self) -> &'static str {
55 "TODAY"
56 }
57
58 fn min_args(&self) -> usize {
59 0
60 }
61
62 fn eval<'a, 'b, 'c>(
63 &self,
64 _args: &'c [ArgumentHandle<'a, 'b>],
65 ctx: &dyn FunctionContext<'b>,
66 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
67 let today = ctx.clock().today();
68 let serial = date_to_serial_for(ctx.date_system(), &today);
69 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
70 serial,
71 )))
72 }
73}
74
75/// Returns the current date and time as a volatile datetime serial.
76///
77/// # Remarks
78/// - `NOW` is volatile and may produce a different value at each recalculation.
79/// - The integer part is the current date serial; the fractional part is time of day.
80/// - Serial output respects the active workbook date system (`1900` or `1904`).
81///
82/// # Examples
83/// ```yaml,sandbox
84/// title: "NOW includes today's date"
85/// formula: "=INT(NOW())=TODAY()"
86/// expected: true
87/// ```
88///
89/// ```yaml,sandbox
90/// title: "NOW is at or after TODAY"
91/// formula: "=NOW()>=TODAY()"
92/// expected: true
93/// ```
94///
95/// ```yaml,docs
96/// related:
97/// - TODAY
98/// - TIME
99/// - SECOND
100/// faq:
101/// - q: "How do I isolate only the time portion from NOW?"
102/// a: "Use NOW()-INT(NOW()); the integer part is date serial and the fractional part is time-of-day."
103/// ```
104#[derive(Debug)]
105pub struct NowFn;
106
107/// [formualizer-docgen:schema:start]
108/// Name: NOW
109/// Type: NowFn
110/// Min args: 0
111/// Max args: 0
112/// Variadic: false
113/// Signature: NOW()
114/// Arg schema: []
115/// Caps: VOLATILE
116/// [formualizer-docgen:schema:end]
117impl Function for NowFn {
118 func_caps!(VOLATILE);
119
120 fn name(&self) -> &'static str {
121 "NOW"
122 }
123
124 fn min_args(&self) -> usize {
125 0
126 }
127
128 fn eval<'a, 'b, 'c>(
129 &self,
130 _args: &'c [ArgumentHandle<'a, 'b>],
131 ctx: &dyn FunctionContext<'b>,
132 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
133 let now = ctx.clock().now();
134 let serial = datetime_to_serial_for(ctx.date_system(), &now);
135 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(
136 serial,
137 )))
138 }
139}
140
141pub fn register_builtins() {
142 use std::sync::Arc;
143 crate::function_registry::register_function(Arc::new(TodayFn));
144 crate::function_registry::register_function(Arc::new(NowFn));
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::test_workbook::TestWorkbook;
151 use std::sync::Arc;
152
153 #[test]
154 fn test_today_volatility() {
155 let wb = TestWorkbook::new().with_function(Arc::new(TodayFn));
156 let ctx = wb.interpreter();
157 let f = ctx.context.get_function("", "TODAY").unwrap();
158
159 // Check that it returns a number
160 let result = f
161 .dispatch(&[], &ctx.function_context(None))
162 .unwrap()
163 .into_literal();
164 match result {
165 LiteralValue::Number(n) => {
166 // Should be a reasonable date serial number (> 0)
167 assert!(n > 0.0);
168 // Should be an integer (no time component)
169 assert_eq!(n.trunc(), n);
170 }
171 _ => panic!("TODAY should return a number"),
172 }
173
174 // Volatility flag is set via func_caps!(VOLATILE) macro
175 }
176
177 #[test]
178 fn test_now_volatility() {
179 let wb = TestWorkbook::new().with_function(Arc::new(NowFn));
180 let ctx = wb.interpreter();
181 let f = ctx.context.get_function("", "NOW").unwrap();
182
183 // Check that it returns a number
184 let result = f
185 .dispatch(&[], &ctx.function_context(None))
186 .unwrap()
187 .into_literal();
188 match result {
189 LiteralValue::Number(n) => {
190 // Should be a reasonable date serial number (> 0)
191 assert!(n > 0.0);
192 // Should have a fractional component (time)
193 // Note: There's a tiny chance this could fail if run exactly at midnight
194 // but that's extremely unlikely
195 }
196 _ => panic!("NOW should return a number"),
197 }
198
199 // Volatility flag is set via func_caps!(VOLATILE) macro
200 }
201}