Skip to main content

journey/
date_time.rs

1use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone};
2use crate::errors::JourneyError;
3
4pub struct DateTimeHandler {
5    pub locale: String,
6}
7
8impl DateTimeHandler {
9    pub fn new(locale: String) -> Self {
10        Self { locale }
11    }
12
13    pub fn parse_date(&self, date_str: &str) -> Result<NaiveDate, JourneyError> {
14        self.parse_date_with_format_override(date_str, None)
15    }
16
17    pub fn parse_date_with_format_override(&self, date_str: &str, format_override: Option<&str>) -> Result<NaiveDate, JourneyError> {
18        // If format override is specified, use only that format
19        if let Some(override_format) = format_override {
20            // Convert common format names to chrono format strings
21            let format_str = match override_format {
22                "YYYY-MM-DD" => "%Y-%m-%d",
23                "MM/DD/YYYY" => "%m/%d/%Y",
24                "DD.MM.YYYY" => "%d.%m.%Y",
25                "DD/MM/YYYY" => "%d/%m/%Y",
26                "MM-DD-YYYY" => "%m-%d-%Y",
27                "DD-MM-YYYY" => "%d-%m-%Y",
28                _ => override_format, // Use as-is if it's already a chrono format
29            };
30
31            if let Ok(date) = NaiveDate::parse_from_str(date_str, format_str) {
32                return Ok(date);
33            }
34
35            return Err(JourneyError::InvalidDateFormat(format!(
36                "Could not parse date: {} with format override: {}",
37                date_str, override_format
38            )));
39        }
40
41        // Try different date formats based on locale (original behavior)
42        let formats: Vec<&str> = if self.locale.starts_with("en") {
43            // US/English formats (MM/DD/YYYY)
44            vec![
45                "%Y-%m-%d",     // ISO: 2025-10-24
46                "%m/%d/%Y",     // US: 10/24/2025
47                "%m-%d-%Y",     // US with dashes: 10-24-2025
48                "%B %d, %Y",    // US long: October 24, 2025
49                "%b %d, %Y",    // US short: Oct 24, 2025
50            ]
51        } else if self.locale.starts_with("no") || self.locale.starts_with("nb") || self.locale.starts_with("nn") {
52            // Norwegian formats (DD.MM.YYYY)
53            vec![
54                "%Y-%m-%d",     // ISO: 2025-10-24
55                "%d.%m.%Y",     // Norwegian: 24.10.2025
56                "%d/%m/%Y",     // European: 24/10/2025
57                "%d-%m-%Y",     // European with dashes: 24-10-2025
58                "%d. %B %Y",    // Norwegian long: 24. oktober 2025
59                "%d. %b %Y",    // Norwegian short: 24. okt 2025
60            ]
61        } else {
62            // Default formats
63            vec![
64                "%Y-%m-%d",     // ISO: 2025-10-24
65                "%m/%d/%Y",     // US: 10/24/2025
66                "%d/%m/%Y",     // European: 24/10/2025
67            ]
68        };
69
70        for format in &formats {
71            if let Ok(date) = NaiveDate::parse_from_str(date_str, format) {
72                return Ok(date);
73            }
74        }
75
76        Err(JourneyError::InvalidDateFormat(format!(
77            "Could not parse date: {} for locale: {}",
78            date_str, self.locale
79        )))
80    }
81
82    pub fn parse_time(&self, time_str: &str) -> Result<NaiveTime, JourneyError> {
83        self.parse_time_with_format_override(time_str, None)
84    }
85
86    pub fn parse_time_with_format_override(&self, time_str: &str, format_override: Option<&str>) -> Result<NaiveTime, JourneyError> {
87        // If format override is specified, use only that format
88        if let Some(override_format) = format_override {
89            let formats = match override_format {
90                "12h" => vec![
91                    "%I:%M %p",     // 12-hour: 2:30 PM
92                    "%I:%M:%S %p",  // 12-hour with seconds: 2:30:45 PM
93                    "%I:%M%p",      // 12-hour compact: 2:30PM
94                    "%I:%M:%S%p",   // 12-hour compact with seconds: 2:30:45PM
95                ],
96                "24h" => vec![
97                    "%H:%M",        // 24-hour: 14:30
98                    "%H:%M:%S",     // 24-hour with seconds: 14:30:45
99                ],
100                _ => return Err(JourneyError::InvalidTimeFormat(format!(
101                    "Invalid time format override: {}. Use '12h' or '24h'", override_format
102                )))
103            };
104
105            for format in &formats {
106                if let Ok(time) = NaiveTime::parse_from_str(time_str, format) {
107                    return Ok(time);
108                }
109            }
110
111            return Err(JourneyError::InvalidTimeFormat(format!(
112                "Could not parse time: {} with format override: {}",
113                time_str, override_format
114            )));
115        }
116
117        // Try different time formats based on locale (original behavior)
118        let formats: Vec<&str> = if self.locale.starts_with("en") {
119            // US/English formats
120            vec![
121                "%H:%M",        // 24-hour: 14:30
122                "%H:%M:%S",     // 24-hour with seconds: 14:30:45
123                "%I:%M %p",     // 12-hour: 2:30 PM
124                "%I:%M:%S %p",  // 12-hour with seconds: 2:30:45 PM
125                "%I:%M%p",      // 12-hour compact: 2:30PM
126                "%I:%M:%S%p",   // 12-hour compact with seconds: 2:30:45PM
127            ]
128        } else if self.locale.starts_with("no") || self.locale.starts_with("nb") || self.locale.starts_with("nn") {
129            // Norwegian formats
130            vec![
131                "%H:%M",        // 24-hour: 14:30
132                "%H:%M:%S",     // 24-hour with seconds: 14:30:45
133                "%I:%M %p",     // 12-hour: 2:30 PM (English AM/PM)
134                "%I:%M:%S %p",  // 12-hour with seconds: 2:30:45 PM
135                "%I:%M%p",      // 12-hour compact: 2:30PM
136                "%I:%M:%S%p",   // 12-hour compact with seconds: 2:30:45PM
137            ]
138        } else {
139            // Default formats
140            vec![
141                "%H:%M",        // 24-hour: 14:30
142                "%H:%M:%S",     // 24-hour with seconds: 14:30:45
143                "%I:%M %p",     // 12-hour: 2:30 PM
144                "%I:%M:%S %p",  // 12-hour with seconds: 2:30:45 PM
145                "%I:%M%p",      // 12-hour compact: 2:30PM
146                "%I:%M:%S%p",   // 12-hour compact with seconds: 2:30:45PM
147            ]
148        };
149
150        for format in &formats {
151            if let Ok(time) = NaiveTime::parse_from_str(time_str, format) {
152                return Ok(time);
153            }
154        }
155
156        Err(JourneyError::InvalidTimeFormat(format!(
157            "Could not parse time: {} for locale: {}",
158            time_str, self.locale
159        )))
160    }
161
162    pub fn parse_relative_date(&self, days_offset: i64) -> NaiveDate {
163        let now = Local::now().date_naive();
164        now - chrono::Duration::days(days_offset)
165    }
166
167    pub fn format_date(&self, date: NaiveDate) -> String {
168        date.format("%Y-%m-%d").to_string()
169    }
170
171    pub fn format_datetime(&self, datetime: DateTime<Local>) -> String {
172        datetime.format("%H:%M:%S").to_string()
173    }
174
175    pub fn format_time(&self, time: chrono::NaiveTime) -> String {
176        time.format("%H:%M:%S").to_string()
177    }
178
179    pub fn get_current_datetime(&self) -> DateTime<Local> {
180        Local::now()
181    }
182
183    pub fn combine_date_time(&self, date: NaiveDate, time: NaiveTime) -> DateTime<Local> {
184        let naive_dt = NaiveDateTime::new(date, time);
185        Local.from_local_datetime(&naive_dt).single().unwrap_or_else(|| Local::now())
186    }
187}
188