1use chrono::{DateTime, SecondsFormat, Utc};
5
6pub fn now_rfc3339_utc_nanos() -> String {
12 Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true)
13}
14
15pub fn rfc3339_utc_nanos(unix_secs: i64) -> Option<String> {
21 DateTime::from_timestamp(unix_secs, 0).map(|dt| dt.to_rfc3339_opts(SecondsFormat::Nanos, true))
22}
23
24pub fn parse_iso_timestamp(ts: &str) -> i64 {
45 if ts.is_empty() {
46 return 0;
47 }
48
49 if let Ok(dt) = DateTime::parse_from_rfc3339(ts) {
51 return dt.timestamp_millis();
52 }
53
54 let formats = [
56 "%Y-%m-%dT%H:%M:%S%.3fZ",
57 "%Y-%m-%dT%H:%M:%S%.fZ",
58 "%Y-%m-%dT%H:%M:%SZ",
59 ];
60
61 for format in &formats {
62 if let Ok(dt) = DateTime::parse_from_str(ts, format) {
63 return dt.timestamp_millis();
64 }
65 }
66
67 0
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn rfc3339_utc_nanos_round_trips_and_matches_the_now_shape() {
76 let stamp = rfc3339_utc_nanos(1_785_058_368).expect("in range");
77 assert_eq!(stamp, "2026-07-26T09:32:48.000000000Z");
78 assert_eq!(parse_iso_timestamp(&stamp), 1_785_058_368_000);
80 assert_eq!(
81 stamp.len(),
82 now_rfc3339_utc_nanos().len(),
83 "both stamps must be the fixed-width nanosecond form"
84 );
85 assert!(rfc3339_utc_nanos(i64::MAX).is_none());
87 }
88
89 #[test]
90 fn test_parse_iso_timestamp_rfc3339() {
91 let ts = "2024-01-15T10:30:45.123Z";
93 let result = parse_iso_timestamp(ts);
94 assert!(result > 0);
95
96 assert!(result > 1_700_000_000_000); assert!(result < 1_800_000_000_000); }
100
101 #[test]
102 fn test_parse_iso_timestamp_with_timezone() {
103 let ts = "2024-01-15T10:30:45.123+08:00";
105 let result = parse_iso_timestamp(ts);
106 assert!(result > 0);
107 }
108
109 #[test]
110 fn test_parse_iso_timestamp_no_millis() {
111 let ts = "2024-01-15T10:30:45Z";
113 let result = parse_iso_timestamp(ts);
114 assert!(result > 0);
115 }
116
117 #[test]
118 fn test_parse_iso_timestamp_fallback_formats() {
119 let ts1 = "2024-01-15T10:30:45.123Z";
121 let result1 = parse_iso_timestamp(ts1);
122 assert!(result1 > 0);
123
124 let ts2 = "2024-01-15T10:30:45.123456Z";
126 let result2 = parse_iso_timestamp(ts2);
127 assert!(result2 > 0);
128
129 let ts3 = "2024-01-15T10:30:45Z";
131 let result3 = parse_iso_timestamp(ts3);
132 assert!(result3 > 0);
133 }
134
135 #[test]
136 fn test_parse_iso_timestamp_empty() {
137 let result = parse_iso_timestamp("");
139 assert_eq!(result, 0);
140 }
141
142 #[test]
143 fn test_parse_iso_timestamp_invalid() {
144 let result = parse_iso_timestamp("not a timestamp");
146 assert_eq!(result, 0);
147
148 let result = parse_iso_timestamp("2024-13-45");
149 assert_eq!(result, 0);
150
151 let result = parse_iso_timestamp("invalid-date-time");
152 assert_eq!(result, 0);
153 }
154
155 #[test]
156 fn test_parse_iso_timestamp_different_years() {
157 let ts_2020 = "2020-06-15T12:00:00Z";
159 let ts_2024 = "2024-06-15T12:00:00Z";
160
161 let result_2020 = parse_iso_timestamp(ts_2020);
162 let result_2024 = parse_iso_timestamp(ts_2024);
163
164 assert!(result_2020 > 0);
165 assert!(result_2024 > 0);
166 assert!(result_2024 > result_2020);
167 }
168
169 #[test]
170 fn test_parse_iso_timestamp_milliseconds_precision() {
171 let ts1 = "2024-01-15T10:30:45.000Z";
173 let ts2 = "2024-01-15T10:30:45.999Z";
174
175 let result1 = parse_iso_timestamp(ts1);
176 let result2 = parse_iso_timestamp(ts2);
177
178 assert!(result1 > 0);
179 assert!(result2 > 0);
180 assert!(result2 > result1);
182 assert!(result2 - result1 < 1000);
183 }
184
185 #[test]
186 fn test_parse_iso_timestamp_same_time() {
187 let ts = "2024-01-15T10:30:45.123Z";
189 let result1 = parse_iso_timestamp(ts);
190 let result2 = parse_iso_timestamp(ts);
191
192 assert_eq!(result1, result2);
193 }
194
195 #[test]
196 fn test_parse_iso_timestamp_edge_cases() {
197 let ts1 = "2024-01-01T00:00:00Z";
201 let result1 = parse_iso_timestamp(ts1);
202 assert!(result1 > 0);
203
204 let ts2 = "2024-12-31T23:59:59Z";
206 let result2 = parse_iso_timestamp(ts2);
207 assert!(result2 > 0);
208 assert!(result2 > result1);
209
210 let ts3 = "2024-02-29T12:00:00Z";
212 let result3 = parse_iso_timestamp(ts3);
213 assert!(result3 > 0);
214 }
215
216 #[test]
217 fn test_parse_iso_timestamp_negative_timezone() {
218 let ts = "2024-01-15T10:30:45.123-05:00";
220 let result = parse_iso_timestamp(ts);
221 assert!(result > 0);
222 }
223
224 #[test]
225 fn test_parse_iso_timestamp_midnight() {
226 let ts = "2024-01-15T00:00:00.000Z";
228 let result = parse_iso_timestamp(ts);
229 assert!(result > 0);
230 }
231
232 #[test]
233 fn test_parse_iso_timestamp_noon() {
234 let ts = "2024-01-15T12:00:00.000Z";
236 let result = parse_iso_timestamp(ts);
237 assert!(result > 0);
238 }
239
240 #[test]
241 fn test_parse_iso_timestamp_whitespace() {
242 let result = parse_iso_timestamp(" 2024-01-15T10:30:45Z ");
244 assert_eq!(result, 0);
245 }
246
247 #[test]
248 fn test_parse_iso_timestamp_partial() {
249 let result = parse_iso_timestamp("2024-01-15");
251 assert_eq!(result, 0);
252
253 let result = parse_iso_timestamp("2024-01-15T10:30");
254 assert_eq!(result, 0);
255 }
256
257 #[test]
258 fn test_parse_iso_timestamp_ordering() {
259 let timestamps = [
261 "2024-01-15T10:00:00Z",
262 "2024-01-15T11:00:00Z",
263 "2024-01-15T12:00:00Z",
264 "2024-01-15T13:00:00Z",
265 ];
266
267 let results: Vec<i64> = timestamps
268 .iter()
269 .map(|ts| parse_iso_timestamp(ts))
270 .collect();
271
272 assert!(results.iter().all(|&r| r > 0));
274
275 for i in 1..results.len() {
277 assert!(results[i] > results[i - 1]);
278 }
279 }
280}