use chrono::{DateTime, TimeDelta, Utc};
use serde::Deserialize;
use std::fmt::{Display, Formatter};
use std::ops::Add;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Error)]
pub enum UtcDateTimeError {
#[error("Invalid date time: {0}")]
InvalidDateTime(String),
}
#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Deserialize)]
pub struct UtcDateTime {
value: DateTime<Utc>,
}
impl UtcDateTime {
pub fn now() -> Self {
Self { value: Utc::now() }
}
pub fn new(value: DateTime<Utc>) -> Self {
Self { value }
}
pub fn from_rfc3339(value: &str) -> Result<Self, UtcDateTimeError> {
let dt = DateTime::parse_from_rfc3339(value)
.map_err(|e| UtcDateTimeError::InvalidDateTime(format!("{e}: {value}")))?;
Ok(Self {
value: dt.with_timezone(&Utc),
})
}
pub fn timestamp(&self) -> i64 {
self.value.timestamp()
}
pub fn value(&self) -> DateTime<Utc> {
self.value
}
pub fn add(&self, rhs: TimeDelta) -> Self {
Self {
value: self.value.add(rhs),
}
}
}
impl From<DateTime<Utc>> for UtcDateTime {
fn from(value: DateTime<Utc>) -> Self {
Self { value }
}
}
impl Display for UtcDateTime {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.value.to_rfc3339_opts(chrono::SecondsFormat::Secs, true))
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_utc_date_time_display() {
let dt = DateTime::parse_from_rfc3339("2024-08-28T12:00:00Z")
.unwrap()
.with_timezone(&Utc);
let datetime = UtcDateTime::from(dt);
assert_eq!(datetime.to_string(), "2024-08-28T12:00:00Z");
}
#[test]
fn test_from_rfc3339() {
let datetime = UtcDateTime::from_rfc3339("2024-08-28T12:00:00Z");
assert!(datetime.is_ok());
assert_eq!(datetime.unwrap().to_string(), "2024-08-28T12:00:00Z".to_owned());
let invalid_datetime = UtcDateTime::from_rfc3339("2024-08-T12:00:00Z");
assert!(invalid_datetime.is_err());
}
#[test]
fn test_value() {
let dt = DateTime::parse_from_rfc3339("2024-08-28T12:00:00Z")
.unwrap()
.with_timezone(&Utc);
let datetime = UtcDateTime::from(dt);
assert_eq!(datetime.value(), dt);
}
#[test]
fn test_timestamp() {
let dt = DateTime::parse_from_rfc3339("2024-08-28T12:00:00Z")
.unwrap()
.with_timezone(&Utc);
let datetime = UtcDateTime::from(dt);
assert_eq!(datetime.timestamp(), 1724846400);
}
#[test]
fn test_add() {
let dt = DateTime::parse_from_rfc3339("2024-08-28T12:00:00Z")
.unwrap()
.with_timezone(&Utc);
let datetime = UtcDateTime::from(dt);
let new_datetime = datetime.add(TimeDelta::seconds(3600));
assert_eq!(new_datetime.to_string(), "2024-08-28T13:00:00Z".to_owned());
}
}