use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use chrono::DateTime;
use chrono::Datelike;
use chrono::Local;
use chrono::LocalResult;
use chrono::NaiveDateTime;
use chrono::TimeZone;
use chrono::Timelike;
pub const YYYY_MM_DD_HH_MM_SS_SSS: &str = "%Y-%m-%d#%H:%M:%S:%3f";
pub const YYYYMMDDHHMMSS: &str = "%Y%m%d%H%M%S";
#[inline(always)]
fn duration_since_unix_epoch(time: std::time::SystemTime) -> std::time::Duration {
time.duration_since(std::time::UNIX_EPOCH).unwrap_or_default()
}
#[inline(always)]
pub fn current_millis() -> u64 {
duration_since_unix_epoch(std::time::SystemTime::now()).as_millis() as u64
}
#[inline(always)]
pub fn current_nano() -> u64 {
duration_since_unix_epoch(std::time::SystemTime::now()).as_nanos() as u64
}
#[inline(always)]
pub fn current_secs() -> u64 {
duration_since_unix_epoch(std::time::SystemTime::now()).as_secs()
}
fn local_timestamp_millis_or_epoch(t: i64) -> DateTime<Local> {
Local
.timestamp_millis_opt(t)
.single()
.or_else(|| Local.timestamp_millis_opt(0).single())
.unwrap_or_else(Local::now)
}
pub fn is_it_time_to_do(when: &str) -> bool {
let hours: Vec<&str> = when.split(";").collect();
if !hours.is_empty() {
let now = Local::now();
for hour in hours {
let now_hour: i32 = hour.parse().unwrap_or(0);
if now_hour == now.hour() as i32 {
return true;
}
}
}
false
}
pub fn time_millis_to_human_string2(t: i64) -> String {
let dt = local_timestamp_millis_or_epoch(t);
format!(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02},{:03}",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
dt.timestamp_subsec_millis()
)
}
pub fn time_millis_to_human_string3(t: i64) -> String {
let dt = local_timestamp_millis_or_epoch(t);
format!(
"{:04}{:02}{:02}{:02}{:02}{:02}",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second()
)
}
pub fn time_millis_to_human_string(t: i64) -> String {
let dt = local_timestamp_millis_or_epoch(t);
format!(
"{:04}{:02}{:02}{:02}{:02}{:02}{:03}",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second(),
dt.timestamp_subsec_millis()
)
}
pub fn compute_next_minutes_time_millis() -> u64 {
let now = SystemTime::now();
let millis_since_epoch = now
.duration_since(UNIX_EPOCH)
.map_or(0, |duration| duration.as_millis() as u64);
let millis_in_minute = 60 * 1000;
((millis_since_epoch / millis_in_minute) + 1) * millis_in_minute
}
pub fn compute_next_morning_time_millis() -> u64 {
let now = Local::now();
let tomorrow = now.date_naive().succ_opt().unwrap_or_else(|| now.date_naive());
let next_morning = match Local.with_ymd_and_hms(tomorrow.year(), tomorrow.month(), tomorrow.day(), 0, 0, 0) {
LocalResult::Single(value) => value,
LocalResult::Ambiguous(earliest, _) => earliest,
LocalResult::None => now,
};
next_morning.timestamp_millis().max(0) as u64
}
pub fn parse_date(date: &str, pattern: &str) -> Option<NaiveDateTime> {
NaiveDateTime::parse_from_str(date, pattern).ok()
}
pub fn parse_date_to_millis(date: &str, pattern: &str) -> Option<i64> {
let parsed = parse_date(date, pattern)?;
let timestamp = match Local.from_local_datetime(&parsed) {
LocalResult::Single(timestamp) => timestamp,
LocalResult::Ambiguous(earliest, _) => earliest,
LocalResult::None => return None,
};
Some(timestamp.timestamp_millis())
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
use super::*;
#[test]
fn duration_since_unix_epoch_returns_zero_for_before_epoch() {
let before_epoch = UNIX_EPOCH - Duration::from_secs(1);
assert_eq!(duration_since_unix_epoch(before_epoch), Duration::ZERO);
}
#[test]
fn current_millis_returns_correct_value() {
let before = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
let current = current_millis();
let after = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
assert!(current >= before && current <= after);
}
#[test]
fn current_nano_returns_correct_value() {
let before = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64;
let current = current_nano();
let after = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64;
assert!(current >= before && current <= after);
}
#[test]
fn current_secs_returns_correct_value() {
let before = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let current = current_secs();
let after = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
assert!(current >= before && current <= after);
}
#[test]
fn all_timestamps_are_positive() {
assert!(current_millis() > 0);
assert!(current_nano() > 0);
assert!(current_secs() > 0);
}
#[test]
fn millis_greater_than_secs() {
let secs = current_secs();
let millis = current_millis();
assert!(millis > secs);
}
#[test]
fn nanos_greater_than_millis() {
let millis = current_millis();
let nanos = current_nano();
assert!(nanos > millis);
}
#[test]
fn timestamps_are_non_decreasing() {
let t1 = current_millis();
std::thread::sleep(Duration::from_millis(5));
let t2 = current_millis();
assert!(t2 >= t1);
}
#[test]
fn secs_consistent_with_millis() {
let secs = current_secs();
let millis_as_secs = current_millis() / 1000;
assert!(millis_as_secs == secs || millis_as_secs == secs + 1);
}
#[test]
fn is_it_time_to_do_returns_true_when_current_hour_is_in_input() {
let current_hour = Local::now().hour();
assert!(is_it_time_to_do(¤t_hour.to_string()));
}
#[test]
fn is_it_time_to_do_returns_false_when_current_hour_is_not_in_input() {
let current_hour = (Local::now().hour() + 1) % 24;
assert!(!is_it_time_to_do(¤t_hour.to_string()));
}
#[test]
fn time_millis_to_human_string_formats_correctly() {
let timestamp = 1743239631601;
let expected = Local
.timestamp_millis_opt(timestamp)
.unwrap()
.format("%Y%m%d%H%M%S%3f")
.to_string();
assert_eq!(time_millis_to_human_string(timestamp), expected);
}
#[test]
fn time_millis_to_human_string_falls_back_to_epoch_for_invalid_timestamp() {
assert_eq!(time_millis_to_human_string(i64::MAX), time_millis_to_human_string(0));
assert_eq!(time_millis_to_human_string2(i64::MAX), time_millis_to_human_string2(0));
assert_eq!(time_millis_to_human_string3(i64::MAX), time_millis_to_human_string3(0));
}
#[test]
fn test_compute_next_minutes_time_millis() {
let next_minute = compute_next_minutes_time_millis();
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64;
assert!(next_minute > now);
assert_eq!(next_minute % (60 * 1000), 0);
}
#[test]
fn compute_next_morning_time_millis_returns_non_zero_timestamp() {
assert!(compute_next_morning_time_millis() > 0);
}
#[test]
fn time_millis_to_human_string2_formats_correctly_with_valid_timestamp() {
use chrono::TimeZone;
let timestamp = 1625140800000;
let expected = Local
.timestamp_millis_opt(timestamp)
.unwrap()
.format("%Y-%m-%d %H:%M:%S,%3f")
.to_string();
assert_eq!(time_millis_to_human_string2(timestamp), expected);
}
#[test]
fn time_millis_to_human_string3_formats_correctly_with_valid_timestamp() {
let timestamp = 1625140800000;
let expect = Local
.timestamp_millis_opt(timestamp)
.unwrap()
.format("%Y%m%d%H%M%S")
.to_string();
assert_eq!(time_millis_to_human_string3(timestamp), expect);
}
#[test]
fn java_date_format_constants_parse_java_shapes() {
assert!(parse_date("20250102030405", YYYYMMDDHHMMSS).is_some());
let parsed = parse_date("2025-01-02#03:04:05:006", YYYY_MM_DD_HH_MM_SS_SSS)
.expect("Java millisecond format should parse");
assert_eq!(parsed.and_utc().timestamp_subsec_millis(), 6);
}
#[test]
fn parse_date_to_millis_uses_local_timezone_like_java_simple_date_format() {
let expected = match Local.with_ymd_and_hms(2025, 1, 2, 3, 4, 5) {
LocalResult::Single(timestamp) => timestamp,
LocalResult::Ambiguous(earliest, _) => earliest,
LocalResult::None => panic!("test timestamp should be valid in the local timezone"),
};
assert_eq!(
parse_date_to_millis("20250102030405", YYYYMMDDHHMMSS),
Some(expected.timestamp_millis())
);
}
}