1use chrono::offset::Local;
2use chrono::{DateTime, TimeZone, Utc};
3
4pub fn simple_timestamp() -> String {
5 format_datetime(chrono::offset::Local::now())
6}
7
8pub fn format_datetime(dt: DateTime<Local>) -> String {
9 dt.format("%Y%m%d-%H%M%S").to_string()
10}
11
12pub fn now() -> String {
13 Local::now().to_rfc3339()
14}
15
16pub fn epoch_to_string(e: i64) -> String {
17 Utc.timestamp_millis_opt(e).unwrap().to_rfc3339()
18}
19
20pub fn epoch_zero() -> String {
21 epoch_to_string(0)
22}
23
24pub fn string_to_epoch(stamp: String) -> i64 {
25 match DateTime::parse_from_rfc3339(&stamp) {
26 Ok(dt) => dt.timestamp_millis(),
27 Err(e) => {
28 log::debug!("{:?}", e);
29 Local::now().timestamp_millis()
30 }
31 }
32}
33
34#[cfg(test)]
35mod tests {
36 use super::*;
37
38 #[test]
39 fn test_epoch_zero() {
40 assert_eq!(super::epoch_zero(), "1970-01-01T00:00:00+00:00");
41 }
42
43 #[test]
44 fn test_epoch_to_string_zero() {
45 assert_eq!(epoch_to_string(0), "1970-01-01T00:00:00+00:00");
46 }
47
48 #[test]
49 fn test_epoch_to_string_positive() {
50 assert_eq!(epoch_to_string(1000), "1970-01-01T00:00:01+00:00");
52 }
53
54 #[test]
55 fn test_epoch_to_string_large() {
56 let timestamp = 1577836800000i64;
58 let result = epoch_to_string(timestamp);
59 assert!(result.starts_with("2020-01-01"));
60 }
61
62 #[test]
63 fn test_simple_timestamp_format() {
64 let timestamp = simple_timestamp();
65 assert_eq!(timestamp.len(), 15, "Timestamp should be 15 characters");
67 assert!(timestamp.contains('-'), "Timestamp should contain hyphen");
68
69 let parts: Vec<&str> = timestamp.split('-').collect();
71 assert_eq!(parts.len(), 2);
72 assert_eq!(parts[0].len(), 8, "Date part should be 8 digits");
73 assert_eq!(parts[1].len(), 6, "Time part should be 6 digits");
74 assert!(parts[0].chars().all(|c| c.is_ascii_digit()));
75 assert!(parts[1].chars().all(|c| c.is_ascii_digit()));
76 }
77
78 #[test]
79 fn test_format_datetime() {
80 let dt = Local.with_ymd_and_hms(2023, 12, 25, 14, 30, 45).unwrap();
82 let formatted = format_datetime(dt);
83 assert_eq!(formatted, "20231225-143045");
84 }
85
86 #[test]
87 fn test_format_datetime_midnight() {
88 let dt = Local.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
89 let formatted = format_datetime(dt);
90 assert_eq!(formatted, "20000101-000000");
91 }
92
93 #[test]
94 fn test_format_datetime_end_of_day() {
95 let dt = Local.with_ymd_and_hms(2023, 12, 31, 23, 59, 59).unwrap();
96 let formatted = format_datetime(dt);
97 assert_eq!(formatted, "20231231-235959");
98 }
99
100 #[test]
101 fn test_now_format() {
102 let timestamp = now();
103 assert!(timestamp.contains('T'), "RFC3339 should contain 'T'");
105 assert!(
106 timestamp.contains('+') || timestamp.contains('Z') || timestamp.contains('-'),
107 "RFC3339 should contain timezone indicator"
108 );
109 }
110
111 #[test]
112 fn test_string_to_epoch_valid_rfc3339() {
113 let valid_stamp = "2020-01-01T00:00:00+00:00".to_string();
114 let epoch = string_to_epoch(valid_stamp);
115 assert_eq!(epoch, 1577836800000);
116 }
117
118 #[test]
119 fn test_string_to_epoch_valid_with_z() {
120 let valid_stamp = "2020-01-01T00:00:00Z".to_string();
121 let epoch = string_to_epoch(valid_stamp);
122 assert_eq!(epoch, 1577836800000);
123 }
124
125 #[test]
126 fn test_string_to_epoch_invalid_format() {
127 let invalid_stamp = "not a timestamp".to_string();
128 let epoch = string_to_epoch(invalid_stamp);
129 assert!(epoch > 0, "Invalid format should return current timestamp");
131 }
132
133 #[test]
134 fn test_string_to_epoch_empty() {
135 let empty_stamp = "".to_string();
136 let epoch = string_to_epoch(empty_stamp);
137 assert!(epoch > 0);
139 }
140
141 #[test]
142 fn test_string_to_epoch_partial_date() {
143 let partial = "2020-01-01".to_string();
144 let epoch = string_to_epoch(partial);
145 assert!(epoch > 0);
147 }
148
149 #[test]
150 fn test_epoch_roundtrip() {
151 let original_epoch = 1609459200000i64; let timestamp_str = epoch_to_string(original_epoch);
153 let recovered_epoch = string_to_epoch(timestamp_str);
154 let difference = (original_epoch - recovered_epoch).abs();
156 assert!(
157 difference < 86400000, "Roundtrip should preserve epoch within timezone tolerance"
159 );
160 }
161
162 #[test]
163 fn test_simple_timestamp_uniqueness() {
164 let ts1 = simple_timestamp();
166 let ts2 = simple_timestamp();
167 assert_eq!(&ts1[0..8], &ts2[0..8], "Timestamps should have same date");
169 }
170
171 #[test]
172 fn epoch_zero() {
173 assert_eq!(super::epoch_zero(), "1970-01-01T00:00:00+00:00");
175 }
176}