Skip to main content

rosetta_utc/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use chrono::NaiveDateTime;
4use core::str::FromStr;
5
6pub mod diesel_impls;
7
8#[repr(transparent)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[cfg_attr(
11    feature = "diesel",
12    derive(diesel::expression::AsExpression, diesel::deserialize::FromSqlRow)
13)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature="diesel", diesel(sql_type = crate::diesel_impls::TimestampUTC))]
16/// A wrapper around the `chrono` crate's `DateTime<Utc>` type.
17pub struct TimestampUTC(chrono::DateTime<chrono::Utc>);
18
19impl TimestampUTC {
20    #[must_use]
21    /// Returns the current time in UTC.
22    ///
23    /// # Examples
24    ///
25    /// ```
26    /// use rosetta_utc::TimestampUTC;
27    ///
28    /// let now = TimestampUTC::now();
29    /// ```
30    pub fn now() -> Self {
31        Self(chrono::Utc::now())
32    }
33}
34
35impl Default for TimestampUTC {
36    fn default() -> Self {
37        Self::now()
38    }
39}
40
41impl From<chrono::DateTime<chrono::Utc>> for TimestampUTC {
42    fn from(value: chrono::DateTime<chrono::Utc>) -> Self {
43        Self(value)
44    }
45}
46
47impl From<TimestampUTC> for chrono::DateTime<chrono::Utc> {
48    fn from(value: TimestampUTC) -> Self {
49        value.0
50    }
51}
52
53impl AsRef<chrono::DateTime<chrono::Utc>> for TimestampUTC {
54    fn as_ref(&self) -> &chrono::DateTime<chrono::Utc> {
55        &self.0
56    }
57}
58
59impl AsMut<chrono::DateTime<chrono::Utc>> for TimestampUTC {
60    fn as_mut(&mut self) -> &mut chrono::DateTime<chrono::Utc> {
61        &mut self.0
62    }
63}
64
65impl From<NaiveDateTime> for TimestampUTC {
66    fn from(value: NaiveDateTime) -> Self {
67        use chrono::TimeZone;
68        Self(chrono::Utc.from_utc_datetime(&value))
69    }
70}
71
72impl core::ops::Deref for TimestampUTC {
73    type Target = chrono::DateTime<chrono::Utc>;
74
75    fn deref(&self) -> &Self::Target {
76        &self.0
77    }
78}
79
80impl core::ops::DerefMut for TimestampUTC {
81    fn deref_mut(&mut self) -> &mut Self::Target {
82        &mut self.0
83    }
84}
85
86impl core::fmt::Display for TimestampUTC {
87    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
88        self.0.fmt(f)
89    }
90}
91
92impl FromStr for TimestampUTC {
93    type Err = chrono::ParseError;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        Ok(Self(
97            chrono::DateTime::parse_from_rfc3339(s)?.with_timezone(&chrono::Utc),
98        ))
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105    use std::cmp::Ordering;
106    use std::collections::HashSet;
107
108    #[test]
109    fn test_default() {
110        let t = TimestampUTC::default();
111        // Just checking it runs, harder to check equality with time moving
112        assert!(t.timestamp() > 0);
113    }
114
115    #[test]
116    fn test_from_str() {
117        let s = "2023-10-27T10:00:00+00:00";
118        let t = TimestampUTC::from_str(s).unwrap();
119        assert_eq!(t.to_rfc3339(), s);
120    }
121
122    #[test]
123    fn test_from_conversions() {
124        let inner = chrono::Utc::now();
125        let wrapper: TimestampUTC = inner.into();
126        assert_eq!(wrapper.0, inner);
127
128        let back: chrono::DateTime<chrono::Utc> = wrapper.into();
129        assert_eq!(back, inner);
130    }
131
132    #[test]
133    fn test_naive_conversion() {
134        let naive =
135            NaiveDateTime::parse_from_str("2023-10-27 10:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
136        let t: TimestampUTC = naive.into();
137        assert_eq!(
138            t.format("%Y-%m-%d %H:%M:%S").to_string(),
139            "2023-10-27 10:00:00"
140        );
141    }
142
143    #[test]
144    fn test_as_ref_as_mut() {
145        let mut t = TimestampUTC::now();
146
147        let r: &chrono::DateTime<chrono::Utc> = t.as_ref();
148        assert_eq!(*r, t.0);
149
150        let m: &mut chrono::DateTime<chrono::Utc> = t.as_mut();
151        *m = chrono::Utc::now(); // Modify
152    }
153
154    #[test]
155    fn test_deref_deref_mut() {
156        let mut t = TimestampUTC::now();
157
158        // Deref
159        assert!(t.timestamp() > 0);
160
161        // DerefMut (modifying internally)
162        // using a specific date
163        let old_time = t;
164        *t = old_time.0 - chrono::Duration::days(1);
165        assert!(*t < *old_time);
166    }
167
168    #[test]
169    fn test_display() {
170        let s = "2023-10-27T10:00:00+00:00";
171        let t = TimestampUTC::from_str(s).unwrap();
172        assert_eq!(format!("{t}"), "2023-10-27 10:00:00 UTC");
173    }
174
175    #[test]
176    fn test_standard_traits() {
177        let t1 = TimestampUTC::now();
178        let t2 = Clone::clone(&t1); // Clone
179        let t3 = t1; // Copy
180
181        assert_eq!(t1, t2); // PartialEq
182        assert_eq!(t1, t3); // PartialEq
183        assert!(t1 == t2); // Eq check implicitly
184
185        let mut set = HashSet::new();
186        set.insert(t1); // Hash
187        assert!(set.contains(&t2));
188    }
189
190    #[test]
191    fn test_ord() {
192        let t1 = TimestampUTC::from_str("2023-01-01T00:00:00+00:00").unwrap();
193        let t2 = TimestampUTC::from_str("2023-01-02T00:00:00+00:00").unwrap();
194
195        assert_eq!(t1.cmp(&t2), Ordering::Less); // Ord
196        assert!(t1 < t2); // PartialOrd
197    }
198}