Skip to main content

whichtime_sys/
types.rs

1//! Common types for whichtime-sys
2
3use serde::{Deserialize, Serialize};
4
5/// Weekday enumeration (0 = Sunday)
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[repr(u8)]
8pub enum Weekday {
9    /// Sunday.
10    Sunday = 0,
11    /// Monday.
12    Monday = 1,
13    /// Tuesday.
14    Tuesday = 2,
15    /// Wednesday.
16    Wednesday = 3,
17    /// Thursday.
18    Thursday = 4,
19    /// Friday.
20    Friday = 5,
21    /// Saturday.
22    Saturday = 6,
23}
24
25/// Meridiem (AM/PM) enumeration
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
27#[repr(u8)]
28pub enum Meridiem {
29    /// Ante meridiem.
30    AM = 0,
31    /// Post meridiem.
32    PM = 1,
33}
34
35/// Time unit for duration calculations
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum TimeUnit {
38    /// Calendar year.
39    Year,
40    /// Quarter of a year.
41    Quarter,
42    /// Calendar month.
43    Month,
44    /// Seven-day week.
45    Week,
46    /// Calendar day.
47    Day,
48    /// Hour.
49    Hour,
50    /// Minute.
51    Minute,
52    /// Second.
53    Second,
54    /// Millisecond.
55    Millisecond,
56}
57
58/// Duration with support for both calendar and clock units.
59#[derive(Debug, Clone, Default)]
60pub struct Duration {
61    /// Number of years to add.
62    pub year: Option<f64>,
63    /// Number of quarters to add.
64    pub quarter: Option<f64>,
65    /// Number of months to add.
66    pub month: Option<f64>,
67    /// Number of weeks to add.
68    pub week: Option<f64>,
69    /// Number of days to add.
70    pub day: Option<f64>,
71    /// Number of hours to add.
72    pub hour: Option<f64>,
73    /// Number of minutes to add.
74    pub minute: Option<f64>,
75    /// Number of seconds to add.
76    pub second: Option<f64>,
77    /// Number of milliseconds to add.
78    pub millisecond: Option<f64>,
79}
80
81impl Duration {
82    /// Create a new empty duration
83    pub const fn new() -> Self {
84        Self {
85            year: None,
86            quarter: None,
87            month: None,
88            week: None,
89            day: None,
90            hour: None,
91            minute: None,
92            second: None,
93            millisecond: None,
94        }
95    }
96
97    /// Return `true` if the duration contains clock-based units.
98    pub fn has_time_component(&self) -> bool {
99        self.hour.is_some()
100            || self.minute.is_some()
101            || self.second.is_some()
102            || self.millisecond.is_some()
103    }
104
105    /// Return `true` if the duration contains calendar-based units.
106    pub fn has_date_component(&self) -> bool {
107        self.year.is_some()
108            || self.quarter.is_some()
109            || self.month.is_some()
110            || self.week.is_some()
111            || self.day.is_some()
112    }
113
114    /// Return a copy of the duration with every populated value negated.
115    pub fn reversed(&self) -> Self {
116        Self {
117            year: self.year.map(|v| -v),
118            quarter: self.quarter.map(|v| -v),
119            month: self.month.map(|v| -v),
120            week: self.week.map(|v| -v),
121            day: self.day.map(|v| -v),
122            hour: self.hour.map(|v| -v),
123            minute: self.minute.map(|v| -v),
124            second: self.second.map(|v| -v),
125            millisecond: self.millisecond.map(|v| -v),
126        }
127    }
128}
129
130/// Add a parsed duration to a local datetime.
131pub fn add_duration(
132    mut date: chrono::DateTime<chrono::Local>,
133    duration: &Duration,
134) -> chrono::DateTime<chrono::Local> {
135    use chrono::{Datelike, Duration as ChronoDuration};
136
137    // Handle year
138    if let Some(years) = duration.year {
139        let y = years.floor() as i32;
140        if let Some(new_date) = date.with_year(date.year() + y) {
141            date = new_date;
142        }
143    }
144
145    // Handle quarter (as 3 months)
146    if let Some(quarters) = duration.quarter {
147        let months = (quarters * 3.0).floor() as i32;
148        date = add_months(date, months);
149    }
150
151    // Handle month
152    if let Some(months) = duration.month {
153        let m = months.floor() as i32;
154        date = add_months(date, m);
155    }
156
157    // Handle week
158    if let Some(weeks) = duration.week {
159        let days = (weeks * 7.0).floor() as i64;
160        date += ChronoDuration::days(days);
161    }
162
163    // Handle day
164    if let Some(days) = duration.day {
165        let d = days.floor() as i64;
166        date += ChronoDuration::days(d);
167    }
168
169    // Handle hour
170    if let Some(hours) = duration.hour {
171        let h = hours.floor() as i64;
172        date += ChronoDuration::hours(h);
173    }
174
175    // Handle minute
176    if let Some(minutes) = duration.minute {
177        let m = minutes.floor() as i64;
178        date += ChronoDuration::minutes(m);
179    }
180
181    // Handle second
182    if let Some(seconds) = duration.second {
183        let s = seconds.floor() as i64;
184        date += ChronoDuration::seconds(s);
185    }
186
187    // Handle millisecond
188    if let Some(ms) = duration.millisecond {
189        let m = ms.floor() as i64;
190        date += ChronoDuration::milliseconds(m);
191    }
192
193    date
194}
195
196/// Add months to a date, handling overflow correctly
197fn add_months(
198    date: chrono::DateTime<chrono::Local>,
199    months: i32,
200) -> chrono::DateTime<chrono::Local> {
201    use chrono::Datelike;
202
203    let current_month = date.month() as i32;
204    let new_month = current_month + months;
205
206    if new_month <= 0 {
207        let years_back = (-new_month) / 12 + 1;
208        let final_month = 12 - ((-new_month) % 12);
209        let new_date = date.with_year(date.year() - years_back).unwrap_or(date);
210        new_date.with_month(final_month as u32).unwrap_or(new_date)
211    } else if new_month > 12 {
212        let years_forward = (new_month - 1) / 12;
213        let final_month = ((new_month - 1) % 12) + 1;
214        let new_date = date.with_year(date.year() + years_forward).unwrap_or(date);
215        new_date.with_month(final_month as u32).unwrap_or(new_date)
216    } else {
217        date.with_month(new_month as u32).unwrap_or(date)
218    }
219}