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 from_iso8601_string(iso_string: &str) -> SdResult<Self> {
79 let time = OffsetDateTime::parse(iso_string, &Rfc3339).map_err(ShadowError::new)?;
80 Ok(DateTime::Local(time))
81 }
82
83 pub fn to_rfc2822(&self) -> String {
84 match self {
85 DateTime::Local(dt) | DateTime::Utc(dt) => dt.format(&Rfc2822).unwrap_or_default(),
86 }
87 }
88
89 pub fn to_rfc3339(&self) -> String {
90 match self {
91 DateTime::Local(dt) | DateTime::Utc(dt) => dt.format(&Rfc3339).unwrap_or_default(),
92 }
93 }
94
95 pub fn timestamp(&self) -> i64 {
96 match self {
97 DateTime::Local(dt) | DateTime::Utc(dt) => dt.unix_timestamp(),
98 }
99 }
100}
101
102impl Format for DateTime {
103 fn human_format(&self) -> String {
104 match self {
105 DateTime::Local(dt) | DateTime::Utc(dt) => dt.human_format(),
106 }
107 }
108}
109
110impl Format for OffsetDateTime {
111 fn human_format(&self) -> String {
112 let fmt = format_description::parse(
113 "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
114 sign:mandatory]:[offset_minute]",
115 )
116 .unwrap();
117 self.format(&fmt).unwrap()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use human_format_validate::parse_human_format;
125
126 mod human_format_validate {
127 use std::num::{NonZeroU32, NonZeroU8};
128 use winnow::ascii::{digit1, space1};
129 use winnow::error::{ContextError, ParseError};
130 use winnow::token::{literal, take};
131 use winnow::{ModalResult, Parser};
132
133 fn u8_len2(input: &mut &str) -> ModalResult<u8> {
134 take(2_usize).try_map(str::parse).parse_next(input)
135 }
136
137 fn non_zero_u8_len2<const LIMIT: u8>(input: &mut &str) -> ModalResult<NonZeroU8> {
138 take(2_usize)
139 .try_map(str::parse)
140 .verify(|x| *x <= unsafe { NonZeroU8::new_unchecked(LIMIT) })
141 .parse_next(input)
142 }
143
144 fn non_zero_u32(input: &mut &str) -> ModalResult<NonZeroU32> {
146 digit1.try_map(str::parse).parse_next(input)
147 }
148
149 pub(crate) fn parse_human_format(
151 input: &str,
152 ) -> Result<(), ParseError<&str, ContextError>> {
153 (
154 non_zero_u32,
155 literal('-'),
156 non_zero_u8_len2::<12>,
157 literal('-'),
158 non_zero_u8_len2::<31>,
159 space1,
160 u8_len2,
161 literal(':'),
162 u8_len2,
163 literal(':'),
164 u8_len2,
165 space1,
166 literal('+'),
167 u8_len2,
168 literal(':'),
169 u8_len2,
170 )
171 .parse(input)?;
172 Ok(())
173 }
174
175 #[test]
176 fn test_parse() {
177 assert!(parse_human_format("2022-07-14 00:40:05 +08:00").is_ok());
178 assert!(parse_human_format("2022-12-14 00:40:05 +08:00").is_ok());
179 assert!(parse_human_format("2022-13-14 00:40:05 +08:00").is_err());
180 assert!(parse_human_format("2022-12-31 00:40:05 +08:00").is_ok());
181 assert!(parse_human_format("2022-12-32 00:40:05 +08:00").is_err());
182 assert!(parse_human_format("2022-07-14 00:40:05 +08:0").is_err());
183 assert!(parse_human_format("2022-07-14 00:40:05 -08:0").is_err());
184 assert!(parse_human_format("2022-07-00 00:40:05 +08:00").is_err());
185 assert!(parse_human_format("2022-00-01 00:40:05 +08:00").is_err());
186 assert!(parse_human_format("2022-00-01 00:40:05 08:00").is_err());
187 assert!(parse_human_format("2022-00-01 00:40:05+08:00").is_err());
188 assert!(parse_human_format("20221-00-01 00:40:05+08:00").is_err());
189 assert!(parse_human_format("20221-01-01 00:40:05 +08:00").is_ok());
190 }
191 }
192
193 #[test]
194 fn test_source_date_epoch() {
195 std::env::set_var(DEFINE_SOURCE_DATE_EPOCH, "1628080443");
196 let time = now_date_time();
197 assert_eq!(time.human_format(), "2021-08-04 12:34:03 +00:00");
198 }
199
200 #[test]
201 fn test_local_now_human_format() {
202 let time = DateTime::local_now().unwrap().human_format();
203 #[cfg(unix)]
204 assert!(!std::fs::read("/etc/localtime").unwrap().is_empty());
205
206 assert!(parse_human_format(&time).is_ok());
207
208 println!("local now:{time}"); assert_eq!(time.len(), 26);
210 }
211
212 #[test]
213 fn test_timestamp_2_utc() {
214 let time = DateTime::timestamp_2_utc(1628080443).unwrap();
215 assert_eq!(time.to_rfc2822(), "Wed, 04 Aug 2021 12:34:03 +0000");
216 assert_eq!(time.to_rfc3339(), "2021-08-04T12:34:03Z");
217 assert_eq!(time.human_format(), "2021-08-04 12:34:03 +00:00");
218 assert_eq!(time.timestamp(), 1628080443);
219 }
220
221 #[test]
222 fn test_from_iso8601_string() {
223 let time = DateTime::from_iso8601_string("2021-08-04T12:34:03+08:00").unwrap();
224 assert_eq!(time.to_rfc2822(), "Wed, 04 Aug 2021 12:34:03 +0800");
225 assert_eq!(time.to_rfc3339(), "2021-08-04T12:34:03+08:00");
226 assert_eq!(time.human_format(), "2021-08-04 12:34:03 +08:00");
227 }
228}