datadog_api/
timestamp.rs

1//! Type-safe timestamp wrappers for Datadog API
2//!
3//! Provides newtype wrappers to prevent mixing up different time units.
4
5use serde::{Deserialize, Serialize};
6use std::fmt;
7use std::time::{Duration, SystemTime, UNIX_EPOCH};
8
9/// Unix timestamp in seconds since epoch.
10///
11/// Used for most Datadog API endpoints that accept time ranges.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
13#[serde(transparent)]
14pub struct TimestampSecs(pub i64);
15
16impl TimestampSecs {
17    /// Create a timestamp for the current time.
18    #[must_use]
19    pub fn now() -> Self {
20        let duration = SystemTime::now()
21            .duration_since(UNIX_EPOCH)
22            .unwrap_or_default();
23        Self(duration.as_secs() as i64)
24    }
25
26    /// Create a timestamp from milliseconds.
27    #[must_use]
28    pub const fn from_millis(ms: i64) -> Self {
29        Self(ms / 1000)
30    }
31
32    /// Convert to milliseconds.
33    #[must_use]
34    pub const fn as_millis(&self) -> i64 {
35        self.0 * 1000
36    }
37
38    /// Create a timestamp N seconds ago from now.
39    #[must_use]
40    pub fn seconds_ago(seconds: i64) -> Self {
41        Self(Self::now().0 - seconds)
42    }
43
44    /// Create a timestamp N minutes ago from now.
45    #[must_use]
46    pub fn minutes_ago(minutes: i64) -> Self {
47        Self::seconds_ago(minutes * 60)
48    }
49
50    /// Create a timestamp N hours ago from now.
51    #[must_use]
52    pub fn hours_ago(hours: i64) -> Self {
53        Self::seconds_ago(hours * 3600)
54    }
55
56    /// Create a timestamp N days ago from now.
57    #[must_use]
58    pub fn days_ago(days: i64) -> Self {
59        Self::seconds_ago(days * 86400)
60    }
61
62    /// Get the raw seconds value.
63    #[must_use]
64    pub const fn as_secs(&self) -> i64 {
65        self.0
66    }
67}
68
69impl fmt::Display for TimestampSecs {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        write!(f, "{}", self.0)
72    }
73}
74
75impl From<i64> for TimestampSecs {
76    fn from(secs: i64) -> Self {
77        Self(secs)
78    }
79}
80
81impl From<TimestampSecs> for i64 {
82    fn from(ts: TimestampSecs) -> Self {
83        ts.0
84    }
85}
86
87impl From<Duration> for TimestampSecs {
88    fn from(duration: Duration) -> Self {
89        Self(duration.as_secs() as i64)
90    }
91}
92
93/// Unix timestamp in milliseconds since epoch.
94///
95/// Used for high-precision timing in APM traces.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
97#[serde(transparent)]
98pub struct TimestampMillis(pub i64);
99
100impl TimestampMillis {
101    /// Create a timestamp for the current time.
102    #[must_use]
103    pub fn now() -> Self {
104        let duration = SystemTime::now()
105            .duration_since(UNIX_EPOCH)
106            .unwrap_or_default();
107        Self(duration.as_millis() as i64)
108    }
109
110    /// Convert to seconds.
111    #[must_use]
112    pub const fn as_secs(&self) -> TimestampSecs {
113        TimestampSecs(self.0 / 1000)
114    }
115
116    /// Get the raw milliseconds value.
117    #[must_use]
118    pub const fn as_millis(&self) -> i64 {
119        self.0
120    }
121}
122
123impl fmt::Display for TimestampMillis {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(f, "{}", self.0)
126    }
127}
128
129impl From<i64> for TimestampMillis {
130    fn from(ms: i64) -> Self {
131        Self(ms)
132    }
133}
134
135impl From<TimestampMillis> for i64 {
136    fn from(ts: TimestampMillis) -> Self {
137        ts.0
138    }
139}
140
141impl From<TimestampSecs> for TimestampMillis {
142    fn from(ts: TimestampSecs) -> Self {
143        Self(ts.0 * 1000)
144    }
145}
146
147/// Unix timestamp in nanoseconds since epoch.
148///
149/// Used for APM span timing where nanosecond precision is needed.
150#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
151#[serde(transparent)]
152pub struct TimestampNanos(pub i64);
153
154impl TimestampNanos {
155    /// Create a timestamp for the current time.
156    #[must_use]
157    pub fn now() -> Self {
158        let duration = SystemTime::now()
159            .duration_since(UNIX_EPOCH)
160            .unwrap_or_default();
161        Self(duration.as_nanos() as i64)
162    }
163
164    /// Convert to seconds.
165    #[must_use]
166    pub const fn as_secs(&self) -> TimestampSecs {
167        TimestampSecs(self.0 / 1_000_000_000)
168    }
169
170    /// Convert to milliseconds.
171    #[must_use]
172    pub const fn as_millis(&self) -> TimestampMillis {
173        TimestampMillis(self.0 / 1_000_000)
174    }
175
176    /// Get the raw nanoseconds value.
177    #[must_use]
178    pub const fn as_nanos(&self) -> i64 {
179        self.0
180    }
181}
182
183impl fmt::Display for TimestampNanos {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        write!(f, "{}", self.0)
186    }
187}
188
189impl From<i64> for TimestampNanos {
190    fn from(ns: i64) -> Self {
191        Self(ns)
192    }
193}
194
195impl From<TimestampNanos> for i64 {
196    fn from(ts: TimestampNanos) -> Self {
197        ts.0
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_timestamp_secs_now() {
207        let ts = TimestampSecs::now();
208        // Should be after year 2020 (1577836800)
209        assert!(ts.0 > 1577836800);
210    }
211
212    #[test]
213    fn test_timestamp_secs_from_millis() {
214        let ts = TimestampSecs::from_millis(1234567890123);
215        assert_eq!(ts.0, 1234567890);
216    }
217
218    #[test]
219    fn test_timestamp_secs_as_millis() {
220        let ts = TimestampSecs(1234567890);
221        assert_eq!(ts.as_millis(), 1234567890000);
222    }
223
224    #[test]
225    fn test_timestamp_secs_ago() {
226        let now = TimestampSecs::now();
227        let one_hour_ago = TimestampSecs::hours_ago(1);
228        assert_eq!(now.0 - one_hour_ago.0, 3600);
229    }
230
231    #[test]
232    fn test_timestamp_millis_to_secs() {
233        let ms = TimestampMillis(1234567890123);
234        assert_eq!(ms.as_secs().0, 1234567890);
235    }
236
237    #[test]
238    fn test_timestamp_nanos_conversions() {
239        let ns = TimestampNanos(1234567890123456789);
240        assert_eq!(ns.as_secs().0, 1234567890);
241        assert_eq!(ns.as_millis().0, 1234567890123);
242    }
243
244    #[test]
245    fn test_timestamp_secs_serialization() {
246        let ts = TimestampSecs(1234567890);
247        let json = serde_json::to_string(&ts).unwrap();
248        assert_eq!(json, "1234567890");
249
250        let deserialized: TimestampSecs = serde_json::from_str(&json).unwrap();
251        assert_eq!(ts, deserialized);
252    }
253
254    #[test]
255    fn test_timestamp_from_i64() {
256        let ts: TimestampSecs = 1234567890i64.into();
257        assert_eq!(ts.0, 1234567890);
258    }
259
260    #[test]
261    fn test_timestamp_into_i64() {
262        let ts = TimestampSecs(1234567890);
263        let value: i64 = ts.into();
264        assert_eq!(value, 1234567890);
265    }
266
267    #[test]
268    fn test_timestamp_display() {
269        let ts = TimestampSecs(1234567890);
270        assert_eq!(format!("{ts}"), "1234567890");
271    }
272
273    #[test]
274    fn test_timestamp_ordering() {
275        let earlier = TimestampSecs(1000);
276        let later = TimestampSecs(2000);
277        assert!(earlier < later);
278    }
279}