Skip to main content

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