1use crate::{Format, SdResult, ShadowError};
2use std::error::Error;
3use time::format_description::well_known::{Rfc2822, Rfc3339};
4#[cfg(feature = "tzdb")]
5use time::UtcOffset;
6use time::{format_description, OffsetDateTime};
7
8pub enum DateTime {
9    Local(OffsetDateTime),
10    Utc(OffsetDateTime),
11}
12
13pub(crate) const DEFINE_SOURCE_DATE_EPOCH: &str = "SOURCE_DATE_EPOCH";
14
15pub fn now_date_time() -> DateTime {
16    match std::env::var_os(DEFINE_SOURCE_DATE_EPOCH) {
21        None => DateTime::now(),
22        Some(timestamp) => {
23            let epoch = timestamp
24                .into_string()
25                .unwrap_or_else(|_| panic!("Input {DEFINE_SOURCE_DATE_EPOCH} could not be parsed"))
26                .parse::<i64>()
27                .unwrap_or_else(|_| {
28                    panic!("Input {DEFINE_SOURCE_DATE_EPOCH} could not be cast to a number")
29                });
30            DateTime::Utc(OffsetDateTime::from_unix_timestamp(epoch).unwrap())
31        }
32    }
33}
34
35impl Default for DateTime {
36    fn default() -> Self {
37        Self::now()
38    }
39}
40
41impl DateTime {
42    pub fn now() -> Self {
43        Self::local_now().unwrap_or_else(|_| DateTime::Utc(OffsetDateTime::now_utc()))
44    }
45
46    pub fn offset_datetime() -> OffsetDateTime {
47        let date_time = Self::now();
48        match date_time {
49            DateTime::Local(time) | DateTime::Utc(time) => time,
50        }
51    }
52
53    #[cfg(not(feature = "tzdb"))]
54    pub fn local_now() -> Result<Self, Box<dyn Error>> {
55        OffsetDateTime::now_local()
59            .map(DateTime::Local)
60            .map_err(|e| e.into())
61    }
62
63    #[cfg(feature = "tzdb")]
64    pub fn local_now() -> Result<Self, Box<dyn Error>> {
65        let local_time = tzdb::now::local()?;
66        let time_zone_offset =
67            UtcOffset::from_whole_seconds(local_time.local_time_type().ut_offset())?;
68        let local_date_time = OffsetDateTime::from_unix_timestamp(local_time.unix_time())?
69            .to_offset(time_zone_offset);
70        Ok(DateTime::Local(local_date_time))
71    }
72
73    pub fn timestamp_2_utc(time_stamp: i64) -> SdResult<Self> {
74        let time = OffsetDateTime::from_unix_timestamp(time_stamp).map_err(ShadowError::new)?;
75        Ok(DateTime::Utc(time))
76    }
77
78    pub fn to_rfc2822(&self) -> String {
79        match self {
80            DateTime::Local(dt) | DateTime::Utc(dt) => dt.format(&Rfc2822).unwrap_or_default(),
81        }
82    }
83
84    pub fn to_rfc3339(&self) -> String {
85        match self {
86            DateTime::Local(dt) | DateTime::Utc(dt) => dt.format(&Rfc3339).unwrap_or_default(),
87        }
88    }
89}
90
91impl Format for DateTime {
92    fn human_format(&self) -> String {
93        match self {
94            DateTime::Local(dt) | DateTime::Utc(dt) => dt.human_format(),
95        }
96    }
97}
98
99impl Format for OffsetDateTime {
100    fn human_format(&self) -> String {
101        let fmt = format_description::parse(
102            "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
103         sign:mandatory]:[offset_minute]",
104        )
105        .unwrap();
106        self.format(&fmt).unwrap()
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use human_format_validate::parse_human_format;
114
115    mod human_format_validate {
116        use std::num::{NonZeroU32, NonZeroU8};
117        use winnow::ascii::{digit1, space1};
118        use winnow::error::{ContextError, ParseError};
119        use winnow::token::{literal, take};
120        use winnow::{ModalResult, Parser};
121
122        fn u8_len2(input: &mut &str) -> ModalResult<u8> {
123            take(2_usize).try_map(str::parse).parse_next(input)
124        }
125
126        fn non_zero_u8_len2<const LIMIT: u8>(input: &mut &str) -> ModalResult<NonZeroU8> {
127            take(2_usize)
128                .try_map(str::parse)
129                .verify(|x| *x <= unsafe { NonZeroU8::new_unchecked(LIMIT) })
130                .parse_next(input)
131        }
132
133        fn non_zero_u32(input: &mut &str) -> ModalResult<NonZeroU32> {
135            digit1.try_map(str::parse).parse_next(input)
136        }
137
138        pub(crate) fn parse_human_format(
140            input: &str,
141        ) -> Result<(), ParseError<&str, ContextError>> {
142            (
143                non_zero_u32,
144                literal('-'),
145                non_zero_u8_len2::<12>,
146                literal('-'),
147                non_zero_u8_len2::<31>,
148                space1,
149                u8_len2,
150                literal(':'),
151                u8_len2,
152                literal(':'),
153                u8_len2,
154                space1,
155                literal('+'),
156                u8_len2,
157                literal(':'),
158                u8_len2,
159            )
160                .parse(input)?;
161            Ok(())
162        }
163
164        #[test]
165        fn test_parse() {
166            assert!(parse_human_format("2022-07-14 00:40:05 +08:00").is_ok());
167            assert!(parse_human_format("2022-12-14 00:40:05 +08:00").is_ok());
168            assert!(parse_human_format("2022-13-14 00:40:05 +08:00").is_err());
169            assert!(parse_human_format("2022-12-31 00:40:05 +08:00").is_ok());
170            assert!(parse_human_format("2022-12-32 00:40:05 +08:00").is_err());
171            assert!(parse_human_format("2022-07-14 00:40:05 +08:0").is_err());
172            assert!(parse_human_format("2022-07-14 00:40:05 -08:0").is_err());
173            assert!(parse_human_format("2022-07-00 00:40:05 +08:00").is_err());
174            assert!(parse_human_format("2022-00-01 00:40:05 +08:00").is_err());
175            assert!(parse_human_format("2022-00-01 00:40:05 08:00").is_err());
176            assert!(parse_human_format("2022-00-01 00:40:05+08:00").is_err());
177            assert!(parse_human_format("20221-00-01 00:40:05+08:00").is_err());
178            assert!(parse_human_format("20221-01-01 00:40:05 +08:00").is_ok());
179        }
180    }
181
182    #[test]
183    fn test_source_date_epoch() {
184        std::env::set_var(DEFINE_SOURCE_DATE_EPOCH, "1628080443");
185        let time = now_date_time();
186        assert_eq!(time.human_format(), "2021-08-04 12:34:03 +00:00");
187    }
188
189    #[test]
190    fn test_local_now_human_format() {
191        let time = DateTime::local_now().unwrap().human_format();
192        #[cfg(unix)]
193        assert!(!std::fs::read("/etc/localtime").unwrap().is_empty());
194
195        assert!(parse_human_format(&time).is_ok());
196
197        println!("local now:{time}"); assert_eq!(time.len(), 26);
199    }
200
201    #[test]
202    fn test_timestamp_2_utc() {
203        let time = DateTime::timestamp_2_utc(1628080443).unwrap();
204        assert_eq!(time.to_rfc2822(), "Wed, 04 Aug 2021 12:34:03 +0000");
205        assert_eq!(time.to_rfc3339(), "2021-08-04T12:34:03Z");
206        assert_eq!(time.human_format(), "2021-08-04 12:34:03 +00:00");
207    }
208}