1use crate::error::{ParseError, Result};
31use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
32
33#[inline]
44pub fn milliseconds() -> i64 {
45 Utc::now().timestamp_millis()
46}
47
48#[inline]
59pub fn seconds() -> i64 {
60 milliseconds() / 1000
61}
62
63#[inline]
74pub fn microseconds() -> i64 {
75 Utc::now().timestamp_micros()
76}
77
78pub fn iso8601(timestamp: i64) -> Result<String> {
98 if timestamp <= 0 {
99 return Err(ParseError::timestamp("Invalid timestamp: must be positive").into());
100 }
101
102 let secs = timestamp / 1000;
104 let nsecs = ((timestamp % 1000) * 1_000_000) as u32;
105
106 let datetime = DateTime::<Utc>::from_timestamp(secs, nsecs)
107 .ok_or_else(|| ParseError::timestamp_owned(format!("Invalid timestamp: {}", timestamp)))?;
108
109 Ok(datetime.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string())
110}
111
112pub fn parse_date(datetime: &str) -> Result<i64> {
138 if datetime.is_empty() {
139 return Err(ParseError::timestamp("Empty datetime string").into());
140 }
141
142 let formats = [
144 "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S%.3fZ", "%Y-%m-%dT%H:%M:%SZ", "%Y-%m-%dT%H:%M:%S%.3f", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S%.3f", ];
151
152 for format in &formats {
154 if let Ok(naive) = NaiveDateTime::parse_from_str(datetime, format) {
155 let dt = Utc.from_utc_datetime(&naive);
156 return Ok(dt.timestamp_millis());
157 }
158 }
159
160 if let Ok(dt) = DateTime::parse_from_rfc3339(datetime) {
162 return Ok(dt.timestamp_millis());
163 }
164
165 Err(ParseError::timestamp_owned(format!("Unable to parse datetime: {}", datetime)).into())
166}
167
168pub fn parse_iso8601(datetime: &str) -> Result<i64> {
193 if datetime.is_empty() {
194 return Err(ParseError::timestamp("Empty datetime string").into());
195 }
196
197 let cleaned = if datetime.contains("+0") {
199 datetime.split('+').next().unwrap_or(datetime)
200 } else {
201 datetime
202 };
203
204 if let Ok(dt) = DateTime::parse_from_rfc3339(cleaned) {
206 return Ok(dt.timestamp_millis());
207 }
208
209 let formats = [
211 "%Y-%m-%dT%H:%M:%S%.3f", "%Y-%m-%d %H:%M:%S%.3f", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S", ];
216
217 for format in &formats {
218 if let Ok(naive) = NaiveDateTime::parse_from_str(cleaned, format) {
219 let dt = Utc.from_utc_datetime(&naive);
220 return Ok(dt.timestamp_millis());
221 }
222 }
223
224 Err(
225 ParseError::timestamp_owned(format!("Unable to parse ISO 8601 datetime: {}", datetime))
226 .into(),
227 )
228}
229
230pub fn ymdhms(timestamp: i64, separator: Option<&str>) -> Result<String> {
254 if timestamp <= 0 {
255 return Err(ParseError::timestamp("Invalid timestamp: must be positive").into());
256 }
257
258 let sep = separator.unwrap_or(" ");
259 let secs = timestamp / 1000;
260 let nsecs = ((timestamp % 1000) * 1_000_000) as u32;
261
262 let datetime = DateTime::<Utc>::from_timestamp(secs, nsecs)
263 .ok_or_else(|| ParseError::timestamp_owned(format!("Invalid timestamp: {}", timestamp)))?;
264
265 Ok(format!(
266 "{}{}{}",
267 datetime.format("%Y-%m-%d"),
268 sep,
269 datetime.format("%H:%M:%S")
270 ))
271}
272
273pub fn yyyymmdd(timestamp: i64, separator: Option<&str>) -> Result<String> {
293 if timestamp <= 0 {
294 return Err(ParseError::timestamp("Invalid timestamp: must be positive").into());
295 }
296
297 let sep = separator.unwrap_or("-");
298 let secs = timestamp / 1000;
299 let nsecs = ((timestamp % 1000) * 1_000_000) as u32;
300
301 let datetime = DateTime::<Utc>::from_timestamp(secs, nsecs)
302 .ok_or_else(|| ParseError::timestamp_owned(format!("Invalid timestamp: {}", timestamp)))?;
303
304 Ok(format!(
305 "{}{}{}{}{}",
306 datetime.format("%Y"),
307 sep,
308 datetime.format("%m"),
309 sep,
310 datetime.format("%d")
311 ))
312}
313
314pub fn yymmdd(timestamp: i64, separator: Option<&str>) -> Result<String> {
334 if timestamp <= 0 {
335 return Err(ParseError::timestamp("Invalid timestamp: must be positive").into());
336 }
337
338 let sep = separator.unwrap_or("");
339 let secs = timestamp / 1000;
340 let nsecs = ((timestamp % 1000) * 1_000_000) as u32;
341
342 let datetime = DateTime::<Utc>::from_timestamp(secs, nsecs)
343 .ok_or_else(|| ParseError::timestamp_owned(format!("Invalid timestamp: {}", timestamp)))?;
344
345 Ok(format!(
346 "{}{}{}{}{}",
347 datetime.format("%y"),
348 sep,
349 datetime.format("%m"),
350 sep,
351 datetime.format("%d")
352 ))
353}
354
355#[inline]
367pub fn ymd(timestamp: i64, separator: Option<&str>) -> Result<String> {
368 yyyymmdd(timestamp, separator)
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_milliseconds() {
377 let now = milliseconds();
378 assert!(now > 1_600_000_000_000); assert!(now < 2_000_000_000_000); }
381
382 #[test]
383 fn test_seconds() {
384 let now = seconds();
385 assert!(now > 1_600_000_000); assert!(now < 2_000_000_000); }
388
389 #[test]
390 fn test_microseconds() {
391 let now = microseconds();
392 assert!(now > 1_600_000_000_000_000); }
394
395 #[test]
396 fn test_iso8601() {
397 let timestamp = 1704110400000; let result = iso8601(timestamp).unwrap();
399 assert_eq!(result, "2024-01-01T12:00:00.000Z");
400 }
401
402 #[test]
403 fn test_iso8601_with_millis() {
404 let timestamp = 1704110400123; let result = iso8601(timestamp).unwrap();
406 assert_eq!(result, "2024-01-01T12:00:00.123Z");
407 }
408
409 #[test]
410 fn test_iso8601_invalid() {
411 let result = iso8601(-1);
412 assert!(result.is_err());
413 }
414
415 #[test]
416 fn test_parse_date_iso8601() {
417 let result = parse_date("2024-01-01T12:00:00.000Z").unwrap();
418 assert_eq!(result, 1704110400000);
419 }
420
421 #[test]
422 fn test_parse_date_space_separated() {
423 let result = parse_date("2024-01-01 12:00:00").unwrap();
424 assert_eq!(result, 1704110400000);
425 }
426
427 #[test]
428 fn test_parse_date_without_timezone() {
429 let result = parse_date("2024-01-01T12:00:00.389").unwrap();
430 assert_eq!(result, 1704110400389);
431 }
432
433 #[test]
434 fn test_parse_iso8601() {
435 let result = parse_iso8601("2024-01-01T12:00:00.000Z").unwrap();
436 assert_eq!(result, 1704110400000);
437 }
438
439 #[test]
440 fn test_parse_iso8601_with_offset() {
441 let result = parse_iso8601("2024-01-01T12:00:00+00:00").unwrap();
442 assert_eq!(result, 1704110400000);
443 }
444
445 #[test]
446 fn test_parse_iso8601_space_separated() {
447 let result = parse_iso8601("2024-01-01 12:00:00.389").unwrap();
448 assert_eq!(result, 1704110400389);
449 }
450
451 #[test]
452 fn test_ymdhms_default_separator() {
453 let timestamp = 1704110400000;
454 let result = ymdhms(timestamp, None).unwrap();
455 assert_eq!(result, "2024-01-01 12:00:00");
456 }
457
458 #[test]
459 fn test_ymdhms_custom_separator() {
460 let timestamp = 1704110400000;
461 let result = ymdhms(timestamp, Some("T")).unwrap();
462 assert_eq!(result, "2024-01-01T12:00:00");
463 }
464
465 #[test]
466 fn test_yyyymmdd_default_separator() {
467 let timestamp = 1704110400000;
468 let result = yyyymmdd(timestamp, None).unwrap();
469 assert_eq!(result, "2024-01-01");
470 }
471
472 #[test]
473 fn test_yyyymmdd_custom_separator() {
474 let timestamp = 1704110400000;
475 let result = yyyymmdd(timestamp, Some("/")).unwrap();
476 assert_eq!(result, "2024/01/01");
477 }
478
479 #[test]
480 fn test_yymmdd_no_separator() {
481 let timestamp = 1704110400000;
482 let result = yymmdd(timestamp, None).unwrap();
483 assert_eq!(result, "240101");
484 }
485
486 #[test]
487 fn test_yymmdd_with_separator() {
488 let timestamp = 1704110400000;
489 let result = yymmdd(timestamp, Some("-")).unwrap();
490 assert_eq!(result, "24-01-01");
491 }
492
493 #[test]
494 fn test_ymd_alias() {
495 let timestamp = 1704110400000;
496 let result = ymd(timestamp, None).unwrap();
497 assert_eq!(result, "2024-01-01");
498 }
499
500 #[test]
501 fn test_round_trip() {
502 let original = 1704110400000;
503 let iso_str = iso8601(original).unwrap();
504 let parsed = parse_date(&iso_str).unwrap();
505 assert_eq!(original, parsed);
506 }
507}