use anyhow::{Context, Result};
use std::time::{SystemTime, UNIX_EPOCH};
pub fn get_current_timestamp_millis() -> Result<u64> {
let millis = chrono::Utc::now().timestamp_millis();
if millis < 0 {
anyhow::bail!("System time produced negative timestamp: {millis} ms");
}
Ok(millis as u64)
}
#[allow(dead_code)]
pub fn get_current_timestamp_nanos() -> Result<u64> {
match chrono::Utc::now().timestamp_nanos_opt() {
Some(nanos) => {
if nanos < 0 {
anyhow::bail!("System time produced negative timestamp: {nanos}");
}
Ok(nanos as u64)
}
None => {
log::warn!("Timestamp overflow detected, falling back to millisecond precision");
let millis = chrono::Utc::now().timestamp_millis();
if millis < 0 {
anyhow::bail!("System time produced negative timestamp: {millis} ms");
}
Ok((millis as u64) * 1_000_000)
}
}
}
pub fn get_system_time_millis() -> Result<u64> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.context("System time is before Unix epoch (January 1, 1970)")
.map(|duration| duration.as_millis() as u64)
}
#[allow(dead_code)]
pub fn get_system_time_nanos() -> Result<u64> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.context("System time is before Unix epoch (January 1, 1970)")
.map(|duration| duration.as_nanos() as u64)
}
#[allow(dead_code)]
pub fn get_timestamp_with_fallback(default_on_error: Option<u64>) -> Result<u64> {
if let Ok(timestamp) = get_current_timestamp_millis() {
return Ok(timestamp);
}
if let Ok(timestamp) = get_system_time_millis() {
log::debug!("Using SystemTime fallback for timestamp");
return Ok(timestamp);
}
if let Some(default) = default_on_error {
log::error!("All timestamp methods failed, using default value: {default}");
return Ok(default);
}
anyhow::bail!("Unable to obtain valid timestamp from system")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_current_timestamp_millis() {
let result = get_current_timestamp_millis();
assert!(result.is_ok());
let timestamp = result.unwrap();
assert!(timestamp > 0);
}
#[test]
fn test_get_system_time_millis() {
let result = get_system_time_millis();
assert!(result.is_ok());
let timestamp = result.unwrap();
assert!(timestamp > 0);
}
#[test]
fn test_get_current_timestamp_nanos() {
let result = get_current_timestamp_nanos();
assert!(result.is_ok());
let timestamp = result.unwrap();
assert!(timestamp > 0);
}
#[test]
fn test_get_system_time_nanos() {
let result = get_system_time_nanos();
assert!(result.is_ok());
let timestamp = result.unwrap();
assert!(timestamp > 0);
}
#[test]
fn test_get_timestamp_with_fallback() {
let result = get_timestamp_with_fallback(None);
assert!(result.is_ok());
let result_with_default = get_timestamp_with_fallback(Some(42));
assert!(result_with_default.is_ok());
}
#[test]
fn test_millis_timestamp_in_valid_range() {
use drasi_core::models::validate_effective_from;
let ts = get_current_timestamp_millis().unwrap();
assert!(
validate_effective_from(ts).is_ok(),
"Application source timestamp ({ts}) should be in millisecond range"
);
}
}