Skip to main content

threads_rs/types/
time.rs

1use chrono::{DateTime, Utc};
2use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
3use std::fmt;
4
5/// Custom time type that handles various Threads API timestamp formats.
6///
7/// The API may return timestamps in several formats. This type tries them all
8/// during deserialization and always serialises to RFC 3339.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct ThreadsTime(pub DateTime<Utc>);
11
12impl ThreadsTime {
13    /// Create a new `ThreadsTime` from a `DateTime<Utc>`.
14    pub fn new(dt: DateTime<Utc>) -> Self {
15        Self(dt)
16    }
17}
18
19impl fmt::Display for ThreadsTime {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        write!(f, "{}", self.0.to_rfc3339())
22    }
23}
24
25impl Serialize for ThreadsTime {
26    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
27        serializer.serialize_str(&self.0.to_rfc3339())
28    }
29}
30
31impl<'de> Deserialize<'de> for ThreadsTime {
32    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
33        let s = String::deserialize(deserializer)?;
34
35        // Threads API format: "2006-01-02T15:04:05+0000"
36        if let Ok(dt) = DateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S%z") {
37            return Ok(ThreadsTime(dt.with_timezone(&Utc)));
38        }
39
40        // ISO 8601 UTC: "2006-01-02T15:04:05Z"
41        if let Ok(dt) = s.parse::<DateTime<Utc>>() {
42            return Ok(ThreadsTime(dt));
43        }
44
45        // RFC 3339
46        if let Ok(dt) = DateTime::parse_from_rfc3339(&s) {
47            return Ok(ThreadsTime(dt.with_timezone(&Utc)));
48        }
49
50        Err(serde::de::Error::custom(format!(
51            "unable to parse timestamp: {s}"
52        )))
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn test_threads_api_format() {
62        let json = r#""2024-01-15T10:30:00+0000""#;
63        let t: ThreadsTime = serde_json::from_str(json).unwrap();
64        assert_eq!(t.0.year(), 2024);
65        assert_eq!(t.0.month(), 1);
66        assert_eq!(t.0.day(), 15);
67    }
68
69    #[test]
70    fn test_iso8601_utc() {
71        let json = r#""2024-06-01T12:00:00Z""#;
72        let t: ThreadsTime = serde_json::from_str(json).unwrap();
73        assert_eq!(t.0.year(), 2024);
74        assert_eq!(t.0.month(), 6);
75    }
76
77    #[test]
78    fn test_rfc3339() {
79        let json = r#""2024-03-20T08:15:30+05:30""#;
80        let t: ThreadsTime = serde_json::from_str(json).unwrap();
81        assert_eq!(t.0.year(), 2024);
82    }
83
84    #[test]
85    fn test_serialize_roundtrip() {
86        let json = r#""2024-01-15T10:30:00+0000""#;
87        let t: ThreadsTime = serde_json::from_str(json).unwrap();
88        let serialized = serde_json::to_string(&t).unwrap();
89        // Should serialize to RFC 3339
90        let back: ThreadsTime = serde_json::from_str(&serialized).unwrap();
91        assert_eq!(t, back);
92    }
93
94    #[test]
95    fn test_display() {
96        let json = r#""2024-01-01T00:00:00Z""#;
97        let t: ThreadsTime = serde_json::from_str(json).unwrap();
98        let s = t.to_string();
99        assert!(s.contains("2024-01-01"));
100    }
101
102    #[test]
103    fn test_invalid_timestamp() {
104        let json = r#""not-a-timestamp""#;
105        let result = serde_json::from_str::<ThreadsTime>(json);
106        assert!(result.is_err());
107    }
108
109    use chrono::Datelike;
110}