Skip to main content

deep_time/ymdhms/
mod.rs

1use crate::{Dt, LiteStr, Scale, Weekday};
2
3mod to_str;
4
5/// Combined Gregorian date + wall time with subsecond precision.
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7#[cfg_attr(feature = "js", derive(tsify::Tsify))]
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub struct YmdHms {
10    pub(crate) yr: i64,
11    pub(crate) mo: u8,
12    pub(crate) day: u8,
13    pub(crate) hr: u8,
14    pub(crate) min: u8,
15    pub(crate) sec: u8,    // 0–60 (60 only during leap seconds)
16    pub(crate) attos: u64, // attoseconds (0 ≤ subsec < 10¹⁸)
17    pub(crate) unix_attosec: i128,
18}
19
20impl YmdHms {
21    #[inline]
22    pub const fn yr(&self) -> i64 {
23        self.yr
24    }
25
26    #[inline]
27    pub const fn mo(&self) -> u8 {
28        self.mo
29    }
30
31    #[inline]
32    pub const fn day(&self) -> u8 {
33        self.day
34    }
35
36    #[inline]
37    pub const fn hr(&self) -> u8 {
38        self.hr
39    }
40
41    #[inline]
42    pub const fn min(&self) -> u8 {
43        self.min
44    }
45
46    #[inline]
47    pub const fn sec(&self) -> u8 {
48        self.sec
49    }
50
51    #[inline]
52    pub const fn attos(&self) -> u64 {
53        self.attos
54    }
55
56    /// Attoseconds since 1970-01-01 midnight, on whatever time scale
57    /// the object was created on.
58    #[inline]
59    pub const fn unix_attosec(&self) -> i128 {
60        self.unix_attosec
61    }
62
63    pub(crate) const fn to_ymdhms_rich_on(
64        &self,
65        iso_yr: i64,
66        iso_wk: u8,
67        iso_wkday: Weekday,
68        day_of_yr: u16,
69        wkday: u8,
70        wk_of_yr_sun: u8,
71        wk_of_yr_mon: u8,
72        scale: Scale,
73    ) -> YmdHmsRich {
74        YmdHmsRich::new(
75            self.unix_attosec,
76            self.yr,
77            self.mo,
78            self.day,
79            self.hr,
80            self.min,
81            self.sec,
82            self.attos,
83            iso_yr,
84            iso_wk,
85            iso_wkday,
86            day_of_yr,
87            wkday,
88            wk_of_yr_sun,
89            wk_of_yr_mon,
90            scale,
91        )
92    }
93}
94
95/// Gregorian calendar and time-of-day components of a [`Dt`].
96#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
97#[cfg_attr(feature = "js", derive(tsify::Tsify))]
98#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99pub struct YmdHmsRich {
100    /// UNIX attoseconds counting from 1970 epoch
101    pub(crate) unix_attosec: i128,
102    /// Gregorian year (proleptic Gregorian calendar, supports negative years and year 0).
103    pub(crate) yr: i64,
104    /// Gregorian month in the range [1, 12].
105    pub(crate) mo: u8,
106    /// Gregorian day of the month in the range [1, 31].
107    pub(crate) day: u8,
108    /// Hour of the day in the range [0, 23].
109    pub(crate) hr: u8,
110    /// Minute in the range [0, 59].
111    pub(crate) min: u8,
112    /// Second in the range [0, 60] (60 only during UTC leap seconds).
113    pub(crate) sec: u8,
114    /// Fractional part of the second expressed in attoseconds (u64).
115    pub(crate) attos: u64,
116    /// ISO 8601 week year.
117    pub(crate) iso_yr: i64,
118    /// ISO 8601 week number in the range [1, 53].
119    pub(crate) iso_wk: u8,
120    /// ISO 8601 weekday enum e.g. Monday/Tuesday/...
121    pub(crate) iso_wkday: Weekday,
122    /// Ordinal day of the year (1-based).
123    pub(crate) day_of_yr: u16,
124    /// Weekday number (0 = Sunday … 6 = Saturday).
125    pub(crate) wkday: u8,
126    /// Sunday based week of year (Range: `0..=53`).
127    pub(crate) wk_of_yr_sun: u8,
128    /// Monday based week of year (Range: `0..=53`).
129    pub(crate) wk_of_yr_mon: u8,
130    /// Used for formatting (strftime).
131    /// A stored offset in seconds, used within the crate.
132    pub(crate) offset_sec: Option<i32>,
133    /// A stored IANA name, used within the crate, %Q.
134    pub(crate) tz: Option<LiteStr<49>>,
135    /// UTC, EST, %Z
136    pub(crate) tz_abbrev: Option<LiteStr<49>>,
137    /// Scale the instance was created on
138    pub(crate) scale: Scale,
139}
140
141impl YmdHmsRich {
142    /// Creates a new [`YmdHmsRich`] with all fields specified.
143    #[inline]
144    pub(crate) const fn new(
145        unix_attosec: i128,
146        yr: i64,
147        mo: u8,
148        day: u8,
149        hr: u8,
150        min: u8,
151        sec: u8,
152        attos: u64,
153        iso_yr: i64,
154        iso_wk: u8,
155        iso_wkday: Weekday,
156        day_of_yr: u16,
157        wkday: u8,
158        wk_of_yr_sun: u8,
159        wk_of_yr_mon: u8,
160        scale: Scale,
161    ) -> Self {
162        Self {
163            unix_attosec,
164            yr,
165            mo,
166            day,
167            hr,
168            min,
169            sec,
170            attos,
171            iso_yr,
172            iso_wk,
173            iso_wkday,
174            day_of_yr,
175            wkday,
176            wk_of_yr_sun,
177            wk_of_yr_mon,
178            offset_sec: None,
179            tz: None,
180            tz_abbrev: None,
181            scale,
182        }
183    }
184
185    /// Reconstructs a [`Dt`]. Round trips with [`Dt::to_date_time`].
186    #[inline]
187    pub const fn to_dt(&self) -> Dt {
188        Dt::from_ymdhms_on(
189            self.yr, self.mo, self.day, self.hr, self.min, self.sec, self.attos, self.scale,
190        )
191    }
192
193    /// Attoseconds since 1970-01-01 midnight, on whatever time scale
194    /// the object was created on.
195    #[inline]
196    pub const fn unix_attosec(&self) -> i128 {
197        self.unix_attosec
198    }
199
200    /// Returns the Unix timestamp since 1970-01-01 00:00:00 as a tuple of
201    /// `(whole_seconds, attoseconds)`.
202    ///
203    /// - The timestamp will be on whatever [`Scale`] the [`DateTime`] was created on.
204    /// - `whole_seconds` can be negative (for dates before 1970).
205    /// - The fractional part (`attoseconds`) is always in the range `0..=999_999_999_999_999_999`.
206    #[inline]
207    pub const fn unix_timestamp(&self) -> (i64, u64) {
208        const ATTOS_PER_SEC_I128: i128 = 1_000_000_000_000_000_000;
209        let total = self.unix_attosec;
210        let secs = (total / ATTOS_PER_SEC_I128) as i64;
211        let frac = (total % ATTOS_PER_SEC_I128).unsigned_abs() as u64;
212        (secs, frac)
213    }
214
215    /// Gregorian year (proleptic Gregorian calendar, supports negative years and year 0).
216    #[inline]
217    pub const fn yr(&self) -> i64 {
218        self.yr
219    }
220
221    /// Gregorian month in the range [1, 12].
222    #[inline]
223    pub const fn mo(&self) -> u8 {
224        self.mo
225    }
226
227    /// Gregorian day of the month in the range [1, 31].
228    #[inline]
229    pub const fn day(&self) -> u8 {
230        self.day
231    }
232
233    /// Hour of the day in the range [0, 23].
234    #[inline]
235    pub const fn hr(&self) -> u8 {
236        self.hr
237    }
238
239    /// Minute in the range [0, 59].
240    #[inline]
241    pub const fn min(&self) -> u8 {
242        self.min
243    }
244
245    /// Second in the range [0, 60] (60 only during UTC leap seconds).
246    #[inline]
247    pub const fn sec(&self) -> u8 {
248        self.sec
249    }
250
251    /// Fractional part of the second expressed in attoseconds (`0 ≤ attos < 10¹⁸`).
252    #[inline]
253    pub const fn attos(&self) -> u64 {
254        self.attos
255    }
256
257    /// ISO 8601 week year.
258    #[inline]
259    pub const fn iso_yr(&self) -> i64 {
260        self.iso_yr
261    }
262
263    /// ISO 8601 week number in the range [1, 53].
264    #[inline]
265    pub const fn iso_wk(&self) -> u8 {
266        self.iso_wk
267    }
268
269    /// ISO 8601 weekday (Monday-based [`Weekday`] enum).
270    #[inline]
271    pub const fn iso_wkday(&self) -> Weekday {
272        self.iso_wkday
273    }
274
275    /// Ordinal day of the year (1-based).
276    #[inline]
277    pub const fn day_of_yr(&self) -> u16 {
278        self.day_of_yr
279    }
280
281    /// Weekday number (0 = Sunday … 6 = Saturday).
282    #[inline]
283    pub const fn wkday_sun(&self) -> u8 {
284        self.wkday
285    }
286
287    /// ISO 8601 weekday (0 = Monday ... 6 = Sunday).
288    #[inline]
289    pub const fn wkday_mon(&self) -> u8 {
290        self.iso_wkday.wk_mon()
291    }
292
293    /// Sunday based week of year (Range: `0..=53`).
294    #[inline]
295    pub const fn wk_of_yr_sun(&self) -> u8 {
296        self.wk_of_yr_sun
297    }
298
299    /// Monday based week of year (Range: `0..=53`).
300    #[inline]
301    pub const fn wk_of_yr_mon(&self) -> u8 {
302        self.wk_of_yr_mon
303    }
304
305    #[inline]
306    pub(crate) const fn offset_sec(&self) -> Option<i32> {
307        self.offset_sec
308    }
309
310    #[inline]
311    pub(crate) const fn tz(&self) -> Option<&LiteStr<49>> {
312        self.tz.as_ref()
313    }
314
315    #[inline]
316    pub(crate) const fn tz_abbrev(&self) -> Option<&LiteStr<49>> {
317        self.tz_abbrev.as_ref()
318    }
319
320    #[inline]
321    pub(crate) fn set_offset(&mut self, offset_sec: Option<i32>) -> &mut Self {
322        self.offset_sec = offset_sec;
323        self
324    }
325
326    #[inline]
327    pub(crate) fn set_tz(&mut self, tz: Option<&str>) -> &mut Self {
328        self.tz = tz.and_then(|s| Some(LiteStr::from_str(s)));
329        self
330    }
331
332    #[inline]
333    pub(crate) fn set_tz_abbrev(&mut self, tz_abbrev: Option<&str>) -> &mut Self {
334        self.tz_abbrev = tz_abbrev.and_then(|s| Some(LiteStr::from_str(s)));
335        self
336    }
337}