1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! x-amz-date

/// x-amz-date
#[derive(Debug, Clone, Copy)]
pub struct AmzDate {
    /// year
    year: u32,
    /// month
    month: u32,
    /// day
    day: u32,
    /// hour
    hour: u32,
    /// minute
    minute: u32,
    /// second
    second: u32,
}

/// `ParseAmzDateError`
#[allow(missing_copy_implementations)] // Why? See `crate::path::ParseS3PathError`.
#[derive(Debug, thiserror::Error)]
#[error("ParseAmzDateError")]
pub struct ParseAmzDateError {
    /// private place holder
    _priv: (),
}

impl AmzDate {
    /// Parses `AmzDate` from header
    /// # Errors
    /// Returns an error if the header is invalid
    pub fn from_header_str(header: &str) -> Result<Self, ParseAmzDateError> {
        /// nom parser
        fn parse(input: &str) -> nom::IResult<&str, [&str; 6]> {
            use nom::{
                bytes::complete::{tag, take},
                combinator::{all_consuming, verify},
                sequence::tuple,
            };

            let mut parser = verify(
                all_consuming(tuple((
                    take(4_usize),
                    take(2_usize),
                    take(2_usize),
                    tag("T"),
                    take(2_usize),
                    take(2_usize),
                    take(2_usize),
                    tag("Z"),
                ))),
                |&ss: &(&str, &str, &str, &str, &str, &str, &str, &str)| {
                    [ss.0, ss.1, ss.2, ss.4, ss.5, ss.6]
                        .iter()
                        .copied()
                        .all(|s: &str| s.as_bytes().iter().all(u8::is_ascii_digit))
                },
            );

            let (_, (year_str, month_str, day_str, _, hour_str, minute_str, second_str, _)) =
                parser(input)?;

            Ok((
                input,
                [
                    year_str, month_str, day_str, hour_str, minute_str, second_str,
                ],
            ))
        }

        /// parse u32
        fn to_u32(input: &str) -> Result<u32, ParseAmzDateError> {
            match input.parse::<u32>() {
                Ok(x) => Ok(x),
                Err(_) => Err(ParseAmzDateError { _priv: () }),
            }
        }

        match parse(header) {
            Err(_) => Err(ParseAmzDateError { _priv: () }),
            Ok((_, [year_str, month_str, day_str, hour_str, minute_str, second_str])) => Ok(Self {
                year: to_u32(year_str)?,
                month: to_u32(month_str)?,
                day: to_u32(day_str)?,
                hour: to_u32(hour_str)?,
                minute: to_u32(minute_str)?,
                second: to_u32(second_str)?,
            }),
        }
    }

    /// `YYYYMMDD'T'HHMMSS'Z'`
    #[must_use]
    pub fn to_iso8601(&self) -> String {
        format!(
            "{:04}{:02}{:02}T{:02}{:02}{:02}Z",
            self.year, self.month, self.day, self.hour, self.minute, self.second
        )
    }

    /// `YYYYMMDD`
    #[must_use]
    pub fn to_date(&self) -> String {
        format!("{:04}{:02}{:02}", self.year, self.month, self.day,)
    }
}