tz/datetime/
mod.rs

1//! Types related to a date time.
2
3mod find;
4
5#[doc(inline)]
6#[cfg(feature = "alloc")]
7pub use find::FoundDateTimeList;
8#[doc(inline)]
9pub use find::{FoundDateTimeKind, FoundDateTimeListRefMut};
10
11use crate::constants::*;
12use crate::datetime::find::find_date_time;
13use crate::error::TzError;
14use crate::error::datetime::DateTimeError;
15use crate::timezone::{LocalTimeType, TimeZoneRef};
16use crate::utils::{min, try_into_i32, try_into_i64};
17
18use core::cmp::Ordering;
19use core::fmt;
20
21/// UTC date time expressed in the [proleptic gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar)
22#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
23pub struct UtcDateTime {
24    /// Year
25    year: i32,
26    /// Month in `[1, 12]`
27    month: u8,
28    /// Day of the month in `[1, 31]`
29    month_day: u8,
30    /// Hours since midnight in `[0, 23]`
31    hour: u8,
32    /// Minutes in `[0, 59]`
33    minute: u8,
34    /// Seconds in `[0, 60]`, with a possible leap second
35    second: u8,
36    /// Nanoseconds in `[0, 999_999_999]`
37    nanoseconds: u32,
38}
39
40impl fmt::Display for UtcDateTime {
41    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42        format_date_time(f, self.year, self.month, self.month_day, self.hour, self.minute, self.second, self.nanoseconds, 0)
43    }
44}
45
46impl UtcDateTime {
47    /// Minimum allowed Unix time in seconds
48    const MIN_UNIX_TIME: i64 = -67768100567971200;
49    /// Maximum allowed Unix time in seconds
50    const MAX_UNIX_TIME: i64 = 67767976233532799;
51
52    /// Check if the UTC date time associated to a Unix time in seconds is valid
53    const fn check_unix_time(unix_time: i64) -> Result<(), TzError> {
54        if Self::MIN_UNIX_TIME <= unix_time && unix_time <= Self::MAX_UNIX_TIME { Ok(()) } else { Err(TzError::OutOfRange) }
55    }
56
57    /// Construct a UTC date time
58    ///
59    /// ## Inputs
60    ///
61    /// * `year`: Year
62    /// * `month`: Month in `[1, 12]`
63    /// * `month_day`: Day of the month in `[1, 31]`
64    /// * `hour`: Hours since midnight in `[0, 23]`
65    /// * `minute`: Minutes in `[0, 59]`
66    /// * `second`: Seconds in `[0, 60]`, with a possible leap second
67    /// * `nanoseconds`: Nanoseconds in `[0, 999_999_999]`
68    ///
69    pub const fn new(year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8, nanoseconds: u32) -> Result<Self, TzError> {
70        // Exclude the maximum possible UTC date time with a leap second
71        if year == i32::MAX && month == 12 && month_day == 31 && hour == 23 && minute == 59 && second == 60 {
72            return Err(TzError::OutOfRange);
73        }
74
75        if let Err(error) = check_date_time_inputs(year, month, month_day, hour, minute, second, nanoseconds) {
76            return Err(TzError::DateTime(error));
77        }
78
79        Ok(Self { year, month, month_day, hour, minute, second, nanoseconds })
80    }
81
82    /// Construct a UTC date time from a Unix time in seconds and nanoseconds
83    pub const fn from_timespec(unix_time: i64, nanoseconds: u32) -> Result<Self, TzError> {
84        let seconds = match unix_time.checked_sub(UNIX_OFFSET_SECS) {
85            Some(seconds) => seconds,
86            None => return Err(TzError::OutOfRange),
87        };
88
89        let mut remaining_days = seconds / SECONDS_PER_DAY;
90        let mut remaining_seconds = seconds % SECONDS_PER_DAY;
91        if remaining_seconds < 0 {
92            remaining_seconds += SECONDS_PER_DAY;
93            remaining_days -= 1;
94        }
95
96        let mut cycles_400_years = remaining_days / DAYS_PER_400_YEARS;
97        remaining_days %= DAYS_PER_400_YEARS;
98        if remaining_days < 0 {
99            remaining_days += DAYS_PER_400_YEARS;
100            cycles_400_years -= 1;
101        }
102
103        let cycles_100_years = min(remaining_days / DAYS_PER_100_YEARS, 3);
104        remaining_days -= cycles_100_years * DAYS_PER_100_YEARS;
105
106        let cycles_4_years = min(remaining_days / DAYS_PER_4_YEARS, 24);
107        remaining_days -= cycles_4_years * DAYS_PER_4_YEARS;
108
109        let remaining_years = min(remaining_days / DAYS_PER_NORMAL_YEAR, 3);
110        remaining_days -= remaining_years * DAYS_PER_NORMAL_YEAR;
111
112        let mut year = OFFSET_YEAR + remaining_years + cycles_4_years * 4 + cycles_100_years * 100 + cycles_400_years * 400;
113
114        let mut month = 0;
115        while month < DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH.len() {
116            let days = DAY_IN_MONTHS_LEAP_YEAR_FROM_MARCH[month];
117            if remaining_days < days {
118                break;
119            }
120            remaining_days -= days;
121            month += 1;
122        }
123        month += 2;
124
125        if month >= MONTHS_PER_YEAR as usize {
126            month -= MONTHS_PER_YEAR as usize;
127            year += 1;
128        }
129        month += 1;
130
131        let month_day = 1 + remaining_days;
132
133        let hour = remaining_seconds / SECONDS_PER_HOUR;
134        let minute = (remaining_seconds / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR;
135        let second = remaining_seconds % SECONDS_PER_MINUTE;
136
137        let year = match try_into_i32(year) {
138            Ok(year) => year,
139            Err(error) => return Err(error),
140        };
141
142        Ok(Self { year, month: month as u8, month_day: month_day as u8, hour: hour as u8, minute: minute as u8, second: second as u8, nanoseconds })
143    }
144
145    /// Construct a UTC date time from total nanoseconds since Unix epoch (`1970-01-01T00:00:00Z`)
146    pub const fn from_total_nanoseconds(total_nanoseconds: i128) -> Result<Self, TzError> {
147        match total_nanoseconds_to_timespec(total_nanoseconds) {
148            Ok((unix_time, nanoseconds)) => Self::from_timespec(unix_time, nanoseconds),
149            Err(error) => Err(error),
150        }
151    }
152
153    /// Returns the Unix time in seconds associated to the UTC date time
154    pub const fn unix_time(&self) -> i64 {
155        unix_time(self.year, self.month, self.month_day, self.hour, self.minute, self.second)
156    }
157
158    /// Project the UTC date time into a time zone.
159    ///
160    /// Leap seconds are not preserved.
161    ///
162    pub const fn project(&self, time_zone_ref: TimeZoneRef<'_>) -> Result<DateTime, TzError> {
163        DateTime::from_timespec(self.unix_time(), self.nanoseconds, time_zone_ref)
164    }
165
166    /// Returns the current UTC date time
167    #[cfg(feature = "std")]
168    pub fn now() -> Result<Self, TzError> {
169        Self::from_total_nanoseconds(crate::utils::current_total_nanoseconds())
170    }
171}
172
173/// Date time associated to a local time type, expressed in the [proleptic gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar)
174#[derive(Debug, Copy, Clone)]
175pub struct DateTime {
176    /// Year
177    year: i32,
178    /// Month in `[1, 12]`
179    month: u8,
180    /// Day of the month in `[1, 31]`
181    month_day: u8,
182    /// Hours since midnight in `[0, 23]`
183    hour: u8,
184    /// Minutes in `[0, 59]`
185    minute: u8,
186    /// Seconds in `[0, 60]`, with a possible leap second
187    second: u8,
188    /// Local time type
189    local_time_type: LocalTimeType,
190    /// UTC Unix time in seconds
191    unix_time: i64,
192    /// Nanoseconds in `[0, 999_999_999]`
193    nanoseconds: u32,
194}
195
196impl PartialEq for DateTime {
197    fn eq(&self, other: &Self) -> bool {
198        (self.unix_time, self.nanoseconds) == (other.unix_time, other.nanoseconds)
199    }
200}
201
202impl PartialOrd for DateTime {
203    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
204        (self.unix_time, self.nanoseconds).partial_cmp(&(other.unix_time, other.nanoseconds))
205    }
206}
207
208impl fmt::Display for DateTime {
209    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
210        let ut_offset = self.local_time_type().ut_offset();
211        format_date_time(f, self.year, self.month, self.month_day, self.hour, self.minute, self.second, self.nanoseconds, ut_offset)
212    }
213}
214
215impl DateTime {
216    /// Construct a date time
217    ///
218    /// ## Inputs
219    ///
220    /// * `year`: Year
221    /// * `month`: Month in `[1, 12]`
222    /// * `month_day`: Day of the month in `[1, 31]`
223    /// * `hour`: Hours since midnight in `[0, 23]`
224    /// * `minute`: Minutes in `[0, 59]`
225    /// * `second`: Seconds in `[0, 60]`, with a possible leap second
226    /// * `nanoseconds`: Nanoseconds in `[0, 999_999_999]`
227    /// * `local_time_type`: Local time type associated to a time zone
228    ///
229    #[allow(clippy::too_many_arguments)]
230    pub const fn new(
231        year: i32,
232        month: u8,
233        month_day: u8,
234        hour: u8,
235        minute: u8,
236        second: u8,
237        nanoseconds: u32,
238        local_time_type: LocalTimeType,
239    ) -> Result<Self, TzError> {
240        if let Err(error) = check_date_time_inputs(year, month, month_day, hour, minute, second, nanoseconds) {
241            return Err(TzError::DateTime(error));
242        }
243
244        // Overflow is not possible
245        let unix_time = unix_time(year, month, month_day, hour, minute, second) - local_time_type.ut_offset() as i64;
246
247        // Check if the associated UTC date time is valid
248        if let Err(error) = UtcDateTime::check_unix_time(unix_time) {
249            return Err(error);
250        }
251
252        Ok(Self { year, month, month_day, hour, minute, second, local_time_type, unix_time, nanoseconds })
253    }
254
255    /// Find the possible date times corresponding to a date, a time and a time zone
256    ///
257    /// ## Inputs
258    ///
259    /// * `year`: Year
260    /// * `month`: Month in `[1, 12]`
261    /// * `month_day`: Day of the month in `[1, 31]`
262    /// * `hour`: Hours since midnight in `[0, 23]`
263    /// * `minute`: Minutes in `[0, 59]`
264    /// * `second`: Seconds in `[0, 60]`, with a possible leap second
265    /// * `nanoseconds`: Nanoseconds in `[0, 999_999_999]`
266    /// * `time_zone_ref`: Reference to a time zone
267    ///
268    #[allow(clippy::too_many_arguments)]
269    #[cfg(feature = "alloc")]
270    pub fn find(
271        year: i32,
272        month: u8,
273        month_day: u8,
274        hour: u8,
275        minute: u8,
276        second: u8,
277        nanoseconds: u32,
278        time_zone_ref: TimeZoneRef<'_>,
279    ) -> Result<FoundDateTimeList, TzError> {
280        let mut found_date_time_list = FoundDateTimeList::default();
281        find_date_time(&mut found_date_time_list, year, month, month_day, hour, minute, second, nanoseconds, time_zone_ref)?;
282        Ok(found_date_time_list)
283    }
284
285    /// Find the possible date times corresponding to a date, a time and a time zone.
286    ///
287    /// This method doesn't allocate, and instead takes a preallocated buffer as an input.
288    /// It returns a [`FoundDateTimeListRefMut`] wrapper which has additional methods.
289    ///
290    /// ## Inputs
291    ///
292    /// * `buf`: Preallocated buffer
293    /// * `year`: Year
294    /// * `month`: Month in `[1, 12]`
295    /// * `month_day`: Day of the month in `[1, 31]`
296    /// * `hour`: Hours since midnight in `[0, 23]`
297    /// * `minute`: Minutes in `[0, 59]`
298    /// * `second`: Seconds in `[0, 60]`, with a possible leap second
299    /// * `nanoseconds`: Nanoseconds in `[0, 999_999_999]`
300    /// * `time_zone_ref`: Reference to a time zone
301    ///
302    /// ## Usage
303    ///
304    /// ```rust
305    /// # fn main() -> Result<(), tz::TzError> {
306    /// use tz::datetime::{DateTime, FoundDateTimeKind};
307    /// use tz::timezone::{LocalTimeType, TimeZoneRef, Transition};
308    ///
309    /// let transitions = &[Transition::new(3600, 1), Transition::new(86400, 0), Transition::new(i64::MAX, 0)];
310    /// let local_time_types = &[LocalTimeType::new(0, false, Some(b"STD"))?, LocalTimeType::new(3600, true, Some(b"DST"))?];
311    /// let time_zone_ref = TimeZoneRef::new(transitions, local_time_types, &[], &None)?;
312    ///
313    /// // Buffer is too small, so the results are non exhaustive
314    /// let mut small_buf = [None; 1];
315    /// assert!(!DateTime::find_n(&mut small_buf, 1970, 1, 2, 0, 30, 0, 0, time_zone_ref)?.is_exhaustive());
316    ///
317    /// // Fill buffer
318    /// let mut buf = [None; 2];
319    /// let found_date_time_list = DateTime::find_n(&mut buf, 1970, 1, 2, 0, 30, 0, 0, time_zone_ref)?;
320    /// let data = found_date_time_list.data();
321    /// assert!(found_date_time_list.is_exhaustive());
322    /// assert_eq!(found_date_time_list.count(), 2);
323    /// assert!(matches!(data, [Some(FoundDateTimeKind::Normal(..)), Some(FoundDateTimeKind::Normal(..))]));
324    ///
325    /// // We can reuse the buffer
326    /// let found_date_time_list = DateTime::find_n(&mut buf, 1970, 1, 1, 1, 30, 0, 0, time_zone_ref)?;
327    /// let data = found_date_time_list.data();
328    /// assert!(found_date_time_list.is_exhaustive());
329    /// assert_eq!(found_date_time_list.count(), 1);
330    /// assert!(found_date_time_list.unique().is_none()); // FoundDateTimeKind::Skipped
331    /// assert!(matches!(data, &[Some(FoundDateTimeKind::Skipped { .. })]));
332    /// # Ok(())
333    /// # }
334    /// ```
335    ///
336    #[allow(clippy::too_many_arguments)]
337    pub fn find_n<'a>(
338        buf: &'a mut [Option<FoundDateTimeKind>],
339        year: i32,
340        month: u8,
341        month_day: u8,
342        hour: u8,
343        minute: u8,
344        second: u8,
345        nanoseconds: u32,
346        time_zone_ref: TimeZoneRef<'_>,
347    ) -> Result<FoundDateTimeListRefMut<'a>, TzError> {
348        let mut found_date_time_list = FoundDateTimeListRefMut::new(buf);
349        find_date_time(&mut found_date_time_list, year, month, month_day, hour, minute, second, nanoseconds, time_zone_ref)?;
350        Ok(found_date_time_list)
351    }
352
353    /// Construct a date time from a Unix time in seconds with nanoseconds and a local time type
354    pub const fn from_timespec_and_local(unix_time: i64, nanoseconds: u32, local_time_type: LocalTimeType) -> Result<Self, TzError> {
355        let unix_time_with_offset = match unix_time.checked_add(local_time_type.ut_offset() as i64) {
356            Some(unix_time_with_offset) => unix_time_with_offset,
357            None => return Err(TzError::OutOfRange),
358        };
359
360        let utc_date_time_with_offset = match UtcDateTime::from_timespec(unix_time_with_offset, nanoseconds) {
361            Ok(utc_date_time_with_offset) => utc_date_time_with_offset,
362            Err(error) => return Err(error),
363        };
364
365        let UtcDateTime { year, month, month_day, hour, minute, second, nanoseconds } = utc_date_time_with_offset;
366        Ok(Self { year, month, month_day, hour, minute, second, local_time_type, unix_time, nanoseconds })
367    }
368
369    /// Construct a date time from a Unix time in seconds with nanoseconds and a time zone
370    pub const fn from_timespec(unix_time: i64, nanoseconds: u32, time_zone_ref: TimeZoneRef<'_>) -> Result<Self, TzError> {
371        let local_time_type = match time_zone_ref.find_local_time_type(unix_time) {
372            Ok(&local_time_type) => local_time_type,
373            Err(error) => return Err(error),
374        };
375
376        Self::from_timespec_and_local(unix_time, nanoseconds, local_time_type)
377    }
378
379    /// Construct a date time from total nanoseconds since Unix epoch (`1970-01-01T00:00:00Z`) and a local time type
380    pub const fn from_total_nanoseconds_and_local(total_nanoseconds: i128, local_time_type: LocalTimeType) -> Result<Self, TzError> {
381        match total_nanoseconds_to_timespec(total_nanoseconds) {
382            Ok((unix_time, nanoseconds)) => Self::from_timespec_and_local(unix_time, nanoseconds, local_time_type),
383            Err(error) => Err(error),
384        }
385    }
386
387    /// Construct a date time from total nanoseconds since Unix epoch (`1970-01-01T00:00:00Z`) and a time zone
388    pub const fn from_total_nanoseconds(total_nanoseconds: i128, time_zone_ref: TimeZoneRef<'_>) -> Result<Self, TzError> {
389        match total_nanoseconds_to_timespec(total_nanoseconds) {
390            Ok((unix_time, nanoseconds)) => Self::from_timespec(unix_time, nanoseconds, time_zone_ref),
391            Err(error) => Err(error),
392        }
393    }
394
395    /// Project the date time into another time zone.
396    ///
397    /// Leap seconds are not preserved.
398    ///
399    pub const fn project(&self, time_zone_ref: TimeZoneRef<'_>) -> Result<Self, TzError> {
400        Self::from_timespec(self.unix_time, self.nanoseconds, time_zone_ref)
401    }
402
403    /// Returns the current date time associated to the specified time zone
404    #[cfg(feature = "std")]
405    pub fn now(time_zone_ref: TimeZoneRef<'_>) -> Result<Self, TzError> {
406        let now = crate::utils::current_total_nanoseconds();
407        Self::from_total_nanoseconds(now, time_zone_ref)
408    }
409}
410
411/// Macro for implementing date time getters
412macro_rules! impl_datetime {
413    () => {
414        /// Returns year
415        #[inline]
416        pub const fn year(&self) -> i32 {
417            self.year
418        }
419
420        /// Returns month in `[1, 12]`
421        #[inline]
422        pub const fn month(&self) -> u8 {
423            self.month
424        }
425
426        /// Returns day of the month in `[1, 31]`
427        #[inline]
428        pub const fn month_day(&self) -> u8 {
429            self.month_day
430        }
431
432        /// Returns hours since midnight in `[0, 23]`
433        #[inline]
434        pub const fn hour(&self) -> u8 {
435            self.hour
436        }
437
438        /// Returns minutes in `[0, 59]`
439        #[inline]
440        pub const fn minute(&self) -> u8 {
441            self.minute
442        }
443
444        /// Returns seconds in `[0, 60]`, with a possible leap second
445        #[inline]
446        pub const fn second(&self) -> u8 {
447            self.second
448        }
449
450        /// Returns nanoseconds in `[0, 999_999_999]`
451        #[inline]
452        pub const fn nanoseconds(&self) -> u32 {
453            self.nanoseconds
454        }
455
456        /// Returns days since Sunday in `[0, 6]`
457        #[inline]
458        pub const fn week_day(&self) -> u8 {
459            week_day(self.year, self.month as usize, self.month_day as i64)
460        }
461
462        /// Returns days since January 1 in `[0, 365]`
463        #[inline]
464        pub const fn year_day(&self) -> u16 {
465            year_day(self.year, self.month as usize, self.month_day as i64)
466        }
467
468        /// Returns total nanoseconds since Unix epoch (`1970-01-01T00:00:00Z`)
469        #[inline]
470        pub const fn total_nanoseconds(&self) -> i128 {
471            nanoseconds_since_unix_epoch(self.unix_time(), self.nanoseconds)
472        }
473    };
474}
475
476impl UtcDateTime {
477    impl_datetime!();
478}
479
480impl DateTime {
481    impl_datetime!();
482
483    /// Returns local time type
484    #[inline]
485    pub const fn local_time_type(&self) -> &LocalTimeType {
486        &self.local_time_type
487    }
488
489    /// Returns UTC Unix time in seconds
490    #[inline]
491    pub const fn unix_time(&self) -> i64 {
492        self.unix_time
493    }
494}
495
496/// Compute the number of days since Sunday in `[0, 6]`
497///
498/// ## Inputs
499///
500/// * `year`: Year
501/// * `month`: Month in `[1, 12]`
502/// * `month_day`: Day of the month in `[1, 31]`
503///
504#[inline]
505const fn week_day(year: i32, month: usize, month_day: i64) -> u8 {
506    let days_since_unix_epoch = days_since_unix_epoch(year, month, month_day);
507    (4 + days_since_unix_epoch).rem_euclid(DAYS_PER_WEEK) as u8
508}
509
510/// Compute the number of days since January 1 in `[0, 365]`
511///
512/// ## Inputs
513///
514/// * `year`: Year
515/// * `month`: Month in `[1, 12]`
516/// * `month_day`: Day of the month in `[1, 31]`
517///
518#[inline]
519const fn year_day(year: i32, month: usize, month_day: i64) -> u16 {
520    let leap = (month >= 3 && is_leap_year(year)) as i64;
521    (CUMUL_DAYS_IN_MONTHS_NORMAL_YEAR[month - 1] + leap + month_day - 1) as u16
522}
523
524/// Check if a year is a leap year
525#[inline]
526pub(crate) const fn is_leap_year(year: i32) -> bool {
527    year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)
528}
529
530/// Compute the number of days since Unix epoch (`1970-01-01T00:00:00Z`).
531///
532/// The December 32nd date is possible, which corresponds to January 1st of the next year.
533///
534/// ## Inputs
535///
536/// * `year`: Year
537/// * `month`: Month in `[1, 12]`
538/// * `month_day`: Day of the month in `[1, 32]`
539///
540#[inline]
541pub(crate) const fn days_since_unix_epoch(year: i32, month: usize, month_day: i64) -> i64 {
542    let is_leap_year = is_leap_year(year);
543
544    let year = year as i64;
545
546    let mut result = (year - 1970) * 365;
547
548    if year >= 1970 {
549        result += (year - 1968) / 4;
550        result -= (year - 1900) / 100;
551        result += (year - 1600) / 400;
552
553        if is_leap_year && month < 3 {
554            result -= 1;
555        }
556    } else {
557        result += (year - 1972) / 4;
558        result -= (year - 2000) / 100;
559        result += (year - 2000) / 400;
560
561        if is_leap_year && month >= 3 {
562            result += 1;
563        }
564    }
565
566    result += CUMUL_DAYS_IN_MONTHS_NORMAL_YEAR[month - 1] + month_day - 1;
567
568    result
569}
570
571/// Compute Unix time in seconds
572///
573/// ## Inputs
574///
575/// * `year`: Year
576/// * `month`: Month in `[1, 12]`
577/// * `month_day`: Day of the month in `[1, 31]`
578/// * `hour`: Hours since midnight in `[0, 23]`
579/// * `minute`: Minutes in `[0, 59]`
580/// * `second`: Seconds in `[0, 60]`, with a possible leap second
581///
582#[inline]
583const fn unix_time(year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8) -> i64 {
584    let mut result = days_since_unix_epoch(year, month as usize, month_day as i64);
585    result *= HOURS_PER_DAY;
586    result += hour as i64;
587    result *= MINUTES_PER_HOUR;
588    result += minute as i64;
589    result *= SECONDS_PER_MINUTE;
590    result += second as i64;
591
592    result
593}
594
595/// Compute the number of nanoseconds since Unix epoch (`1970-01-01T00:00:00Z`)
596#[inline]
597const fn nanoseconds_since_unix_epoch(unix_time: i64, nanoseconds: u32) -> i128 {
598    // Overflow is not possible
599    unix_time as i128 * NANOSECONDS_PER_SECOND as i128 + nanoseconds as i128
600}
601
602/// Compute Unix time in seconds with nanoseconds from total nanoseconds since Unix epoch (`1970-01-01T00:00:00Z`)
603///
604/// ## Outputs
605///
606/// * `unix_time`: Unix time in seconds
607/// * `nanoseconds`: Nanoseconds in `[0, 999_999_999]`
608///
609#[inline]
610const fn total_nanoseconds_to_timespec(total_nanoseconds: i128) -> Result<(i64, u32), TzError> {
611    let unix_time = match try_into_i64(total_nanoseconds.div_euclid(NANOSECONDS_PER_SECOND as i128)) {
612        Ok(unix_time) => unix_time,
613        Err(error) => return Err(error),
614    };
615
616    let nanoseconds = total_nanoseconds.rem_euclid(NANOSECONDS_PER_SECOND as i128) as u32;
617
618    Ok((unix_time, nanoseconds))
619}
620
621/// Check date time inputs
622///
623/// ## Inputs
624///
625/// * `year`: Year
626/// * `month`: Month in `[1, 12]`
627/// * `month_day`: Day of the month in `[1, 31]`
628/// * `hour`: Hours since midnight in `[0, 23]`
629/// * `minute`: Minutes in `[0, 59]`
630/// * `second`: Seconds in `[0, 60]`, with a possible leap second
631/// * `nanoseconds`: Nanoseconds in `[0, 999_999_999]`
632///
633const fn check_date_time_inputs(year: i32, month: u8, month_day: u8, hour: u8, minute: u8, second: u8, nanoseconds: u32) -> Result<(), DateTimeError> {
634    if !(1 <= month && month <= 12) {
635        return Err(DateTimeError::InvalidMonth);
636    }
637    if !(1 <= month_day && month_day <= 31) {
638        return Err(DateTimeError::InvalidMonthDay);
639    }
640    if hour > 23 {
641        return Err(DateTimeError::InvalidHour);
642    }
643    if minute > 59 {
644        return Err(DateTimeError::InvalidMinute);
645    }
646    if second > 60 {
647        return Err(DateTimeError::InvalidSecond);
648    }
649    if nanoseconds >= NANOSECONDS_PER_SECOND {
650        return Err(DateTimeError::InvalidNanoseconds);
651    }
652
653    let leap = is_leap_year(year) as i64;
654
655    let mut days_in_month = DAYS_IN_MONTHS_NORMAL_YEAR[month as usize - 1];
656    if month == 2 {
657        days_in_month += leap;
658    }
659
660    if month_day as i64 > days_in_month {
661        return Err(DateTimeError::InvalidMonthDay);
662    }
663
664    Ok(())
665}
666
667/// Format a date time
668///
669/// ## Inputs
670///
671/// * `f`: Formatter
672/// * `year`: Year
673/// * `month`: Month in `[1, 12]`
674/// * `month_day`: Day of the month in `[1, 31]`
675/// * `hour`: Hours since midnight in `[0, 23]`
676/// * `minute`: Minutes in `[0, 59]`
677/// * `second`: Seconds in `[0, 60]`, with a possible leap second
678/// * `nanoseconds`: Nanoseconds in `[0, 999_999_999]`
679/// * `ut_offset`: Offset from UTC in seconds
680///
681#[allow(clippy::too_many_arguments)]
682fn format_date_time(
683    f: &mut fmt::Formatter,
684    year: i32,
685    month: u8,
686    month_day: u8,
687    hour: u8,
688    minute: u8,
689    second: u8,
690    nanoseconds: u32,
691    ut_offset: i32,
692) -> fmt::Result {
693    write!(f, "{year}-{month:02}-{month_day:02}T{hour:02}:{minute:02}:{second:02}.{nanoseconds:09}")?;
694
695    if ut_offset != 0 {
696        let ut_offset = ut_offset as i64;
697        let ut_offset_abs = ut_offset.abs();
698
699        let sign = if ut_offset < 0 { '-' } else { '+' };
700
701        let offset_hour = ut_offset_abs / SECONDS_PER_HOUR;
702        let offset_minute = (ut_offset_abs / SECONDS_PER_MINUTE) % MINUTES_PER_HOUR;
703        let offset_second = ut_offset_abs % SECONDS_PER_MINUTE;
704
705        write!(f, "{sign}{offset_hour:02}:{offset_minute:02}")?;
706
707        if offset_second != 0 {
708            write!(f, ":{offset_second:02}")?;
709        }
710    } else {
711        write!(f, "Z")?;
712    }
713
714    Ok(())
715}
716
717#[cfg(test)]
718mod tests {
719    use super::*;
720
721    #[cfg(feature = "alloc")]
722    use crate::timezone::TimeZone;
723
724    #[cfg(feature = "alloc")]
725    pub(super) fn check_equal_date_time(x: &DateTime, y: &DateTime) {
726        assert_eq!(x.year(), y.year());
727        assert_eq!(x.month(), y.month());
728        assert_eq!(x.month_day(), y.month_day());
729        assert_eq!(x.hour(), y.hour());
730        assert_eq!(x.minute(), y.minute());
731        assert_eq!(x.second(), y.second());
732        assert_eq!(x.local_time_type(), y.local_time_type());
733        assert_eq!(x.unix_time(), y.unix_time());
734        assert_eq!(x.nanoseconds(), y.nanoseconds());
735    }
736
737    #[cfg(feature = "alloc")]
738    #[test]
739    fn test_date_time() -> Result<(), TzError> {
740        let time_zone_utc = TimeZone::utc();
741        let utc = LocalTimeType::utc();
742
743        let time_zone_cet = TimeZone::fixed(3600)?;
744        let cet = LocalTimeType::with_ut_offset(3600)?;
745
746        let time_zone_eet = TimeZone::fixed(7200)?;
747        let eet = LocalTimeType::with_ut_offset(7200)?;
748
749        #[cfg(feature = "std")]
750        {
751            assert_eq!(DateTime::now(time_zone_utc.as_ref())?.local_time_type().ut_offset(), 0);
752            assert_eq!(DateTime::now(time_zone_cet.as_ref())?.local_time_type().ut_offset(), 3600);
753            assert_eq!(DateTime::now(time_zone_eet.as_ref())?.local_time_type().ut_offset(), 7200);
754        }
755
756        let unix_times = &[
757            -93750523134,
758            -11670955134,
759            -11670868734,
760            -8515195134,
761            -8483659134,
762            -8389051134,
763            -8388964734,
764            951825666,
765            951912066,
766            983448066,
767            1078056066,
768            1078142466,
769            4107585666,
770            32540356866,
771        ];
772
773        let nanoseconds_list = &[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
774
775        #[rustfmt::skip]
776        let date_times_utc = &[
777            DateTime { year: -1001, month: 3, month_day: 1,  hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -93750523134, nanoseconds: 10 },
778            DateTime { year: 1600,  month: 2, month_day: 29, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -11670955134, nanoseconds: 11 },
779            DateTime { year: 1600,  month: 3, month_day: 1,  hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -11670868734, nanoseconds: 12 },
780            DateTime { year: 1700,  month: 3, month_day: 1,  hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -8515195134,  nanoseconds: 13 },
781            DateTime { year: 1701,  month: 3, month_day: 1,  hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -8483659134,  nanoseconds: 14 },
782            DateTime { year: 1704,  month: 2, month_day: 29, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -8389051134,  nanoseconds: 15 },
783            DateTime { year: 1704,  month: 3, month_day: 1,  hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: -8388964734,  nanoseconds: 16 },
784            DateTime { year: 2000,  month: 2, month_day: 29, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 951825666,    nanoseconds: 17 },
785            DateTime { year: 2000,  month: 3, month_day: 1,  hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 951912066,    nanoseconds: 18 },
786            DateTime { year: 2001,  month: 3, month_day: 1,  hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 983448066,    nanoseconds: 19 },
787            DateTime { year: 2004,  month: 2, month_day: 29, hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 1078056066,   nanoseconds: 20 },
788            DateTime { year: 2004,  month: 3, month_day: 1,  hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 1078142466,   nanoseconds: 21 },
789            DateTime { year: 2100,  month: 3, month_day: 1,  hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 4107585666,   nanoseconds: 22 },
790            DateTime { year: 3001,  month: 3, month_day: 1,  hour: 12, minute: 1, second: 6, local_time_type: utc, unix_time: 32540356866,  nanoseconds: 23 },
791        ];
792
793        #[rustfmt::skip]
794         let date_times_cet = &[
795            DateTime { year: -1001, month: 3, month_day: 1,  hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -93750523134, nanoseconds: 10 },
796            DateTime { year: 1600,  month: 2, month_day: 29, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -11670955134, nanoseconds: 11 },
797            DateTime { year: 1600,  month: 3, month_day: 1,  hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -11670868734, nanoseconds: 12 },
798            DateTime { year: 1700,  month: 3, month_day: 1,  hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -8515195134,  nanoseconds: 13 },
799            DateTime { year: 1701,  month: 3, month_day: 1,  hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -8483659134,  nanoseconds: 14 },
800            DateTime { year: 1704,  month: 2, month_day: 29, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -8389051134,  nanoseconds: 15 },
801            DateTime { year: 1704,  month: 3, month_day: 1,  hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: -8388964734,  nanoseconds: 16 },
802            DateTime { year: 2000,  month: 2, month_day: 29, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 951825666,    nanoseconds: 17 },
803            DateTime { year: 2000,  month: 3, month_day: 1,  hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 951912066,    nanoseconds: 18 },
804            DateTime { year: 2001,  month: 3, month_day: 1,  hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 983448066,    nanoseconds: 19 },
805            DateTime { year: 2004,  month: 2, month_day: 29, hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 1078056066,   nanoseconds: 20 },
806            DateTime { year: 2004,  month: 3, month_day: 1,  hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 1078142466,   nanoseconds: 21 },
807            DateTime { year: 2100,  month: 3, month_day: 1,  hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 4107585666,   nanoseconds: 22 },
808            DateTime { year: 3001,  month: 3, month_day: 1,  hour: 13, minute: 1, second: 6, local_time_type: cet, unix_time: 32540356866,  nanoseconds: 23 },
809        ];
810
811        #[rustfmt::skip]
812         let date_times_eet = &[
813            DateTime { year: -1001, month: 3, month_day: 1,  hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -93750523134, nanoseconds: 10 },
814            DateTime { year: 1600,  month: 2, month_day: 29, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -11670955134, nanoseconds: 11 },
815            DateTime { year: 1600,  month: 3, month_day: 1,  hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -11670868734, nanoseconds: 12 },
816            DateTime { year: 1700,  month: 3, month_day: 1,  hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -8515195134,  nanoseconds: 13 },
817            DateTime { year: 1701,  month: 3, month_day: 1,  hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -8483659134,  nanoseconds: 14 },
818            DateTime { year: 1704,  month: 2, month_day: 29, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -8389051134,  nanoseconds: 15 },
819            DateTime { year: 1704,  month: 3, month_day: 1,  hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: -8388964734,  nanoseconds: 16 },
820            DateTime { year: 2000,  month: 2, month_day: 29, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 951825666,    nanoseconds: 17 },
821            DateTime { year: 2000,  month: 3, month_day: 1,  hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 951912066,    nanoseconds: 18 },
822            DateTime { year: 2001,  month: 3, month_day: 1,  hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 983448066,    nanoseconds: 19 },
823            DateTime { year: 2004,  month: 2, month_day: 29, hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 1078056066,   nanoseconds: 20 },
824            DateTime { year: 2004,  month: 3, month_day: 1,  hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 1078142466,   nanoseconds: 21 },
825            DateTime { year: 2100,  month: 3, month_day: 1,  hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 4107585666,   nanoseconds: 22 },
826            DateTime { year: 3001,  month: 3, month_day: 1,  hour: 14, minute: 1, second: 6, local_time_type: eet, unix_time: 32540356866,  nanoseconds: 23 },
827        ];
828
829        for ((((&unix_time, &nanoseconds), date_time_utc), date_time_cet), date_time_eet) in
830            unix_times.iter().zip(nanoseconds_list).zip(date_times_utc).zip(date_times_cet).zip(date_times_eet)
831        {
832            let utc_date_time = UtcDateTime::from_timespec(unix_time, nanoseconds)?;
833
834            assert_eq!(UtcDateTime::from_timespec(utc_date_time.unix_time(), nanoseconds)?, utc_date_time);
835
836            assert_eq!(utc_date_time.year(), date_time_utc.year());
837            assert_eq!(utc_date_time.month(), date_time_utc.month());
838            assert_eq!(utc_date_time.month_day(), date_time_utc.month_day());
839            assert_eq!(utc_date_time.hour(), date_time_utc.hour());
840            assert_eq!(utc_date_time.minute(), date_time_utc.minute());
841            assert_eq!(utc_date_time.second(), date_time_utc.second());
842            assert_eq!(utc_date_time.nanoseconds(), date_time_utc.nanoseconds());
843
844            assert_eq!(utc_date_time.unix_time(), unix_time);
845            assert_eq!(date_time_utc.unix_time(), unix_time);
846            assert_eq!(date_time_cet.unix_time(), unix_time);
847            assert_eq!(date_time_eet.unix_time(), unix_time);
848
849            assert_eq!(date_time_utc, date_time_cet);
850            assert_eq!(date_time_utc, date_time_eet);
851
852            check_equal_date_time(&utc_date_time.project(time_zone_utc.as_ref())?, date_time_utc);
853            check_equal_date_time(&utc_date_time.project(time_zone_cet.as_ref())?, date_time_cet);
854            check_equal_date_time(&utc_date_time.project(time_zone_eet.as_ref())?, date_time_eet);
855
856            check_equal_date_time(&date_time_utc.project(time_zone_utc.as_ref())?, date_time_utc);
857            check_equal_date_time(&date_time_cet.project(time_zone_utc.as_ref())?, date_time_utc);
858            check_equal_date_time(&date_time_eet.project(time_zone_utc.as_ref())?, date_time_utc);
859
860            check_equal_date_time(&date_time_utc.project(time_zone_cet.as_ref())?, date_time_cet);
861            check_equal_date_time(&date_time_cet.project(time_zone_cet.as_ref())?, date_time_cet);
862            check_equal_date_time(&date_time_eet.project(time_zone_cet.as_ref())?, date_time_cet);
863
864            check_equal_date_time(&date_time_utc.project(time_zone_eet.as_ref())?, date_time_eet);
865            check_equal_date_time(&date_time_cet.project(time_zone_eet.as_ref())?, date_time_eet);
866            check_equal_date_time(&date_time_eet.project(time_zone_eet.as_ref())?, date_time_eet);
867        }
868
869        Ok(())
870    }
871
872    #[cfg(feature = "alloc")]
873    #[test]
874    fn test_date_time_leap_seconds() -> Result<(), TzError> {
875        let utc_date_time = UtcDateTime::new(1972, 6, 30, 23, 59, 60, 1000)?;
876
877        assert_eq!(UtcDateTime::from_timespec(utc_date_time.unix_time(), 1000)?, UtcDateTime::new(1972, 7, 1, 0, 0, 0, 1000)?);
878
879        let date_time = utc_date_time.project(TimeZone::fixed(-3600)?.as_ref())?;
880
881        let date_time_result = DateTime {
882            year: 1972,
883            month: 6,
884            month_day: 30,
885            hour: 23,
886            minute: 00,
887            second: 00,
888            local_time_type: LocalTimeType::with_ut_offset(-3600)?,
889            unix_time: 78796800,
890            nanoseconds: 1000,
891        };
892
893        check_equal_date_time(&date_time, &date_time_result);
894
895        Ok(())
896    }
897
898    #[cfg(feature = "alloc")]
899    #[test]
900    fn test_date_time_partial_eq_partial_ord() -> Result<(), TzError> {
901        let time_zone_utc = TimeZone::utc();
902        let time_zone_cet = TimeZone::fixed(3600)?;
903        let time_zone_eet = TimeZone::fixed(7200)?;
904
905        let utc_date_time_1 = UtcDateTime::from_timespec(1, 1)?;
906        let utc_date_time_2 = UtcDateTime::from_timespec(2, 1)?;
907        let utc_date_time_3 = UtcDateTime::from_timespec(3, 1)?;
908        let utc_date_time_4 = UtcDateTime::from_timespec(3, 1000)?;
909
910        let date_time_utc_1 = utc_date_time_1.project(time_zone_utc.as_ref())?;
911        let date_time_utc_2 = utc_date_time_2.project(time_zone_utc.as_ref())?;
912        let date_time_utc_3 = utc_date_time_3.project(time_zone_utc.as_ref())?;
913        let date_time_utc_4 = utc_date_time_4.project(time_zone_utc.as_ref())?;
914
915        let date_time_cet_1 = utc_date_time_1.project(time_zone_cet.as_ref())?;
916        let date_time_cet_2 = utc_date_time_2.project(time_zone_cet.as_ref())?;
917        let date_time_cet_3 = utc_date_time_3.project(time_zone_cet.as_ref())?;
918        let date_time_cet_4 = utc_date_time_4.project(time_zone_cet.as_ref())?;
919
920        let date_time_eet_1 = utc_date_time_1.project(time_zone_eet.as_ref())?;
921        let date_time_eet_2 = utc_date_time_2.project(time_zone_eet.as_ref())?;
922        let date_time_eet_3 = utc_date_time_3.project(time_zone_eet.as_ref())?;
923        let date_time_eet_4 = utc_date_time_4.project(time_zone_eet.as_ref())?;
924
925        assert_eq!(date_time_utc_1, date_time_cet_1);
926        assert_eq!(date_time_utc_1, date_time_eet_1);
927
928        assert_eq!(date_time_utc_2, date_time_cet_2);
929        assert_eq!(date_time_utc_2, date_time_eet_2);
930
931        assert_eq!(date_time_utc_3, date_time_cet_3);
932        assert_eq!(date_time_utc_3, date_time_eet_3);
933
934        assert_eq!(date_time_utc_4, date_time_cet_4);
935        assert_eq!(date_time_utc_4, date_time_eet_4);
936
937        assert_ne!(date_time_utc_1, date_time_utc_2);
938        assert_ne!(date_time_utc_1, date_time_utc_3);
939        assert_ne!(date_time_utc_1, date_time_utc_4);
940
941        assert_eq!(date_time_utc_1.partial_cmp(&date_time_cet_1), Some(Ordering::Equal));
942        assert_eq!(date_time_utc_1.partial_cmp(&date_time_eet_1), Some(Ordering::Equal));
943
944        assert_eq!(date_time_utc_2.partial_cmp(&date_time_cet_2), Some(Ordering::Equal));
945        assert_eq!(date_time_utc_2.partial_cmp(&date_time_eet_2), Some(Ordering::Equal));
946
947        assert_eq!(date_time_utc_3.partial_cmp(&date_time_cet_3), Some(Ordering::Equal));
948        assert_eq!(date_time_utc_3.partial_cmp(&date_time_eet_3), Some(Ordering::Equal));
949
950        assert_eq!(date_time_utc_4.partial_cmp(&date_time_cet_4), Some(Ordering::Equal));
951        assert_eq!(date_time_utc_4.partial_cmp(&date_time_eet_4), Some(Ordering::Equal));
952
953        assert_eq!(date_time_utc_1.partial_cmp(&date_time_utc_2), Some(Ordering::Less));
954        assert_eq!(date_time_utc_2.partial_cmp(&date_time_utc_3), Some(Ordering::Less));
955        assert_eq!(date_time_utc_3.partial_cmp(&date_time_utc_4), Some(Ordering::Less));
956
957        Ok(())
958    }
959
960    #[test]
961    fn test_date_time_sync_and_send() {
962        trait _AssertSyncSendStatic: Sync + Send + 'static {}
963        impl _AssertSyncSendStatic for DateTime {}
964    }
965
966    #[test]
967    fn test_utc_date_time_ord() -> Result<(), TzError> {
968        let utc_date_time_1 = UtcDateTime::new(1972, 6, 30, 23, 59, 59, 1000)?;
969        let utc_date_time_2 = UtcDateTime::new(1972, 6, 30, 23, 59, 60, 1000)?;
970        let utc_date_time_3 = UtcDateTime::new(1972, 7, 1, 0, 0, 0, 1000)?;
971        let utc_date_time_4 = UtcDateTime::new(1972, 7, 1, 0, 0, 0, 1001)?;
972
973        assert_eq!(utc_date_time_1.cmp(&utc_date_time_1), Ordering::Equal);
974        assert_eq!(utc_date_time_1.cmp(&utc_date_time_2), Ordering::Less);
975        assert_eq!(utc_date_time_1.cmp(&utc_date_time_3), Ordering::Less);
976        assert_eq!(utc_date_time_1.cmp(&utc_date_time_4), Ordering::Less);
977
978        assert_eq!(utc_date_time_2.cmp(&utc_date_time_1), Ordering::Greater);
979        assert_eq!(utc_date_time_2.cmp(&utc_date_time_2), Ordering::Equal);
980        assert_eq!(utc_date_time_2.cmp(&utc_date_time_3), Ordering::Less);
981        assert_eq!(utc_date_time_2.cmp(&utc_date_time_4), Ordering::Less);
982
983        assert_eq!(utc_date_time_3.cmp(&utc_date_time_1), Ordering::Greater);
984        assert_eq!(utc_date_time_3.cmp(&utc_date_time_2), Ordering::Greater);
985        assert_eq!(utc_date_time_3.cmp(&utc_date_time_3), Ordering::Equal);
986        assert_eq!(utc_date_time_3.cmp(&utc_date_time_4), Ordering::Less);
987
988        assert_eq!(utc_date_time_4.cmp(&utc_date_time_1), Ordering::Greater);
989        assert_eq!(utc_date_time_4.cmp(&utc_date_time_2), Ordering::Greater);
990        assert_eq!(utc_date_time_4.cmp(&utc_date_time_3), Ordering::Greater);
991        assert_eq!(utc_date_time_4.cmp(&utc_date_time_4), Ordering::Equal);
992
993        assert_eq!(utc_date_time_1.cmp(&utc_date_time_1), utc_date_time_1.unix_time().cmp(&utc_date_time_1.unix_time()));
994        assert_eq!(utc_date_time_1.cmp(&utc_date_time_2), utc_date_time_1.unix_time().cmp(&utc_date_time_2.unix_time()));
995        assert_eq!(utc_date_time_1.cmp(&utc_date_time_3), utc_date_time_1.unix_time().cmp(&utc_date_time_3.unix_time()));
996        assert_eq!(utc_date_time_1.cmp(&utc_date_time_4), utc_date_time_1.unix_time().cmp(&utc_date_time_4.unix_time()));
997
998        assert_eq!(utc_date_time_2.cmp(&utc_date_time_1), utc_date_time_2.unix_time().cmp(&utc_date_time_1.unix_time()));
999        assert_eq!(utc_date_time_2.cmp(&utc_date_time_2), utc_date_time_2.unix_time().cmp(&utc_date_time_2.unix_time()));
1000
1001        assert_eq!(utc_date_time_3.cmp(&utc_date_time_1), utc_date_time_3.unix_time().cmp(&utc_date_time_1.unix_time()));
1002        assert_eq!(utc_date_time_3.cmp(&utc_date_time_3), utc_date_time_3.unix_time().cmp(&utc_date_time_3.unix_time()));
1003
1004        assert_eq!(utc_date_time_4.cmp(&utc_date_time_1), utc_date_time_4.unix_time().cmp(&utc_date_time_1.unix_time()));
1005        assert_eq!(utc_date_time_4.cmp(&utc_date_time_4), utc_date_time_4.unix_time().cmp(&utc_date_time_4.unix_time()));
1006
1007        Ok(())
1008    }
1009
1010    #[cfg(feature = "alloc")]
1011    #[test]
1012    fn test_date_time_format() -> Result<(), TzError> {
1013        use alloc::string::ToString;
1014
1015        let time_zones = [
1016            TimeZone::fixed(-49550)?,
1017            TimeZone::fixed(-5400)?,
1018            TimeZone::fixed(-3600)?,
1019            TimeZone::fixed(-1800)?,
1020            TimeZone::fixed(0)?,
1021            TimeZone::fixed(1800)?,
1022            TimeZone::fixed(3600)?,
1023            TimeZone::fixed(5400)?,
1024            TimeZone::fixed(49550)?,
1025        ];
1026
1027        let utc_date_times = &[UtcDateTime::new(2000, 1, 2, 3, 4, 5, 0)?, UtcDateTime::new(2000, 1, 2, 3, 4, 5, 123_456_789)?];
1028
1029        let utc_date_time_strings = &["2000-01-02T03:04:05.000000000Z", "2000-01-02T03:04:05.123456789Z"];
1030
1031        let date_time_strings_list = &[
1032            &[
1033                "2000-01-01T13:18:15.000000000-13:45:50",
1034                "2000-01-02T01:34:05.000000000-01:30",
1035                "2000-01-02T02:04:05.000000000-01:00",
1036                "2000-01-02T02:34:05.000000000-00:30",
1037                "2000-01-02T03:04:05.000000000Z",
1038                "2000-01-02T03:34:05.000000000+00:30",
1039                "2000-01-02T04:04:05.000000000+01:00",
1040                "2000-01-02T04:34:05.000000000+01:30",
1041                "2000-01-02T16:49:55.000000000+13:45:50",
1042            ],
1043            &[
1044                "2000-01-01T13:18:15.123456789-13:45:50",
1045                "2000-01-02T01:34:05.123456789-01:30",
1046                "2000-01-02T02:04:05.123456789-01:00",
1047                "2000-01-02T02:34:05.123456789-00:30",
1048                "2000-01-02T03:04:05.123456789Z",
1049                "2000-01-02T03:34:05.123456789+00:30",
1050                "2000-01-02T04:04:05.123456789+01:00",
1051                "2000-01-02T04:34:05.123456789+01:30",
1052                "2000-01-02T16:49:55.123456789+13:45:50",
1053            ],
1054        ];
1055
1056        for ((utc_date_time, &utc_date_time_string), &date_time_strings) in utc_date_times.iter().zip(utc_date_time_strings).zip(date_time_strings_list) {
1057            for (time_zone, &date_time_string) in time_zones.iter().zip(date_time_strings) {
1058                assert_eq!(utc_date_time.to_string(), utc_date_time_string);
1059                assert_eq!(utc_date_time.project(time_zone.as_ref())?.to_string(), date_time_string);
1060            }
1061        }
1062
1063        Ok(())
1064    }
1065
1066    #[cfg(feature = "alloc")]
1067    #[test]
1068    fn test_date_time_overflow() -> Result<(), TzError> {
1069        assert!(UtcDateTime::new(i32::MIN, 1, 1, 0, 0, 0, 0).is_ok());
1070        assert!(UtcDateTime::new(i32::MAX, 12, 31, 23, 59, 59, 0).is_ok());
1071
1072        assert!(DateTime::new(i32::MIN, 1, 1, 0, 0, 0, 0, LocalTimeType::utc()).is_ok());
1073        assert!(DateTime::new(i32::MAX, 12, 31, 23, 59, 59, 0, LocalTimeType::utc()).is_ok());
1074
1075        assert!(matches!(DateTime::new(i32::MIN, 1, 1, 0, 0, 0, 0, LocalTimeType::with_ut_offset(1)?), Err(TzError::OutOfRange)));
1076        assert!(matches!(DateTime::new(i32::MAX, 12, 31, 23, 59, 59, 0, LocalTimeType::with_ut_offset(-1)?), Err(TzError::OutOfRange)));
1077
1078        assert!(matches!(UtcDateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0), Err(TzError::OutOfRange)));
1079        assert!(matches!(DateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0, LocalTimeType::utc()), Err(TzError::OutOfRange)));
1080        assert!(DateTime::new(i32::MAX, 12, 31, 23, 59, 60, 0, LocalTimeType::with_ut_offset(1)?).is_ok());
1081
1082        assert!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME, 0).is_ok());
1083        assert!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME, 0).is_ok());
1084
1085        assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME - 1, 0), Err(TzError::OutOfRange)));
1086        assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME + 1, 0), Err(TzError::OutOfRange)));
1087
1088        assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME, 0)?.project(TimeZone::fixed(-1)?.as_ref()), Err(TzError::OutOfRange)));
1089        assert!(matches!(UtcDateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME, 0)?.project(TimeZone::fixed(1)?.as_ref()), Err(TzError::OutOfRange)));
1090
1091        assert!(matches!(UtcDateTime::from_timespec(i64::MIN, 0), Err(TzError::OutOfRange)));
1092        assert!(matches!(UtcDateTime::from_timespec(i64::MAX, 0), Err(TzError::OutOfRange)));
1093
1094        assert!(DateTime::from_timespec(UtcDateTime::MIN_UNIX_TIME, 0, TimeZone::fixed(0)?.as_ref()).is_ok());
1095        assert!(DateTime::from_timespec(UtcDateTime::MAX_UNIX_TIME, 0, TimeZone::fixed(0)?.as_ref()).is_ok());
1096
1097        assert!(matches!(DateTime::from_timespec(i64::MIN, 0, TimeZone::fixed(-1)?.as_ref()), Err(TzError::OutOfRange)));
1098        assert!(matches!(DateTime::from_timespec(i64::MAX, 0, TimeZone::fixed(1)?.as_ref()), Err(TzError::OutOfRange)));
1099
1100        Ok(())
1101    }
1102
1103    #[test]
1104    fn test_week_day() {
1105        assert_eq!(week_day(1970, 1, 1), 4);
1106
1107        assert_eq!(week_day(2000, 1, 1), 6);
1108        assert_eq!(week_day(2000, 2, 28), 1);
1109        assert_eq!(week_day(2000, 2, 29), 2);
1110        assert_eq!(week_day(2000, 3, 1), 3);
1111        assert_eq!(week_day(2000, 12, 31), 0);
1112
1113        assert_eq!(week_day(2001, 1, 1), 1);
1114        assert_eq!(week_day(2001, 2, 28), 3);
1115        assert_eq!(week_day(2001, 3, 1), 4);
1116        assert_eq!(week_day(2001, 12, 31), 1);
1117    }
1118
1119    #[test]
1120    fn test_year_day() {
1121        assert_eq!(year_day(2000, 1, 1), 0);
1122        assert_eq!(year_day(2000, 2, 28), 58);
1123        assert_eq!(year_day(2000, 2, 29), 59);
1124        assert_eq!(year_day(2000, 3, 1), 60);
1125        assert_eq!(year_day(2000, 12, 31), 365);
1126
1127        assert_eq!(year_day(2001, 1, 1), 0);
1128        assert_eq!(year_day(2001, 2, 28), 58);
1129        assert_eq!(year_day(2001, 3, 1), 59);
1130        assert_eq!(year_day(2001, 12, 31), 364);
1131    }
1132
1133    #[test]
1134    fn test_is_leap_year() {
1135        assert!(is_leap_year(2000));
1136        assert!(!is_leap_year(2001));
1137        assert!(is_leap_year(2004));
1138        assert!(!is_leap_year(2100));
1139        assert!(!is_leap_year(2200));
1140        assert!(!is_leap_year(2300));
1141        assert!(is_leap_year(2400));
1142    }
1143
1144    #[test]
1145    fn test_days_since_unix_epoch() {
1146        assert_eq!(days_since_unix_epoch(-1001, 3, 1), -1085076);
1147        assert_eq!(days_since_unix_epoch(1600, 2, 29), -135081);
1148        assert_eq!(days_since_unix_epoch(1600, 3, 1), -135080);
1149        assert_eq!(days_since_unix_epoch(1700, 3, 1), -98556);
1150        assert_eq!(days_since_unix_epoch(1701, 3, 1), -98191);
1151        assert_eq!(days_since_unix_epoch(1704, 2, 29), -97096);
1152        assert_eq!(days_since_unix_epoch(2000, 2, 29), 11016);
1153        assert_eq!(days_since_unix_epoch(2000, 3, 1), 11017);
1154        assert_eq!(days_since_unix_epoch(2001, 3, 1), 11382);
1155        assert_eq!(days_since_unix_epoch(2004, 2, 29), 12477);
1156        assert_eq!(days_since_unix_epoch(2100, 3, 1), 47541);
1157        assert_eq!(days_since_unix_epoch(3001, 3, 1), 376624);
1158    }
1159
1160    #[test]
1161    fn test_nanoseconds_since_unix_epoch() {
1162        assert_eq!(nanoseconds_since_unix_epoch(1, 1000), 1_000_001_000);
1163        assert_eq!(nanoseconds_since_unix_epoch(0, 1000), 1000);
1164        assert_eq!(nanoseconds_since_unix_epoch(-1, 1000), -999_999_000);
1165        assert_eq!(nanoseconds_since_unix_epoch(-2, 1000), -1_999_999_000);
1166    }
1167
1168    #[test]
1169    fn test_total_nanoseconds_to_timespec() -> Result<(), TzError> {
1170        assert!(matches!(total_nanoseconds_to_timespec(1_000_001_000), Ok((1, 1000))));
1171        assert!(matches!(total_nanoseconds_to_timespec(1000), Ok((0, 1000))));
1172        assert!(matches!(total_nanoseconds_to_timespec(-999_999_000), Ok((-1, 1000))));
1173        assert!(matches!(total_nanoseconds_to_timespec(-1_999_999_000), Ok((-2, 1000))));
1174
1175        assert!(matches!(total_nanoseconds_to_timespec(i128::MAX), Err(TzError::OutOfRange)));
1176        assert!(matches!(total_nanoseconds_to_timespec(i128::MIN), Err(TzError::OutOfRange)));
1177
1178        let min_total_nanoseconds = -9223372036854775808000000000;
1179        let max_total_nanoseconds = 9223372036854775807999999999;
1180
1181        assert!(matches!(total_nanoseconds_to_timespec(min_total_nanoseconds), Ok((i64::MIN, 0))));
1182        assert!(matches!(total_nanoseconds_to_timespec(max_total_nanoseconds), Ok((i64::MAX, 999999999))));
1183
1184        assert!(matches!(total_nanoseconds_to_timespec(min_total_nanoseconds - 1), Err(TzError::OutOfRange)));
1185        assert!(matches!(total_nanoseconds_to_timespec(max_total_nanoseconds + 1), Err(TzError::OutOfRange)));
1186
1187        Ok(())
1188    }
1189
1190    #[test]
1191    fn test_const() -> Result<(), TzError> {
1192        use crate::timezone::{AlternateTime, LeapSecond, MonthWeekDay, RuleDay, Transition, TransitionRule};
1193
1194        macro_rules! unwrap {
1195            ($x:expr) => {
1196                match $x {
1197                    Ok(x) => x,
1198                    Err(_) => panic!(),
1199                }
1200            };
1201        }
1202
1203        const TIME_ZONE_REF: TimeZoneRef<'static> = unwrap!(TimeZoneRef::new(
1204            &[
1205                Transition::new(-2334101314, 1),
1206                Transition::new(-1157283000, 2),
1207                Transition::new(-1155436200, 1),
1208                Transition::new(-880198200, 3),
1209                Transition::new(-769395600, 4),
1210                Transition::new(-765376200, 1),
1211                Transition::new(-712150200, 5),
1212            ],
1213            const {
1214                &[
1215                    unwrap!(LocalTimeType::new(-37886, false, Some(b"LMT"))),
1216                    unwrap!(LocalTimeType::new(-37800, false, Some(b"HST"))),
1217                    unwrap!(LocalTimeType::new(-34200, true, Some(b"HDT"))),
1218                    unwrap!(LocalTimeType::new(-34200, true, Some(b"HWT"))),
1219                    unwrap!(LocalTimeType::new(-34200, true, Some(b"HPT"))),
1220                    unwrap!(LocalTimeType::new(-36000, false, Some(b"HST"))),
1221                ]
1222            },
1223            &[
1224                LeapSecond::new(78796800, 1),
1225                LeapSecond::new(94694401, 2),
1226                LeapSecond::new(126230402, 3),
1227                LeapSecond::new(157766403, 4),
1228                LeapSecond::new(189302404, 5),
1229                LeapSecond::new(220924805, 6),
1230            ],
1231            const {
1232                &Some(TransitionRule::Alternate(unwrap!(AlternateTime::new(
1233                    unwrap!(LocalTimeType::new(-36000, false, Some(b"HST"))),
1234                    unwrap!(LocalTimeType::new(-34200, true, Some(b"HPT"))),
1235                    RuleDay::MonthWeekDay(unwrap!(MonthWeekDay::new(10, 5, 0))),
1236                    93600,
1237                    RuleDay::MonthWeekDay(unwrap!(MonthWeekDay::new(3, 4, 4))),
1238                    7200,
1239                ))))
1240            },
1241        ));
1242
1243        const UTC: TimeZoneRef<'static> = TimeZoneRef::utc();
1244
1245        const UNIX_EPOCH: UtcDateTime = unwrap!(UtcDateTime::from_timespec(0, 0));
1246        const UTC_DATE_TIME: UtcDateTime = unwrap!(UtcDateTime::new(2000, 1, 1, 0, 0, 0, 1000));
1247
1248        const DATE_TIME: DateTime = unwrap!(DateTime::new(2000, 1, 1, 1, 0, 0, 1000, unwrap!(LocalTimeType::with_ut_offset(3600))));
1249
1250        const DATE_TIME_1: DateTime = unwrap!(UTC_DATE_TIME.project(TIME_ZONE_REF));
1251        const DATE_TIME_2: DateTime = unwrap!(DATE_TIME_1.project(UTC));
1252
1253        const LOCAL_TIME_TYPE_1: &LocalTimeType = DATE_TIME_1.local_time_type();
1254        const LOCAL_TIME_TYPE_2: &LocalTimeType = DATE_TIME_2.local_time_type();
1255
1256        assert_eq!(UNIX_EPOCH.unix_time(), 0);
1257        assert_eq!(DATE_TIME.unix_time(), UTC_DATE_TIME.unix_time());
1258        assert_eq!(DATE_TIME_2.unix_time(), UTC_DATE_TIME.unix_time());
1259        assert_eq!(DATE_TIME_2.nanoseconds(), UTC_DATE_TIME.nanoseconds());
1260
1261        let date_time = UTC_DATE_TIME.project(TIME_ZONE_REF)?;
1262        assert_eq!(date_time.local_time_type().time_zone_designation(), LOCAL_TIME_TYPE_1.time_zone_designation());
1263
1264        let date_time_1 = DateTime::from_timespec(UTC_DATE_TIME.unix_time(), 1000, TIME_ZONE_REF)?;
1265        let date_time_2 = date_time_1.project(UTC)?;
1266
1267        assert_eq!(date_time, DATE_TIME_1);
1268        assert_eq!(date_time_1, DATE_TIME_1);
1269        assert_eq!(date_time_2, DATE_TIME_2);
1270
1271        let local_time_type_1 = date_time_1.local_time_type();
1272        let local_time_type_2 = date_time_2.local_time_type();
1273
1274        assert_eq!(local_time_type_1.ut_offset(), LOCAL_TIME_TYPE_1.ut_offset());
1275        assert_eq!(local_time_type_1.is_dst(), LOCAL_TIME_TYPE_1.is_dst());
1276        assert_eq!(local_time_type_1.time_zone_designation(), LOCAL_TIME_TYPE_1.time_zone_designation());
1277
1278        assert_eq!(local_time_type_2.ut_offset(), LOCAL_TIME_TYPE_2.ut_offset());
1279        assert_eq!(local_time_type_2.is_dst(), LOCAL_TIME_TYPE_2.is_dst());
1280        assert_eq!(local_time_type_2.time_zone_designation(), LOCAL_TIME_TYPE_2.time_zone_designation());
1281
1282        Ok(())
1283    }
1284}