use crate::{Clock, DateTime, Error, Utc};
#[cfg(doc)]
use chrono::ParseError;
#[derive(Debug)]
pub struct FrozenClock {
datetime: DateTime<Utc>,
}
impl FrozenClock {
const DEFAULT_DATETIME: &'static str = "2025-05-23T10:13:00-07:00";
pub fn default_datetime() -> DateTime<Utc> {
DateTime::parse_from_rfc3339(Self::DEFAULT_DATETIME.as_ref())
.expect(&format!("invalid default date: {}", Self::DEFAULT_DATETIME))
.with_timezone(&Utc)
}
pub fn new(datetime: DateTime<Utc>) -> Self {
FrozenClock { datetime }
}
}
impl TryFrom<String> for FrozenClock {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl TryFrom<&str> for FrozenClock {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let datetime = DateTime::parse_from_rfc3339(value)?.with_timezone(&Utc);
Ok(Self::new(datetime))
}
}
impl Clock for FrozenClock {
fn now(&self) -> DateTime<Utc> {
self.datetime
}
}
impl Default for FrozenClock {
fn default() -> Self {
Self::new(Self::default_datetime())
}
}
#[cfg(test)]
mod tests {
use super::FrozenClock;
use crate::{Clock, Error, SystemClock};
use chrono::{Datelike, Timelike};
#[test]
fn it_parses_a_datetime_string() -> Result<(), Error> {
let s = "2025-12-25T12:01:02-08:00";
let dt = FrozenClock::try_from(String::from(s))?.now();
assert_eq!(dt.month(), 12);
assert_eq!(dt.day(), 25);
assert_eq!(dt.year(), 2025);
assert_eq!(dt.hour(), 20);
assert_eq!(dt.minute(), 1);
assert_eq!(dt.second(), 2);
Ok(())
}
#[test]
fn it_parses_a_datetime_str() -> Result<(), Error> {
let s = "2025-12-25T12:01:02-08:00";
let dt = FrozenClock::try_from(s)?.now();
assert_eq!(dt.month(), 12);
assert_eq!(dt.day(), 25);
assert_eq!(dt.year(), 2025);
assert_eq!(dt.hour(), 20);
assert_eq!(dt.minute(), 1);
assert_eq!(dt.second(), 2);
Ok(())
}
#[test]
fn it_returns_an_error_if_there_is_an_invalid_string() {
let s = "2025-12-32T12:01:02-08:00";
let c = FrozenClock::try_from(s);
assert!(c.is_err());
}
#[test]
fn it_returns_a_default_date_in_the_past() {
let now = SystemClock::default().now();
let dt = FrozenClock::default().now();
assert!(dt < now);
}
}