api_tools/value_objects/
datetime.rs

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