aws_smithy_types/date_time/
mod.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! DateTime type for representing Smithy timestamps.
7
8use crate::date_time::format::rfc3339::AllowOffsets;
9use crate::date_time::format::DateTimeParseErrorKind;
10use num_integer::div_mod_floor;
11use num_integer::Integer;
12use std::cmp::Ordering;
13use std::error::Error as StdError;
14use std::fmt;
15use std::fmt::Display;
16use std::time::Duration;
17use std::time::SystemTime;
18use std::time::UNIX_EPOCH;
19
20#[cfg(all(aws_sdk_unstable, feature = "serde-deserialize"))]
21mod de;
22mod format;
23#[cfg(all(aws_sdk_unstable, feature = "serde-serialize"))]
24mod ser;
25
26pub use self::format::DateTimeFormatError;
27pub use self::format::DateTimeParseError;
28
29const MILLIS_PER_SECOND: i64 = 1000;
30const NANOS_PER_MILLI: u32 = 1_000_000;
31const NANOS_PER_SECOND: i128 = 1_000_000_000;
32const NANOS_PER_SECOND_U32: u32 = 1_000_000_000;
33
34/* ANCHOR: date_time */
35
36/// DateTime in time.
37///
38/// DateTime in time represented as seconds and sub-second nanos since
39/// the Unix epoch (January 1, 1970 at midnight UTC/GMT).
40///
41/// This type can be converted to/from the standard library's [`SystemTime`]:
42/// ```rust
43/// # fn doc_fn() -> Result<(), aws_smithy_types::date_time::ConversionError> {
44/// # use aws_smithy_types::date_time::DateTime;
45/// # use std::time::SystemTime;
46///
47/// let the_millennium_as_system_time = SystemTime::try_from(DateTime::from_secs(946_713_600))?;
48/// let now_as_date_time = DateTime::from(SystemTime::now());
49/// # Ok(())
50/// # }
51/// ```
52///
53/// The [`aws-smithy-types-convert`](https://crates.io/crates/aws-smithy-types-convert) crate
54/// can be used for conversions to/from other libraries, such as
55/// [`time`](https://crates.io/crates/time) or [`chrono`](https://crates.io/crates/chrono).
56#[derive(PartialEq, Eq, Hash, Clone, Copy)]
57pub struct DateTime {
58    pub(crate) seconds: i64,
59    /// Subsecond nanos always advances the wallclock time, even for times where seconds is negative
60    ///
61    /// Bigger subsecond nanos => later time
62    pub(crate) subsecond_nanos: u32,
63}
64
65/* ANCHOR_END: date_time */
66
67impl DateTime {
68    /// Creates a `DateTime` from a number of seconds since the Unix epoch.
69    pub fn from_secs(epoch_seconds: i64) -> Self {
70        DateTime {
71            seconds: epoch_seconds,
72            subsecond_nanos: 0,
73        }
74    }
75
76    /// Creates a `DateTime` from a number of milliseconds since the Unix epoch.
77    pub fn from_millis(epoch_millis: i64) -> DateTime {
78        let (seconds, millis) = div_mod_floor(epoch_millis, MILLIS_PER_SECOND);
79        DateTime::from_secs_and_nanos(seconds, millis as u32 * NANOS_PER_MILLI)
80    }
81
82    /// Creates a `DateTime` from a number of nanoseconds since the Unix epoch.
83    pub fn from_nanos(epoch_nanos: i128) -> Result<Self, ConversionError> {
84        let (seconds, subsecond_nanos) = epoch_nanos.div_mod_floor(&NANOS_PER_SECOND);
85        let seconds = i64::try_from(seconds).map_err(|_| {
86            ConversionError("given epoch nanos are too large to fit into a DateTime")
87        })?;
88        let subsecond_nanos = subsecond_nanos as u32; // safe cast because of the modulus
89        Ok(DateTime {
90            seconds,
91            subsecond_nanos,
92        })
93    }
94
95    /// Returns the number of nanoseconds since the Unix epoch that this `DateTime` represents.
96    pub fn as_nanos(&self) -> i128 {
97        let seconds = self.seconds as i128 * NANOS_PER_SECOND;
98        seconds + self.subsecond_nanos as i128
99    }
100
101    /// Creates a `DateTime` from a number of seconds and a fractional second since the Unix epoch.
102    ///
103    /// # Example
104    /// ```
105    /// # use aws_smithy_types::DateTime;
106    /// assert_eq!(
107    ///     DateTime::from_secs_and_nanos(1, 500_000_000u32),
108    ///     DateTime::from_fractional_secs(1, 0.5),
109    /// );
110    /// ```
111    pub fn from_fractional_secs(mut epoch_seconds: i64, fraction: f64) -> Self {
112        // Because of floating point issues, `fraction` can end up being 1.0 leading to
113        // a full second of subsecond nanos. In that case, rollover the subsecond into the second.
114        let mut subsecond_nanos = (fraction * 1_000_000_000_f64) as u32;
115        if subsecond_nanos == 1_000_000_000 {
116            epoch_seconds += 1;
117            subsecond_nanos = 0;
118        }
119        DateTime::from_secs_and_nanos(epoch_seconds, subsecond_nanos)
120    }
121
122    /// Creates a `DateTime` from a number of seconds and sub-second nanos since the Unix epoch.
123    ///
124    /// # Panics
125    /// This function will panic if `subsecond_nanos` is >= 1_000_000_000
126    ///
127    /// # Example
128    /// ```
129    /// # use aws_smithy_types::DateTime;
130    /// assert_eq!(
131    ///     DateTime::from_fractional_secs(1, 0.5),
132    ///     DateTime::from_secs_and_nanos(1, 500_000_000u32),
133    /// );
134    /// ```
135    pub fn from_secs_and_nanos(seconds: i64, subsecond_nanos: u32) -> Self {
136        if subsecond_nanos >= 1_000_000_000 {
137            panic!("{} is > 1_000_000_000", subsecond_nanos)
138        }
139        DateTime {
140            seconds,
141            subsecond_nanos,
142        }
143    }
144
145    /// Returns the `DateTime` value as an `f64` representing the seconds since the Unix epoch.
146    ///
147    /// _Note: This conversion will lose precision due to the nature of floating point numbers._
148    pub fn as_secs_f64(&self) -> f64 {
149        self.seconds as f64 + self.subsecond_nanos as f64 / 1_000_000_000_f64
150    }
151
152    /// Creates a `DateTime` from an `f64` representing the number of seconds since the Unix epoch.
153    ///
154    /// # Example
155    /// ```
156    /// # use aws_smithy_types::DateTime;
157    /// assert_eq!(
158    ///     DateTime::from_fractional_secs(1, 0.5),
159    ///     DateTime::from_secs_f64(1.5),
160    /// );
161    /// ```
162    pub fn from_secs_f64(epoch_seconds: f64) -> Self {
163        let seconds = epoch_seconds.floor() as i64;
164        let rem = epoch_seconds - epoch_seconds.floor();
165        DateTime::from_fractional_secs(seconds, rem)
166    }
167
168    /// Parses a `DateTime` from a string using the given `format`.
169    pub fn from_str(s: &str, format: Format) -> Result<Self, DateTimeParseError> {
170        match format {
171            Format::DateTime => format::rfc3339::parse(s, AllowOffsets::OffsetsForbidden),
172            Format::DateTimeWithOffset => format::rfc3339::parse(s, AllowOffsets::OffsetsAllowed),
173            Format::HttpDate => format::http_date::parse(s),
174            Format::EpochSeconds => format::epoch_seconds::parse(s),
175        }
176    }
177
178    /// Returns true if sub-second nanos is greater than zero.
179    pub fn has_subsec_nanos(&self) -> bool {
180        self.subsecond_nanos != 0
181    }
182
183    /// Returns the epoch seconds component of the `DateTime`.
184    ///
185    /// _Note: this does not include the sub-second nanos._
186    pub fn secs(&self) -> i64 {
187        self.seconds
188    }
189
190    /// Set the seconds component of this `DateTime`.
191    pub fn set_seconds(&mut self, seconds: i64) -> &mut Self {
192        self.seconds = seconds;
193        self
194    }
195
196    /// Returns the sub-second nanos component of the `DateTime`.
197    ///
198    /// _Note: this does not include the number of seconds since the epoch._
199    pub fn subsec_nanos(&self) -> u32 {
200        self.subsecond_nanos
201    }
202
203    /// Set the "sub-second" nanoseconds of this `DateTime`.
204    pub fn set_subsec_nanos(&mut self, subsec_nanos: u32) -> &mut Self {
205        self.subsecond_nanos = subsec_nanos;
206        self
207    }
208
209    /// Converts the `DateTime` to the number of milliseconds since the Unix epoch.
210    ///
211    /// This is fallible since `DateTime` holds more precision than an `i64`, and will
212    /// return a `ConversionError` for `DateTime` values that can't be converted.
213    pub fn to_millis(self) -> Result<i64, ConversionError> {
214        let subsec_millis =
215            Integer::div_floor(&i64::from(self.subsecond_nanos), &(NANOS_PER_MILLI as i64));
216        if self.seconds < 0 {
217            self.seconds
218                .checked_add(1)
219                .and_then(|seconds| seconds.checked_mul(MILLIS_PER_SECOND))
220                .and_then(|millis| millis.checked_sub(1000 - subsec_millis))
221        } else {
222            self.seconds
223                .checked_mul(MILLIS_PER_SECOND)
224                .and_then(|millis| millis.checked_add(subsec_millis))
225        }
226        .ok_or(ConversionError(
227            "DateTime value too large to fit into i64 epoch millis",
228        ))
229    }
230
231    /// Read 1 date of `format` from `s`, expecting either `delim` or EOF
232    ///
233    /// Enable parsing multiple dates from the same string
234    pub fn read(s: &str, format: Format, delim: char) -> Result<(Self, &str), DateTimeParseError> {
235        let (inst, next) = match format {
236            Format::DateTime => format::rfc3339::read(s, AllowOffsets::OffsetsForbidden)?,
237            Format::DateTimeWithOffset => format::rfc3339::read(s, AllowOffsets::OffsetsAllowed)?,
238            Format::HttpDate => format::http_date::read(s)?,
239            Format::EpochSeconds => {
240                let split_point = s.find(delim).unwrap_or(s.len());
241                let (s, rest) = s.split_at(split_point);
242                (Self::from_str(s, format)?, rest)
243            }
244        };
245        if next.is_empty() {
246            Ok((inst, next))
247        } else if next.starts_with(delim) {
248            Ok((inst, &next[1..]))
249        } else {
250            Err(DateTimeParseErrorKind::Invalid("didn't find expected delimiter".into()).into())
251        }
252    }
253
254    /// Formats the `DateTime` to a string using the given `format`.
255    ///
256    /// Returns an error if the given `DateTime` cannot be represented by the desired format.
257    pub fn fmt(&self, format: Format) -> Result<String, DateTimeFormatError> {
258        match format {
259            Format::DateTime | Format::DateTimeWithOffset => format::rfc3339::format(self),
260            Format::EpochSeconds => Ok(format::epoch_seconds::format(self)),
261            Format::HttpDate => format::http_date::format(self),
262        }
263    }
264}
265
266/// Tries to convert a [`DateTime`] into a [`SystemTime`].
267///
268/// This can fail if the the `DateTime` value is larger or smaller than what the `SystemTime`
269/// can represent on the operating system it's compiled for. On Linux, for example, it will only
270/// fail on `Instant::from_secs(i64::MIN)` (with any nanoseconds value). On Windows, however,
271/// Rust's standard library uses a smaller precision type for `SystemTime`, and it will fail
272/// conversion for a much larger range of date-times. This is only an issue if dealing with
273/// date-times beyond several thousands of years from now.
274impl TryFrom<DateTime> for SystemTime {
275    type Error = ConversionError;
276
277    fn try_from(date_time: DateTime) -> Result<Self, Self::Error> {
278        if date_time.secs() < 0 {
279            let mut secs = date_time.secs().unsigned_abs();
280            let mut nanos = date_time.subsec_nanos();
281            if date_time.has_subsec_nanos() {
282                // This is safe because we just went from a negative number to a positive and are subtracting
283                secs -= 1;
284                // This is safe because nanos are < 999,999,999
285                nanos = NANOS_PER_SECOND_U32 - nanos;
286            }
287            UNIX_EPOCH
288                .checked_sub(Duration::new(secs, nanos))
289                .ok_or(ConversionError(
290                    "overflow occurred when subtracting duration from UNIX_EPOCH",
291                ))
292        } else {
293            UNIX_EPOCH
294                .checked_add(Duration::new(
295                    date_time.secs().unsigned_abs(),
296                    date_time.subsec_nanos(),
297                ))
298                .ok_or(ConversionError(
299                    "overflow occurred when adding duration to UNIX_EPOCH",
300                ))
301        }
302    }
303}
304
305impl From<SystemTime> for DateTime {
306    fn from(time: SystemTime) -> Self {
307        if time < UNIX_EPOCH {
308            let duration = UNIX_EPOCH.duration_since(time).expect("time < UNIX_EPOCH");
309            let mut secs = -(duration.as_secs() as i128);
310            let mut nanos = duration.subsec_nanos() as i128;
311            if nanos != 0 {
312                secs -= 1;
313                nanos = NANOS_PER_SECOND - nanos;
314            }
315            DateTime::from_nanos(secs * NANOS_PER_SECOND + nanos)
316                .expect("SystemTime has same precision as DateTime")
317        } else {
318            let duration = time.duration_since(UNIX_EPOCH).expect("UNIX_EPOCH <= time");
319            DateTime::from_secs_and_nanos(
320                i64::try_from(duration.as_secs())
321                    .expect("SystemTime has same precision as DateTime"),
322                duration.subsec_nanos(),
323            )
324        }
325    }
326}
327
328impl PartialOrd for DateTime {
329    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
330        Some(self.cmp(other))
331    }
332}
333
334impl Ord for DateTime {
335    fn cmp(&self, other: &Self) -> Ordering {
336        self.as_nanos().cmp(&other.as_nanos())
337    }
338}
339
340impl Display for DateTime {
341    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
342        // Some dates are out of range to be serialized with `DateTime`.
343        // In these cases, fallback to using epoch seconds which always works
344        let date = match self.fmt(Format::DateTime) {
345            Ok(date) => date,
346            Err(_err) => format::epoch_seconds::format(self),
347        };
348        write!(f, "{}", date)
349    }
350}
351
352impl fmt::Debug for DateTime {
353    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354        fmt::Display::fmt(self, f)
355    }
356}
357/// Failure to convert a `DateTime` to or from another type.
358#[derive(Debug)]
359#[non_exhaustive]
360pub struct ConversionError(&'static str);
361
362impl StdError for ConversionError {}
363
364impl fmt::Display for ConversionError {
365    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366        write!(f, "{}", self.0)
367    }
368}
369
370/// Formats for representing a `DateTime` in the Smithy protocols.
371#[derive(Clone, Copy, Debug, Eq, PartialEq)]
372pub enum Format {
373    /// RFC-3339 Date Time. If the date time has an offset, an error will be returned.
374    /// e.g. `2019-12-16T23:48:18Z`
375    DateTime,
376
377    /// RFC-3339 Date Time. Offsets are supported.
378    /// e.g. `2019-12-16T23:48:18+01:00`
379    DateTimeWithOffset,
380
381    /// Date format used by the HTTP `Date` header, specified in RFC-7231.
382    /// e.g. `Mon, 16 Dec 2019 23:48:18 GMT`
383    HttpDate,
384
385    /// Number of seconds since the Unix epoch formatted as a floating point.
386    /// e.g. `1576540098.52`
387    EpochSeconds,
388}
389
390#[cfg(test)]
391mod test {
392    use crate::date_time::Format;
393    use crate::DateTime;
394    use proptest::proptest;
395    use std::time::SystemTime;
396    use time::format_description::well_known::Rfc3339;
397    use time::OffsetDateTime;
398
399    #[test]
400    fn test_display_date_time() {
401        let date_time = DateTime::from_secs(1576540098);
402        assert_eq!(format!("{}", date_time), "2019-12-16T23:48:18Z");
403
404        let date_time = DateTime::from_fractional_secs(1576540098, 0.52);
405        assert_eq!(format!("{}", date_time), "2019-12-16T23:48:18.52Z");
406
407        let date_time = DateTime::from_secs(1699942527);
408        assert_eq!(format!("{}", date_time), "2023-11-14T06:15:27Z");
409
410        let date_time = DateTime::from_secs(16995123);
411        assert_eq!(format!("{}", date_time), "1970-07-16T16:52:03Z");
412    }
413
414    #[test]
415    fn test_debug_date_time() {
416        let date_time = DateTime::from_secs(1576540098);
417        assert_eq!(format!("{:?}", date_time), "2019-12-16T23:48:18Z");
418
419        let date_time = DateTime::from_fractional_secs(1576540098, 0.52);
420        assert_eq!(format!("{:?}", date_time), "2019-12-16T23:48:18.52Z");
421
422        let date_time = DateTime::from_secs(1699942527);
423        assert_eq!(format!("{:?}", date_time), "2023-11-14T06:15:27Z");
424
425        let date_time = DateTime::from_secs(16995123);
426        assert_eq!(format!("{:?}", date_time), "1970-07-16T16:52:03Z");
427    }
428
429    #[test]
430    fn test_fmt() {
431        let date_time = DateTime::from_secs(1576540098);
432        assert_eq!(
433            date_time.fmt(Format::DateTime).unwrap(),
434            "2019-12-16T23:48:18Z"
435        );
436        assert_eq!(date_time.fmt(Format::EpochSeconds).unwrap(), "1576540098");
437        assert_eq!(
438            date_time.fmt(Format::HttpDate).unwrap(),
439            "Mon, 16 Dec 2019 23:48:18 GMT"
440        );
441
442        let date_time = DateTime::from_fractional_secs(1576540098, 0.52);
443        assert_eq!(
444            date_time.fmt(Format::DateTime).unwrap(),
445            "2019-12-16T23:48:18.52Z"
446        );
447        assert_eq!(
448            date_time.fmt(Format::EpochSeconds).unwrap(),
449            "1576540098.52"
450        );
451        assert_eq!(
452            date_time.fmt(Format::HttpDate).unwrap(),
453            "Mon, 16 Dec 2019 23:48:18 GMT"
454        );
455    }
456
457    #[test]
458    fn test_fmt_zero_seconds() {
459        let date_time = DateTime::from_secs(1576540080);
460        assert_eq!(
461            date_time.fmt(Format::DateTime).unwrap(),
462            "2019-12-16T23:48:00Z"
463        );
464        assert_eq!(date_time.fmt(Format::EpochSeconds).unwrap(), "1576540080");
465        assert_eq!(
466            date_time.fmt(Format::HttpDate).unwrap(),
467            "Mon, 16 Dec 2019 23:48:00 GMT"
468        );
469    }
470
471    #[test]
472    fn test_read_single_http_date() {
473        let s = "Mon, 16 Dec 2019 23:48:18 GMT";
474        let (_, next) = DateTime::read(s, Format::HttpDate, ',').expect("valid");
475        assert_eq!(next, "");
476    }
477
478    #[test]
479    fn test_read_single_float() {
480        let s = "1576540098.52";
481        let (_, next) = DateTime::read(s, Format::EpochSeconds, ',').expect("valid");
482        assert_eq!(next, "");
483    }
484
485    #[test]
486    fn test_read_many_float() {
487        let s = "1576540098.52,1576540098.53";
488        let (_, next) = DateTime::read(s, Format::EpochSeconds, ',').expect("valid");
489        assert_eq!(next, "1576540098.53");
490    }
491
492    #[test]
493    fn test_ready_many_http_date() {
494        let s = "Mon, 16 Dec 2019 23:48:18 GMT,Tue, 17 Dec 2019 23:48:18 GMT";
495        let (_, next) = DateTime::read(s, Format::HttpDate, ',').expect("valid");
496        assert_eq!(next, "Tue, 17 Dec 2019 23:48:18 GMT");
497    }
498
499    #[derive(Debug)]
500    struct EpochMillisTestCase {
501        _rfc3339: &'static str,
502        epoch_millis: i64,
503        epoch_seconds: i64,
504        epoch_subsec_nanos: u32,
505    }
506
507    // These test case values were generated from the following Kotlin JVM code:
508    // ```kotlin
509    // val date_time = DateTime.ofEpochMilli(<epoch milli value>);
510    // println(DateTimeFormatter.ISO_DATE_TIME.format(date_time.atOffset(ZoneOffset.UTC)))
511    // println(date_time.epochSecond)
512    // println(date_time.nano)
513    // ```
514    const EPOCH_MILLIS_TEST_CASES: &[EpochMillisTestCase] = &[
515        EpochMillisTestCase {
516            _rfc3339: "2021-07-30T21:20:04.123Z",
517            epoch_millis: 1627680004123,
518            epoch_seconds: 1627680004,
519            epoch_subsec_nanos: 123000000,
520        },
521        EpochMillisTestCase {
522            _rfc3339: "1918-06-04T02:39:55.877Z",
523            epoch_millis: -1627680004123,
524            epoch_seconds: -1627680005,
525            epoch_subsec_nanos: 877000000,
526        },
527        EpochMillisTestCase {
528            _rfc3339: "+292278994-08-17T07:12:55.807Z",
529            epoch_millis: i64::MAX,
530            epoch_seconds: 9223372036854775,
531            epoch_subsec_nanos: 807000000,
532        },
533        EpochMillisTestCase {
534            _rfc3339: "-292275055-05-16T16:47:04.192Z",
535            epoch_millis: i64::MIN,
536            epoch_seconds: -9223372036854776,
537            epoch_subsec_nanos: 192000000,
538        },
539    ];
540
541    #[test]
542    fn to_millis() {
543        for test_case in EPOCH_MILLIS_TEST_CASES {
544            println!("Test case: {:?}", test_case);
545            let date_time = DateTime::from_secs_and_nanos(
546                test_case.epoch_seconds,
547                test_case.epoch_subsec_nanos,
548            );
549            assert_eq!(test_case.epoch_seconds, date_time.secs());
550            assert_eq!(test_case.epoch_subsec_nanos, date_time.subsec_nanos());
551            assert_eq!(test_case.epoch_millis, date_time.to_millis().unwrap());
552        }
553
554        assert!(DateTime::from_secs_and_nanos(i64::MAX, 0)
555            .to_millis()
556            .is_err());
557    }
558
559    #[test]
560    fn from_millis() {
561        for test_case in EPOCH_MILLIS_TEST_CASES {
562            println!("Test case: {:?}", test_case);
563            let date_time = DateTime::from_millis(test_case.epoch_millis);
564            assert_eq!(test_case.epoch_seconds, date_time.secs());
565            assert_eq!(test_case.epoch_subsec_nanos, date_time.subsec_nanos());
566        }
567    }
568
569    #[test]
570    fn to_from_millis_round_trip() {
571        for millis in &[0, 1627680004123, -1627680004123, i64::MAX, i64::MIN] {
572            assert_eq!(*millis, DateTime::from_millis(*millis).to_millis().unwrap());
573        }
574    }
575
576    #[test]
577    fn as_nanos() {
578        assert_eq!(
579            -9_223_372_036_854_775_807_000_000_001_i128,
580            DateTime::from_secs_and_nanos(i64::MIN, 999_999_999).as_nanos()
581        );
582        assert_eq!(
583            -10_876_543_211,
584            DateTime::from_secs_and_nanos(-11, 123_456_789).as_nanos()
585        );
586        assert_eq!(0, DateTime::from_secs_and_nanos(0, 0).as_nanos());
587        assert_eq!(
588            11_123_456_789,
589            DateTime::from_secs_and_nanos(11, 123_456_789).as_nanos()
590        );
591        assert_eq!(
592            9_223_372_036_854_775_807_999_999_999_i128,
593            DateTime::from_secs_and_nanos(i64::MAX, 999_999_999).as_nanos()
594        );
595    }
596
597    #[test]
598    fn from_nanos() {
599        assert_eq!(
600            DateTime::from_secs_and_nanos(i64::MIN, 999_999_999),
601            DateTime::from_nanos(-9_223_372_036_854_775_807_000_000_001_i128).unwrap(),
602        );
603        assert_eq!(
604            DateTime::from_secs_and_nanos(-11, 123_456_789),
605            DateTime::from_nanos(-10_876_543_211).unwrap(),
606        );
607        assert_eq!(
608            DateTime::from_secs_and_nanos(0, 0),
609            DateTime::from_nanos(0).unwrap(),
610        );
611        assert_eq!(
612            DateTime::from_secs_and_nanos(11, 123_456_789),
613            DateTime::from_nanos(11_123_456_789).unwrap(),
614        );
615        assert_eq!(
616            DateTime::from_secs_and_nanos(i64::MAX, 999_999_999),
617            DateTime::from_nanos(9_223_372_036_854_775_807_999_999_999_i128).unwrap(),
618        );
619        assert!(DateTime::from_nanos(-10_000_000_000_000_000_000_999_999_999_i128).is_err());
620        assert!(DateTime::from_nanos(10_000_000_000_000_000_000_999_999_999_i128).is_err());
621    }
622
623    // TODO(https://github.com/smithy-lang/smithy-rs/issues/1857)
624    #[cfg(not(any(target_arch = "powerpc", target_arch = "x86")))]
625    #[test]
626    fn system_time_conversions() {
627        // Check agreement
628        let date_time = DateTime::from_str("1000-01-02T01:23:10.123Z", Format::DateTime).unwrap();
629        let off_date_time = OffsetDateTime::parse("1000-01-02T01:23:10.123Z", &Rfc3339).unwrap();
630        assert_eq!(
631            SystemTime::from(off_date_time),
632            SystemTime::try_from(date_time).unwrap()
633        );
634
635        let date_time = DateTime::from_str("2039-10-31T23:23:10.456Z", Format::DateTime).unwrap();
636        let off_date_time = OffsetDateTime::parse("2039-10-31T23:23:10.456Z", &Rfc3339).unwrap();
637        assert_eq!(
638            SystemTime::from(off_date_time),
639            SystemTime::try_from(date_time).unwrap()
640        );
641    }
642
643    #[test]
644    fn formatting_of_early_dates() {
645        let date: DateTime =
646            DateTime::from_str("Mon, 16 Dec -019 23:48:18 GMT", Format::HttpDate).unwrap();
647        assert_eq!(format!("{}", date), "-62736509502");
648    }
649
650    #[test]
651    fn ord() {
652        let first = DateTime::from_secs_and_nanos(-1, 0);
653        let second = DateTime::from_secs_and_nanos(-1, 1);
654        let third = DateTime::from_secs_and_nanos(0, 0);
655        let fourth = DateTime::from_secs_and_nanos(0, 1);
656        let fifth = DateTime::from_secs_and_nanos(1, 0);
657
658        assert!(first == first);
659        assert!(first < second);
660        assert!(first < third);
661        assert!(first < fourth);
662        assert!(first < fifth);
663
664        assert!(second > first);
665        assert!(second == second);
666        assert!(second < third);
667        assert!(second < fourth);
668        assert!(second < fifth);
669
670        assert!(third > first);
671        assert!(third > second);
672        assert!(third == third);
673        assert!(third < fourth);
674        assert!(third < fifth);
675
676        assert!(fourth > first);
677        assert!(fourth > second);
678        assert!(fourth > third);
679        assert!(fourth == fourth);
680        assert!(fourth < fifth);
681
682        assert!(fifth > first);
683        assert!(fifth > second);
684        assert!(fifth > third);
685        assert!(fifth > fourth);
686        assert!(fifth == fifth);
687    }
688
689    /// https://github.com/smithy-lang/smithy-rs/issues/3805
690    #[test]
691    fn panic_in_fromsecs_f64() {
692        assert_eq!(DateTime::from_secs_f64(-1.0), DateTime::from_secs(-1));
693
694        assert_eq!(
695            DateTime::from_secs_f64(-1.95877825437922e-309),
696            DateTime::from_secs(0)
697        );
698    }
699
700    const MIN_RFC_3339_MILLIS: i64 = -62135596800000;
701    const MAX_RFC_3339_MILLIS: i64 = 253402300799999;
702
703    // This test uses milliseconds, because `Format::DateTime` does not support nanoseconds.
704    proptest! {
705        #[test]
706        fn ord_proptest(
707            left_millis in MIN_RFC_3339_MILLIS..MAX_RFC_3339_MILLIS,
708            right_millis in MIN_RFC_3339_MILLIS..MAX_RFC_3339_MILLIS,
709        ) {
710            let left = DateTime::from_millis(left_millis);
711            let right = DateTime::from_millis(right_millis);
712
713            let left_str = left.fmt(Format::DateTime).unwrap();
714            let right_str = right.fmt(Format::DateTime).unwrap();
715
716            assert_eq!(left.cmp(&right), left_str.cmp(&right_str));
717        }
718    }
719
720    proptest! {
721        #[test]
722        fn from_secs_f64_proptest(secs: f64) {
723            let _date = DateTime::from_secs_f64(secs);
724        }
725    }
726}