aws_sigv4/
date_time.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6// Some of the functions in this file are unused when disabling certain features
7#![allow(dead_code)]
8
9use std::time::SystemTime;
10use time::{OffsetDateTime, Time};
11
12/// Truncates the subseconds from the given `SystemTime` to zero.
13pub(crate) fn truncate_subsecs(time: SystemTime) -> SystemTime {
14    let date_time = OffsetDateTime::from(time);
15    let time = date_time.time();
16    date_time
17        .replace_time(
18            Time::from_hms(time.hour(), time.minute(), time.second()).expect("was already a time"),
19        )
20        .into()
21}
22
23/// Formats a `SystemTime` in `YYYYMMDD` format.
24pub(crate) fn format_date(time: SystemTime) -> String {
25    let time = OffsetDateTime::from(time);
26    format!(
27        "{:04}{:02}{:02}",
28        time.year(),
29        u8::from(time.month()),
30        time.day()
31    )
32}
33
34/// Formats a `SystemTime` in `YYYYMMDD'T'HHMMSS'Z'` format.
35pub(crate) fn format_date_time(time: SystemTime) -> String {
36    let time = OffsetDateTime::from(time);
37    format!(
38        "{:04}{:02}{:02}T{:02}{:02}{:02}Z",
39        time.year(),
40        u8::from(time.month()),
41        time.day(),
42        time.hour(),
43        time.minute(),
44        time.second()
45    )
46}
47
48/// Parse functions that are only needed for unit tests.
49#[cfg(test)]
50pub(crate) mod test_parsers {
51    use std::{borrow::Cow, error::Error, fmt, time::SystemTime};
52    use time::format_description;
53    use time::{Date, PrimitiveDateTime, Time};
54
55    const DATE_TIME_FORMAT: &str = "[year][month][day]T[hour][minute][second]Z";
56    const DATE_FORMAT: &str = "[year][month][day]";
57
58    /// Parses `YYYYMMDD'T'HHMMSS'Z'` formatted dates into a `SystemTime`.
59    pub(crate) fn parse_date_time(date_time_str: &str) -> Result<SystemTime, ParseError> {
60        let date_time = PrimitiveDateTime::parse(
61            date_time_str,
62            &format_description::parse(DATE_TIME_FORMAT).unwrap(),
63        )
64        .map_err(|err| ParseError(err.to_string().into()))?
65        .assume_utc();
66        Ok(date_time.into())
67    }
68
69    /// Parses `YYYYMMDD` formatted dates into a `SystemTime`.
70    pub(crate) fn parse_date(date_str: &str) -> Result<SystemTime, ParseError> {
71        let date_time = PrimitiveDateTime::new(
72            Date::parse(date_str, &format_description::parse(DATE_FORMAT).unwrap())
73                .map_err(|err| ParseError(err.to_string().into()))?,
74            Time::from_hms(0, 0, 0).unwrap(),
75        )
76        .assume_utc();
77        Ok(date_time.into())
78    }
79
80    #[derive(Debug)]
81    pub(crate) struct ParseError(Cow<'static, str>);
82
83    impl fmt::Display for ParseError {
84        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85            write!(f, "failed to parse time: {}", self.0)
86        }
87    }
88
89    impl Error for ParseError {}
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::date_time::test_parsers::{parse_date, parse_date_time};
96    use time::format_description::well_known::Rfc3339;
97
98    // TODO(https://github.com/smithy-lang/smithy-rs/issues/1857)
99    #[cfg(not(any(target_arch = "powerpc", target_arch = "x86")))]
100    #[test]
101    fn date_format() {
102        let time: SystemTime = OffsetDateTime::parse("2039-02-04T23:01:09.104Z", &Rfc3339)
103            .unwrap()
104            .into();
105        assert_eq!("20390204", format_date(time));
106        let time: SystemTime = OffsetDateTime::parse("0100-01-02T00:00:00.000Z", &Rfc3339)
107            .unwrap()
108            .into();
109        assert_eq!("01000102", format_date(time));
110    }
111
112    // TODO(https://github.com/smithy-lang/smithy-rs/issues/1857)
113    #[cfg(not(any(target_arch = "powerpc", target_arch = "x86")))]
114    #[test]
115    fn date_time_format() {
116        let time: SystemTime = OffsetDateTime::parse("2039-02-04T23:01:09.104Z", &Rfc3339)
117            .unwrap()
118            .into();
119        assert_eq!("20390204T230109Z", format_date_time(time));
120        let time: SystemTime = OffsetDateTime::parse("0100-01-02T00:00:00.000Z", &Rfc3339)
121            .unwrap()
122            .into();
123        assert_eq!("01000102T000000Z", format_date_time(time));
124    }
125
126    #[test]
127    fn date_time_roundtrip() {
128        let time = parse_date_time("20150830T123600Z").unwrap();
129        assert_eq!("20150830T123600Z", format_date_time(time));
130    }
131
132    #[test]
133    fn date_roundtrip() {
134        let time = parse_date("20150830").unwrap();
135        assert_eq!("20150830", format_date(time));
136    }
137
138    // TODO(https://github.com/smithy-lang/smithy-rs/issues/1857)
139    #[cfg(not(any(target_arch = "powerpc", target_arch = "x86")))]
140    #[test]
141    fn test_truncate_subsecs() {
142        let time: SystemTime = OffsetDateTime::parse("2039-02-04T23:01:09.104Z", &Rfc3339)
143            .unwrap()
144            .into();
145        let expected: SystemTime = OffsetDateTime::parse("2039-02-04T23:01:09.000Z", &Rfc3339)
146            .unwrap()
147            .into();
148        assert_eq!(expected, truncate_subsecs(time));
149    }
150}