use chrono::{
format::{DelayedFormat, StrftimeItems},
DateTime, Local, SecondsFormat, Utc,
};
#[cfg(feature = "syslog_writer")]
use chrono::{Datelike, Timelike};
use std::sync::{Mutex, OnceLock};
#[derive(Debug, Default)]
pub struct DeferredNow(Option<DateTime<Local>>);
impl<'a> DeferredNow {
#[must_use]
pub fn new() -> Self {
Self(None)
}
#[cfg(test)]
#[must_use]
fn new_from_datetime(dt: DateTime<Local>) -> Self {
Self(Some(dt))
}
pub fn now(&'a mut self) -> &'a DateTime<Local> {
self.0.get_or_insert_with(Local::now)
}
pub fn now_utc_owned(&'a mut self) -> DateTime<Utc> {
(*self.now()).into()
}
pub fn format<'b>(&'a mut self, fmt: &'b str) -> DelayedFormat<StrftimeItems<'b>> {
if use_utc() {
self.now_utc_owned().format(fmt)
} else {
self.now().format(fmt)
}
}
pub fn format_rfc3339(&mut self) -> String {
if use_utc() {
self.now_utc_owned()
.to_rfc3339_opts(SecondsFormat::Millis, false)
} else {
self.now().to_rfc3339_opts(SecondsFormat::Millis, false)
}
}
#[cfg(feature = "syslog_writer")]
pub(crate) fn format_rfc3164(&mut self) -> String {
let (date, time) = if use_utc() {
let now = self.now_utc_owned();
(now.date_naive(), now.time())
} else {
let now = self.now();
(now.date_naive(), now.time())
};
format!(
"{mmm} {dd:>2} {hh:02}:{mm:02}:{ss:02}",
mmm = match date.month() {
1 => "Jan",
2 => "Feb",
3 => "Mar",
4 => "Apr",
5 => "May",
6 => "Jun",
7 => "Jul",
8 => "Aug",
9 => "Sep",
10 => "Oct",
11 => "Nov",
12 => "Dec",
_ => unreachable!(),
},
dd = date.day(),
hh = time.hour(),
mm = time.minute(),
ss = time.second()
)
}
pub fn force_utc() {
let mut cfg_force_utc = cfg_force_utc().lock().unwrap();
match *cfg_force_utc {
Some(false) => {
panic!("offset is already initialized not to enforce UTC");
}
Some(true) => {
}
None => *cfg_force_utc = Some(true),
}
}
}
fn cfg_force_utc() -> &'static Mutex<Option<bool>> {
static CFG_FORCE_UTC: OnceLock<Mutex<Option<bool>>> = OnceLock::new();
CFG_FORCE_UTC.get_or_init(|| Mutex::new(None))
}
fn use_utc() -> bool {
let mut cfg_force_utc = cfg_force_utc().lock().unwrap();
if let Some(true) = *cfg_force_utc {
true
} else {
if cfg_force_utc.is_none() {
*cfg_force_utc = Some(false);
}
false
}
}
#[cfg(test)]
pub(crate) fn set_force_utc(b: bool) {
let mut cfg_force_utc = cfg_force_utc().lock().unwrap();
*cfg_force_utc = Some(b);
}
#[cfg(test)]
mod test {
use crate::DeferredNow;
use chrono::{
DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, SecondsFormat, TimeZone, Utc,
};
#[test]
fn test_timestamp_taken_only_once() {
let mut deferred_now = super::DeferredNow::new();
let once = *deferred_now.now();
std::thread::sleep(std::time::Duration::from_millis(30));
let again = *deferred_now.now();
assert_eq!(once, again);
println!("Now: {}", deferred_now.format("%Y-%m-%d %H:%M:%S%.6f %:z"));
println!("Now: {}", once.format("%Y-%m-%d %H:%M:%S%.6f %:z"));
println!("Now: {}", again.format("%Y-%m-%d %H:%M:%S%.6f %:z"));
}
fn utc_and_offset_timestamps() -> (DateTime<Utc>, DateTime<FixedOffset>) {
let naive_datetime = NaiveDateTime::new(
NaiveDate::from_ymd_opt(2021, 4, 29).unwrap(),
NaiveTime::from_hms_milli_opt(13, 14, 15, 678).unwrap(),
);
(
Utc.from_local_datetime(&naive_datetime).unwrap(),
FixedOffset::east_opt(3600)
.unwrap()
.from_local_datetime(&naive_datetime)
.unwrap(),
)
}
fn get_deferred_nows() -> (DeferredNow, DeferredNow) {
let (ts_utc, ts_plus1) = utc_and_offset_timestamps();
(
DeferredNow::new_from_datetime(ts_utc.into()),
DeferredNow::new_from_datetime(ts_plus1.into()),
)
}
#[test]
fn test_chrono_rfc3339() {
let (ts_utc, ts_plus1) = utc_and_offset_timestamps();
assert_eq!(
ts_utc.to_rfc3339_opts(SecondsFormat::Millis, true),
"2021-04-29T13:14:15.678Z",
);
assert_eq!(
ts_plus1.to_rfc3339_opts(SecondsFormat::Millis, true),
"2021-04-29T13:14:15.678+01:00",
);
assert_eq!(
ts_utc.to_rfc3339_opts(SecondsFormat::Millis, false),
"2021-04-29T13:14:15.678+00:00",
);
assert_eq!(
ts_plus1.to_rfc3339_opts(SecondsFormat::Millis, false),
"2021-04-29T13:14:15.678+01:00",
);
}
#[test]
fn test_formats() {
#[cfg(feature = "syslog_writer")]
{
log::info!("test rfc3164");
super::set_force_utc(true);
let (mut dn1, mut dn2) = get_deferred_nows();
assert_eq!("Apr 29 13:14:15", &dn1.format_rfc3164());
assert_eq!("Apr 29 12:14:15", &dn2.format_rfc3164());
}
log::info!("test rfc3339");
{
super::set_force_utc(false);
let (mut dn1, mut dn2) = get_deferred_nows();
log::info!("2021-04-29T15:14:15.678+02:00, {}", dn1.format_rfc3339());
log::info!("2021-04-29T14:14:15.678+02:00, {}", dn2.format_rfc3339());
super::set_force_utc(true);
let (mut dn1, mut dn2) = get_deferred_nows();
assert_eq!("2021-04-29T13:14:15.678+00:00", dn1.format_rfc3339());
assert_eq!("2021-04-29T12:14:15.678+00:00", dn2.format_rfc3339());
}
}
}