api_tools/value_objects/
datetime.rs

1//! Datetime represents a date and time value in the UTC timezone.
2
3use chrono::{DateTime, TimeDelta, Utc};
4use serde::Deserialize;
5use std::fmt::{Display, Formatter};
6use std::ops::Add;
7use thiserror::Error;
8
9/// UTC Datetime possible errors
10#[derive(Debug, Clone, PartialEq, Error)]
11pub enum UtcDateTimeError {
12    #[error("Invalid date time: {0}")]
13    InvalidDateTime(String),
14}
15
16/// Date time with UTC timezone
17#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Deserialize)]
18pub struct UtcDateTime {
19    value: DateTime<Utc>,
20}
21
22impl UtcDateTime {
23    /// Create a new date time for now
24    pub fn now() -> Self {
25        Self { value: Utc::now() }
26    }
27
28    /// Create a new date time
29    pub fn new(value: DateTime<Utc>) -> Self {
30        Self { value }
31    }
32
33    /// Create a new date time from RFC3339 string
34    ///
35    /// # Example
36    /// ```rust
37    /// use api_tools::value_objects::datetime::UtcDateTime;
38    ///
39    /// let datetime = UtcDateTime::from_rfc3339("2024-08-28T12:00:00Z");
40    /// assert_eq!(datetime.unwrap().to_string(), "2024-08-28T12:00:00Z".to_owned());
41    ///
42    /// let invalid_datetime = UtcDateTime::from_rfc3339("2024-08-T12:00:00Z");
43    /// ```
44    pub fn from_rfc3339(value: &str) -> Result<Self, UtcDateTimeError> {
45        let dt = DateTime::parse_from_rfc3339(value)
46            .map_err(|e| UtcDateTimeError::InvalidDateTime(format!("{e}: {value}")))?;
47
48        Ok(Self {
49            value: dt.with_timezone(&Utc),
50        })
51    }
52
53    /// Get timestamp value
54    pub fn timestamp(&self) -> i64 {
55        self.value.timestamp()
56    }
57
58    /// Get date time value
59    pub fn value(&self) -> DateTime<Utc> {
60        self.value
61    }
62
63    /// Create a new date time from a timestamp
64    pub fn add(&self, rhs: TimeDelta) -> Self {
65        Self {
66            value: self.value.add(rhs),
67        }
68    }
69}
70
71impl From<DateTime<Utc>> for UtcDateTime {
72    fn from(value: DateTime<Utc>) -> Self {
73        Self { value }
74    }
75}
76
77impl Display for UtcDateTime {
78    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
79        write!(f, "{}", self.value.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))
80    }
81}
82
83#[cfg(test)]
84mod test {
85    use super::*;
86
87    #[test]
88    fn test_utc_date_time_display() {
89        let dt = DateTime::parse_from_rfc3339("2024-08-28T12:00:00Z")
90            .unwrap()
91            .with_timezone(&Utc);
92        let datetime = UtcDateTime::from(dt);
93
94        assert_eq!(datetime.to_string(), "2024-08-28T12:00:00Z");
95    }
96
97    #[test]
98    fn test_from_rfc3339() {
99        let datetime = UtcDateTime::from_rfc3339("2024-08-28T12:00:00Z");
100        assert!(datetime.is_ok());
101        assert_eq!(datetime.unwrap().to_string(), "2024-08-28T12:00:00Z".to_owned());
102
103        let invalid_datetime = UtcDateTime::from_rfc3339("2024-08-T12:00:00Z");
104        assert!(invalid_datetime.is_err());
105    }
106
107    #[test]
108    fn test_value() {
109        let dt = DateTime::parse_from_rfc3339("2024-08-28T12:00:00Z")
110            .unwrap()
111            .with_timezone(&Utc);
112        let datetime = UtcDateTime::from(dt);
113
114        assert_eq!(datetime.value(), dt);
115    }
116
117    #[test]
118    fn test_timestamp() {
119        let dt = DateTime::parse_from_rfc3339("2024-08-28T12:00:00Z")
120            .unwrap()
121            .with_timezone(&Utc);
122        let datetime = UtcDateTime::from(dt);
123
124        assert_eq!(datetime.timestamp(), 1724846400);
125    }
126
127    #[test]
128    fn test_add() {
129        let dt = DateTime::parse_from_rfc3339("2024-08-28T12:00:00Z")
130            .unwrap()
131            .with_timezone(&Utc);
132        let datetime = UtcDateTime::from(dt);
133        let new_datetime = datetime.add(TimeDelta::seconds(3600));
134        assert_eq!(new_datetime.to_string(), "2024-08-28T13:00:00Z".to_owned());
135    }
136}