1use chrono::{Duration, Utc};
2
3pub fn iso8601_ms(dt: chrono::DateTime<Utc>) -> String {
5 dt.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string()
6}
7
8pub fn default_order_window() -> (String, String) {
10 window_days(30)
11}
12
13pub fn default_orders_all_window() -> (String, String) {
15 window_days(60)
16}
17
18pub fn default_transaction_window() -> (String, String) {
20 window_days(30)
21}
22
23fn window_days(days: i64) -> (String, String) {
24 let to = Utc::now();
25 let from = to - Duration::days(days);
26 (iso8601_ms(from), iso8601_ms(to))
27}
28
29pub fn resolve_time_range(
31 from: Option<&str>,
32 to: Option<&str>,
33 default: impl FnOnce() -> (String, String),
34) -> Result<(String, String), String> {
35 match (from, to) {
36 (Some(f), Some(t)) => Ok((f.to_string(), t.to_string())),
37 (None, None) => Ok(default()),
38 _ => Err("Provide both range parameters or omit both to use CLI defaults".into()),
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn default_window_is_valid_iso8601() {
48 let (from, to) = default_order_window();
49 assert!(from.ends_with('Z'));
50 assert!(to.ends_with('Z'));
51 assert!(from < to);
52 }
53
54 #[test]
55 fn resolve_requires_both_or_neither() {
56 assert!(resolve_time_range(Some("a"), None, default_order_window).is_err());
57 assert!(resolve_time_range(None, None, default_order_window).is_ok());
58 }
59}