tz/datetime/
mod.rs

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