rocketmq_runtime/common/
time_utils.rs1use std::time::SystemTime;
23use std::time::UNIX_EPOCH;
24
25use chrono::DateTime;
26use chrono::Datelike;
27use chrono::Local;
28use chrono::LocalResult;
29use chrono::NaiveDateTime;
30use chrono::TimeZone;
31use chrono::Timelike;
32
33pub const YYYY_MM_DD_HH_MM_SS_SSS: &str = "%Y-%m-%d#%H:%M:%S:%3f";
35pub const YYYYMMDDHHMMSS: &str = "%Y%m%d%H%M%S";
37
38#[inline(always)]
39fn duration_since_unix_epoch(time: std::time::SystemTime) -> std::time::Duration {
40 time.duration_since(std::time::UNIX_EPOCH).unwrap_or_default()
41}
42
43#[inline(always)]
47pub fn current_millis() -> u64 {
48 duration_since_unix_epoch(std::time::SystemTime::now()).as_millis() as u64
49}
50
51#[inline(always)]
55pub fn current_nano() -> u64 {
56 duration_since_unix_epoch(std::time::SystemTime::now()).as_nanos() as u64
57}
58
59#[inline(always)]
63pub fn current_secs() -> u64 {
64 duration_since_unix_epoch(std::time::SystemTime::now()).as_secs()
65}
66
67fn local_timestamp_millis_or_epoch(t: i64) -> DateTime<Local> {
68 Local
69 .timestamp_millis_opt(t)
70 .single()
71 .or_else(|| Local.timestamp_millis_opt(0).single())
72 .unwrap_or_else(Local::now)
73}
74
75pub fn is_it_time_to_do(when: &str) -> bool {
78 let hours: Vec<&str> = when.split(";").collect();
79 if !hours.is_empty() {
80 let now = Local::now();
81 for hour in hours {
82 let now_hour: i32 = hour.parse().unwrap_or(0);
83 if now_hour == now.hour() as i32 {
84 return true;
85 }
86 }
87 }
88 false
89}
90
91pub fn time_millis_to_human_string2(t: i64) -> String {
103 let dt = local_timestamp_millis_or_epoch(t);
104
105 format!(
106 "{:04}-{:02}-{:02} {:02}:{:02}:{:02},{:03}",
107 dt.year(),
108 dt.month(),
109 dt.day(),
110 dt.hour(),
111 dt.minute(),
112 dt.second(),
113 dt.timestamp_subsec_millis()
114 )
115}
116
117pub fn time_millis_to_human_string3(t: i64) -> String {
129 let dt = local_timestamp_millis_or_epoch(t);
130
131 format!(
132 "{:04}{:02}{:02}{:02}{:02}{:02}",
133 dt.year(),
134 dt.month(),
135 dt.day(),
136 dt.hour(),
137 dt.minute(),
138 dt.second()
139 )
140}
141
142pub fn time_millis_to_human_string(t: i64) -> String {
154 let dt = local_timestamp_millis_or_epoch(t);
155
156 format!(
157 "{:04}{:02}{:02}{:02}{:02}{:02}{:03}",
158 dt.year(),
159 dt.month(),
160 dt.day(),
161 dt.hour(),
162 dt.minute(),
163 dt.second(),
164 dt.timestamp_subsec_millis()
165 )
166}
167
168pub fn compute_next_minutes_time_millis() -> u64 {
170 let now = SystemTime::now();
171 let millis_since_epoch = now
172 .duration_since(UNIX_EPOCH)
173 .map_or(0, |duration| duration.as_millis() as u64);
174
175 let millis_in_minute = 60 * 1000;
176 ((millis_since_epoch / millis_in_minute) + 1) * millis_in_minute
177}
178
179pub fn compute_next_morning_time_millis() -> u64 {
181 let now = Local::now();
182 let tomorrow = now.date_naive().succ_opt().unwrap_or_else(|| now.date_naive());
183 let next_morning = match Local.with_ymd_and_hms(tomorrow.year(), tomorrow.month(), tomorrow.day(), 0, 0, 0) {
184 LocalResult::Single(value) => value,
185 LocalResult::Ambiguous(earliest, _) => earliest,
186 LocalResult::None => now,
187 };
188 next_morning.timestamp_millis().max(0) as u64
189}
190
191pub fn parse_date(date: &str, pattern: &str) -> Option<NaiveDateTime> {
193 NaiveDateTime::parse_from_str(date, pattern).ok()
194}
195
196pub fn parse_date_to_millis(date: &str, pattern: &str) -> Option<i64> {
198 let parsed = parse_date(date, pattern)?;
199 let timestamp = match Local.from_local_datetime(&parsed) {
200 LocalResult::Single(timestamp) => timestamp,
201 LocalResult::Ambiguous(earliest, _) => earliest,
202 LocalResult::None => return None,
203 };
204 Some(timestamp.timestamp_millis())
205}
206
207#[cfg(test)]
208mod tests {
209 use std::time::Duration;
210 use std::time::SystemTime;
211 use std::time::UNIX_EPOCH;
212
213 use super::*;
214
215 #[test]
216 fn duration_since_unix_epoch_returns_zero_for_before_epoch() {
217 let before_epoch = UNIX_EPOCH - Duration::from_secs(1);
218
219 assert_eq!(duration_since_unix_epoch(before_epoch), Duration::ZERO);
220 }
221
222 #[test]
223 fn current_millis_returns_correct_value() {
224 let before = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
225 let current = current_millis();
226 let after = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
227 assert!(current >= before && current <= after);
228 }
229
230 #[test]
231 fn current_nano_returns_correct_value() {
232 let before = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64;
233 let current = current_nano();
234 let after = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64;
235 assert!(current >= before && current <= after);
236 }
237
238 #[test]
239 fn current_secs_returns_correct_value() {
240 let before = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
241 let current = current_secs();
242 let after = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
243 assert!(current >= before && current <= after);
244 }
245
246 #[test]
247 fn all_timestamps_are_positive() {
248 assert!(current_millis() > 0);
249 assert!(current_nano() > 0);
250 assert!(current_secs() > 0);
251 }
252
253 #[test]
254 fn millis_greater_than_secs() {
255 let secs = current_secs();
256 let millis = current_millis();
257 assert!(millis > secs);
258 }
259
260 #[test]
261 fn nanos_greater_than_millis() {
262 let millis = current_millis();
263 let nanos = current_nano();
264 assert!(nanos > millis);
265 }
266
267 #[test]
268 fn timestamps_are_non_decreasing() {
269 let t1 = current_millis();
270 std::thread::sleep(Duration::from_millis(5));
271 let t2 = current_millis();
272 assert!(t2 >= t1);
273 }
274
275 #[test]
276 fn secs_consistent_with_millis() {
277 let secs = current_secs();
278 let millis_as_secs = current_millis() / 1000;
279 assert!(millis_as_secs == secs || millis_as_secs == secs + 1);
281 }
282
283 #[test]
284 fn is_it_time_to_do_returns_true_when_current_hour_is_in_input() {
285 let current_hour = Local::now().hour();
286 assert!(is_it_time_to_do(¤t_hour.to_string()));
287 }
288
289 #[test]
290 fn is_it_time_to_do_returns_false_when_current_hour_is_not_in_input() {
291 let current_hour = (Local::now().hour() + 1) % 24;
292 assert!(!is_it_time_to_do(¤t_hour.to_string()));
293 }
294
295 #[test]
296 fn time_millis_to_human_string_formats_correctly() {
297 let timestamp = 1743239631601;
298 let expected = Local
299 .timestamp_millis_opt(timestamp)
300 .unwrap()
301 .format("%Y%m%d%H%M%S%3f")
302 .to_string();
303 assert_eq!(time_millis_to_human_string(timestamp), expected);
304 }
305
306 #[test]
307 fn time_millis_to_human_string_falls_back_to_epoch_for_invalid_timestamp() {
308 assert_eq!(time_millis_to_human_string(i64::MAX), time_millis_to_human_string(0));
309 assert_eq!(time_millis_to_human_string2(i64::MAX), time_millis_to_human_string2(0));
310 assert_eq!(time_millis_to_human_string3(i64::MAX), time_millis_to_human_string3(0));
311 }
312
313 #[test]
314 fn test_compute_next_minutes_time_millis() {
315 let next_minute = compute_next_minutes_time_millis();
316 let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
317
318 assert!(next_minute > now);
319 assert_eq!(next_minute % (60 * 1000), 0);
320 }
321
322 #[test]
323 fn compute_next_morning_time_millis_returns_non_zero_timestamp() {
324 assert!(compute_next_morning_time_millis() > 0);
325 }
326
327 #[test]
328 fn time_millis_to_human_string2_formats_correctly_with_valid_timestamp() {
329 use chrono::TimeZone;
330
331 let timestamp = 1625140800000;
332 let expected = Local
333 .timestamp_millis_opt(timestamp)
334 .unwrap()
335 .format("%Y-%m-%d %H:%M:%S,%3f")
336 .to_string();
337 assert_eq!(time_millis_to_human_string2(timestamp), expected);
338 }
339
340 #[test]
341 fn time_millis_to_human_string3_formats_correctly_with_valid_timestamp() {
342 let timestamp = 1625140800000;
343 let expect = Local
344 .timestamp_millis_opt(timestamp)
345 .unwrap()
346 .format("%Y%m%d%H%M%S")
347 .to_string();
348 assert_eq!(time_millis_to_human_string3(timestamp), expect);
349 }
350
351 #[test]
352 fn java_date_format_constants_parse_java_shapes() {
353 assert!(parse_date("20250102030405", YYYYMMDDHHMMSS).is_some());
354
355 let parsed = parse_date("2025-01-02#03:04:05:006", YYYY_MM_DD_HH_MM_SS_SSS)
356 .expect("Java millisecond format should parse");
357 assert_eq!(parsed.and_utc().timestamp_subsec_millis(), 6);
358 }
359
360 #[test]
361 fn parse_date_to_millis_uses_local_timezone_like_java_simple_date_format() {
362 let expected = match Local.with_ymd_and_hms(2025, 1, 2, 3, 4, 5) {
363 LocalResult::Single(timestamp) => timestamp,
364 LocalResult::Ambiguous(earliest, _) => earliest,
365 LocalResult::None => panic!("test timestamp should be valid in the local timezone"),
366 };
367
368 assert_eq!(
369 parse_date_to_millis("20250102030405", YYYYMMDDHHMMSS),
370 Some(expected.timestamp_millis())
371 );
372 }
373}