Skip to main content

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}