1use std::cmp::Ordering;
2use std::hash::Hash;
3use std::marker::PhantomData;
4
5use chrono::{
6 DateTime as CrDateTime, Datelike, DurationRound, Months, NaiveDate, NaiveDateTime, NaiveTime,
7 Timelike, Utc,
8};
9use tea_error::{TResult, tbail};
10
11use super::timeunit::*;
12use crate::TimeDelta;
13
14#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16#[repr(transparent)]
17pub struct DateTime<U: TimeUnitTrait = Nanosecond>(pub i64, PhantomData<U>);
28
29impl<U: TimeUnitTrait> std::fmt::Debug for DateTime<U>
30where
31 Self: TryInto<CrDateTime<Utc>>,
32{
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 if self.is_nat() {
35 write!(f, "NaT")
36 } else {
37 write!(f, "{}", self.strftime(None))
38 }
39 }
40}
41
42unsafe impl<U: TimeUnitTrait> Send for DateTime<U> {}
43unsafe impl<U: TimeUnitTrait> Sync for DateTime<U> {}
44
45const TIME_RULE_VEC: [&str; 11] = [
46 "%Y-%m-%d %H:%M:%S",
47 "%Y-%m-%d %H:%M:%S.%f",
48 "%Y-%m-%d",
49 "%Y%m%d",
50 "%Y%m%d %H%M%S",
51 "%d/%m/%Y",
52 "%d/%m/%Y H%M%S",
53 "%Y%m%d%H%M%S",
54 "%d/%m/%YH%M%S",
55 "%Y/%m/%d",
56 "%Y/%m/%d %H:%M:%S",
57];
58
59impl<U: TimeUnitTrait> DateTime<U> {
60 #[inline]
70 pub const fn new(dt: i64) -> Self {
71 Self(dt, PhantomData)
72 }
73
74 #[inline]
80 pub const fn is_nat(&self) -> bool {
81 self.0 == i64::MIN
82 }
83
84 #[inline]
90 pub const fn is_not_nat(&self) -> bool {
91 self.0 != i64::MIN
92 }
93
94 #[inline]
100 pub const fn nat() -> Self {
101 Self(i64::MIN, PhantomData)
102 }
103
104 #[inline]
110 pub const fn into_i64(self) -> i64 {
111 self.0
112 }
113
114 #[inline]
124 pub const fn from_opt_i64(v: Option<i64>) -> Self {
125 if let Some(v) = v {
126 Self::new(v)
127 } else {
128 Self::nat()
129 }
130 }
131
132 #[inline]
138 pub const fn into_opt_i64(self) -> Option<i64> {
139 if self.is_nat() { None } else { Some(self.0) }
140 }
141
142 #[inline]
148 #[deprecated(since = "0.5.0", note = "use `as_cr` instead")]
149 pub fn to_cr(&self) -> Option<CrDateTime<Utc>>
150 where
151 Self: TryInto<CrDateTime<Utc>>,
152 {
153 self.as_cr()
154 }
155
156 #[inline]
157 pub fn as_cr(&self) -> Option<CrDateTime<Utc>>
158 where
159 Self: TryInto<CrDateTime<Utc>>,
160 {
161 if self.is_nat() {
162 None
163 } else {
164 (*self).try_into().ok()
165 }
166 }
167
168 #[inline(always)]
179 pub fn parse(s: &str, fmt: Option<&str>) -> TResult<Self>
180 where
181 Self: From<CrDateTime<Utc>>,
182 {
183 if let Some(fmt) = fmt {
184 if let Ok(cr_dt) = NaiveDateTime::parse_from_str(s, fmt) {
185 Ok(cr_dt.into())
186 } else if let Ok(cr_date) = NaiveDate::parse_from_str(s, fmt) {
187 Ok(cr_date.into())
188 } else {
189 tbail!(ParseError:"Failed to parse datetime from string: {}", s)
190 }
191 } else {
192 for fmt in TIME_RULE_VEC.iter() {
193 if let Ok(cr_dt) = NaiveDateTime::parse_from_str(s, fmt) {
194 return Ok(cr_dt.into());
195 } else if let Ok(cr_date) = NaiveDate::parse_from_str(s, fmt) {
196 return Ok(cr_date.into());
197 }
198 }
199 tbail!(ParseError:"Failed to parse datetime from string: {}", s)
200 }
201 }
202
203 #[inline]
213 pub fn strftime(&self, fmt: Option<&str>) -> String
214 where
215 Self: TryInto<CrDateTime<Utc>>,
216 {
217 if self.is_nat() {
218 "NaT".to_string()
219 } else {
220 let fmt = fmt.unwrap_or("%Y-%m-%d %H:%M:%S.%f");
221 self.as_cr().unwrap().format(fmt).to_string()
222 }
223 }
224
225 pub fn duration_trunc(self, duration: TimeDelta) -> Self
235 where
236 Self: TryInto<CrDateTime<Utc>> + From<CrDateTime<Utc>>,
237 {
238 if self.is_nat() {
239 return self;
240 }
241 let mut dt = self.as_cr().unwrap();
242 let dm = duration.months;
243 if dm != 0 {
244 let (flag, dt_year) = dt.year_ce();
245 if dm < 0 {
246 unimplemented!("not support year before ce or negative month")
247 }
248 let dt_month = if flag {
249 (dt_year * 12 + dt.month()) as i32
250 } else {
251 dt_year as i32 * (-12) + dt.month() as i32
252 };
253 let delta_down = dt_month % dm;
254 dt = match delta_down.cmp(&0) {
255 Ordering::Equal => dt,
256 Ordering::Greater => dt - Months::new(delta_down as u32),
257 Ordering::Less => dt - Months::new((dm - delta_down.abs()) as u32),
258 };
259 if let Some(nd) = duration.inner.num_nanoseconds() {
260 if nd == 0 {
261 return dt.into();
262 }
263 }
264 }
265 dt.duration_trunc(duration.inner)
266 .expect("Rounding Error")
267 .into()
268 }
269}
270
271impl<U: TimeUnitTrait> DateTime<U>
272where
273 Self: TryInto<CrDateTime<Utc>>,
274{
275 #[inline(always)]
281 pub fn time(&self) -> Option<NaiveTime> {
282 self.as_cr().map(|dt| dt.time())
283 }
284
285 #[inline(always)]
291 pub fn year(&self) -> Option<i32> {
292 self.as_cr().map(|dt| dt.year())
293 }
294
295 #[inline(always)]
301 pub fn day(&self) -> Option<usize> {
302 self.as_cr().map(|dt| dt.day() as usize)
303 }
304
305 #[inline(always)]
311 pub fn month(&self) -> Option<usize> {
312 self.as_cr().map(|dt| dt.month() as usize)
313 }
314
315 #[inline(always)]
321 pub fn hour(&self) -> Option<usize> {
322 self.as_cr().map(|dt| dt.hour() as usize)
323 }
324
325 #[inline(always)]
331 pub fn minute(&self) -> Option<usize> {
332 self.as_cr().map(|dt| dt.minute() as usize)
333 }
334
335 #[inline(always)]
341 pub fn second(&self) -> Option<usize> {
342 self.as_cr().map(|dt| dt.second() as usize)
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 #[allow(unused_assignments, unused_variables)]
352 fn test_parse_datetime() -> TResult<()> {
353 let mut dt: DateTime = "2020-01-01 00:00:00".parse()?;
354 dt = DateTime::parse("2020-01-01", Some("%Y-%m-%d"))?;
355 dt = "2020-01-01".parse()?;
356 dt = "20220101".parse()?;
357 dt = "2021/02/03".parse()?;
358 Ok(())
359 }
360
361 #[test]
362 fn test_datetime_components() -> TResult<()> {
363 let dt: DateTime = "2023-05-15 14:30:45".parse()?;
364 assert_eq!(dt.year(), Some(2023));
365 assert_eq!(dt.month(), Some(5));
366 assert_eq!(dt.day(), Some(15));
367 assert_eq!(dt.hour(), Some(14));
368 assert_eq!(dt.minute(), Some(30));
369 assert_eq!(dt.second(), Some(45));
370 Ok(())
371 }
372
373 #[test]
374 fn test_nat_datetime() {
375 let nat_dt: DateTime = DateTime::nat();
376 assert!(nat_dt.is_nat());
377 assert_eq!(nat_dt.year(), None);
378 assert_eq!(nat_dt.month(), None);
379 assert_eq!(nat_dt.day(), None);
380 assert_eq!(nat_dt.hour(), None);
381 assert_eq!(nat_dt.minute(), None);
382 assert_eq!(nat_dt.second(), None);
383 }
384
385 #[test]
386 fn test_invalid_datetime_parse() {
387 assert!("invalid date".parse::<DateTime>().is_err());
388 }
389}