use std::env;
use anyhow::{Context, Result, anyhow};
use time::format_description::well_known::Rfc3339;
use time::macros::format_description;
use time::{Date, OffsetDateTime};
pub fn now() -> OffsetDateTime {
if let Ok(raw) = env::var("SOURCE_DATE_EPOCH")
&& let Ok(secs) = raw.trim().parse::<i64>()
&& let Ok(t) = OffsetDateTime::from_unix_timestamp(secs)
{
return t;
}
OffsetDateTime::now_utc()
}
pub fn today() -> Date {
now().date()
}
pub fn format_rfc3339(t: OffsetDateTime) -> String {
t.format(&Rfc3339)
.unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string())
}
pub fn parse_ymd(s: &str) -> Result<Date> {
let fmt = format_description!("[year]-[month]-[day]");
Date::parse(s, fmt).with_context(|| format!("invalid YYYY-MM-DD date: {s:?}"))
}
pub fn format_ymd(d: Date) -> String {
let fmt = format_description!("[year]-[month]-[day]");
d.format(fmt).unwrap_or_else(|_| "1970-01-01".to_string())
}
pub fn is_expired(expires: Date) -> bool {
expires < today()
}
pub fn is_expired_str(s: &str) -> Result<bool> {
parse_ymd(s).map(is_expired).map_err(|e| anyhow!("{e}"))
}
#[cfg(test)]
pub(crate) fn test_env_lock() -> std::sync::MutexGuard<'static, ()> {
use std::sync::{Mutex, OnceLock};
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(|e| e.into_inner())
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::todo,
clippy::unimplemented
)]
use super::*;
fn env_lock() -> std::sync::MutexGuard<'static, ()> {
test_env_lock()
}
#[test]
fn parse_ymd_accepts_valid() {
let d = parse_ymd("2026-04-29").unwrap();
assert_eq!(format_ymd(d), "2026-04-29");
}
#[test]
fn parse_ymd_rejects_malformed() {
assert!(parse_ymd("2026/04/29").is_err());
assert!(parse_ymd("not-a-date").is_err());
assert!(parse_ymd("").is_err());
}
#[test]
fn parse_ymd_rejects_non_zero_padded() {
assert!(parse_ymd("2026-4-29").is_err());
assert!(parse_ymd("2026-04-9").is_err());
}
#[test]
fn now_honors_source_date_epoch() {
let _g = env_lock();
unsafe {
env::set_var("SOURCE_DATE_EPOCH", "1777593600");
}
let t = now();
assert_eq!(t.unix_timestamp(), 1777593600);
assert_eq!(format_ymd(t.date()), "2026-05-01");
unsafe {
env::remove_var("SOURCE_DATE_EPOCH");
}
}
#[test]
fn now_is_read_per_call_not_cached() {
let _g = env_lock();
unsafe {
env::set_var("SOURCE_DATE_EPOCH", "1000000000");
}
let a = now();
unsafe {
env::set_var("SOURCE_DATE_EPOCH", "2000000000");
}
let b = now();
assert_ne!(a.unix_timestamp(), b.unix_timestamp());
assert_eq!(a.unix_timestamp(), 1000000000);
assert_eq!(b.unix_timestamp(), 2000000000);
unsafe {
env::remove_var("SOURCE_DATE_EPOCH");
}
}
#[test]
fn malformed_source_date_epoch_falls_back() {
let _g = env_lock();
unsafe {
env::set_var("SOURCE_DATE_EPOCH", "not-a-number");
}
let _ = now();
unsafe {
env::remove_var("SOURCE_DATE_EPOCH");
}
}
#[test]
fn format_rfc3339_round_trip() {
let t = OffsetDateTime::from_unix_timestamp(1777593600).unwrap();
let s = format_rfc3339(t);
assert_eq!(s, "2026-05-01T00:00:00Z");
}
#[test]
fn is_expired_ordering() {
let _g = env_lock();
unsafe {
env::set_var("SOURCE_DATE_EPOCH", "1777593600");
} assert!(is_expired(parse_ymd("2026-04-30").unwrap()));
assert!(!is_expired(parse_ymd("2026-05-01").unwrap()));
assert!(!is_expired(parse_ymd("2026-05-02").unwrap()));
unsafe {
env::remove_var("SOURCE_DATE_EPOCH");
}
}
}