use std::{
ops::{Deref, DerefMut},
sync::{LazyLock, RwLock},
};
use chrono::{Datelike, Days, Duration, Local, Months, NaiveDateTime, Timelike, Utc};
use serde::{Deserialize, Serialize};
use crate::{
BaseFormat, GetInner,
error::{DateTimeError, ErrorContext, SpanError},
span::Span,
timestamp::{TimestampMicro, TimestampMilli, TimestampNano},
};
pub(crate) static BASE_DATETIME_FORMAT: BaseFormat<Option<&'static str>> =
LazyLock::new(|| RwLock::new(None));
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Serialize, Deserialize)]
pub enum DateTimeUnit {
Year,
Month,
Day,
Hour,
Minute,
Second,
}
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Deserialize, Serialize)]
pub struct DateTime {
pub(crate) datetime: NaiveDateTime,
#[serde(skip)]
#[serde(default = "base_datetime_format")]
pub(crate) format: String,
}
fn base_datetime_format() -> String {
BASE_DATETIME_FORMAT.get().to_string()
}
impl Default for DateTime {
fn default() -> Self {
Self {
datetime: NaiveDateTime::default(),
format: BASE_DATETIME_FORMAT.get().to_string(),
}
}
}
impl std::fmt::Display for DateTime {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.datetime.format(&self.format))
}
}
impl Deref for DateTime {
type Target = NaiveDateTime;
fn deref(&self) -> &Self::Target {
&self.datetime
}
}
impl DerefMut for DateTime {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.datetime
}
}
impl DateTime {
pub fn datetime(&self) -> NaiveDateTime {
self.datetime
}
pub fn timestamp(&self) -> i64 {
self.datetime.and_utc().timestamp()
}
pub fn clear_time(&self) -> Self {
let datetime = NaiveDateTime::new(self.datetime.date(), chrono::NaiveTime::default());
Self {
datetime,
format: BASE_DATETIME_FORMAT.get().to_string(),
}
}
pub fn with_time(mut self, hour: u32, minute: u32, second: u32) -> Result<Self, SpanError> {
let Some(time) = chrono::NaiveTime::from_hms_opt(hour, minute, second) else {
return Err(SpanError::InvalidTime(hour, minute, second)).err_ctx(DateTimeError);
};
self.datetime = self.datetime.date().and_time(time);
Ok(self)
}
}
impl Span<DateTimeUnit, i32> for DateTime {
fn new(year: i32, month: u32, day: u32) -> Result<Self, SpanError> {
let date = chrono::NaiveDate::from_ymd_opt(year, month, day)
.ok_or(SpanError::InvalidDate(year, month, day))
.err_ctx(DateTimeError)?;
let datetime = NaiveDateTime::new(date, chrono::NaiveTime::default());
Ok(Self {
datetime,
format: BASE_DATETIME_FORMAT.get().to_string(),
})
}
fn format(mut self, format: impl ToString) -> Self {
self.format = format.to_string();
self
}
fn default_format(mut self) -> Self {
self.format = BASE_DATETIME_FORMAT.get().to_string();
self
}
fn update(&self, unit: DateTimeUnit, value: i32) -> Result<Self, SpanError> {
let datetime = match unit {
DateTimeUnit::Year if value > 0 => self
.datetime
.checked_add_months(Months::new(value as u32 * 12)),
DateTimeUnit::Year => self
.datetime
.checked_sub_months(Months::new(value.unsigned_abs() * 12)),
DateTimeUnit::Month if value > 0 => {
self.datetime.checked_add_months(Months::new(value as u32))
}
DateTimeUnit::Month => self
.datetime
.checked_sub_months(Months::new(value.unsigned_abs())),
DateTimeUnit::Day if value > 0 => {
self.datetime.checked_add_days(Days::new(value as u64))
}
DateTimeUnit::Day => self
.datetime
.checked_sub_days(Days::new(value.unsigned_abs() as u64)),
DateTimeUnit::Hour => {
Duration::try_hours(value as i64).map(|hours| self.datetime + hours)
}
DateTimeUnit::Minute => {
Duration::try_minutes(value as i64).map(|minutes| self.datetime + minutes)
}
DateTimeUnit::Second => {
Duration::try_seconds(value as i64).map(|seconds| self.datetime + seconds)
}
};
match datetime {
Some(datetime) => Ok(Self {
datetime,
format: self.format.clone(),
}),
None => Err(SpanError::InvalidUpdate(format!(
"Cannot Add/Remove {value} {unit:?} to/from {self}"
)))
.err_ctx(DateTimeError),
}
}
fn next(&self, unit: DateTimeUnit) -> Result<Self, SpanError> {
self.update(unit, 1)
}
fn matches(&self, unit: DateTimeUnit, value: u32) -> bool {
match unit {
DateTimeUnit::Year => self.datetime.year() == value as i32,
DateTimeUnit::Month => self.datetime.month() == value,
DateTimeUnit::Day => self.datetime.day() == value,
DateTimeUnit::Hour => self.datetime.hour() == value,
DateTimeUnit::Minute => self.datetime.minute() == value,
DateTimeUnit::Second => self.datetime.second() == value,
}
}
fn now() -> Result<Self, SpanError> {
let datetime = Local::now();
Self::new(datetime.year(), datetime.month(), datetime.day())?.with_time(
datetime.hour(),
datetime.minute(),
datetime.second(),
)
}
fn is_in_future(&self) -> Result<bool, SpanError> {
let datetime = Local::now();
let now = Self::new(datetime.year(), datetime.month(), datetime.day())?.with_time(
datetime.hour(),
datetime.minute(),
datetime.second(),
)?;
Ok(self.datetime > now.datetime)
}
fn elapsed(&self, lhs: &Self) -> Duration {
self.datetime.signed_duration_since(lhs.datetime)
}
fn unit_elapsed(&self, rhs: &Self, unit: DateTimeUnit) -> Result<i64, SpanError> {
Ok(match unit {
DateTimeUnit::Year => (self.datetime.year() - rhs.datetime.year()) as i64,
DateTimeUnit::Month => {
self.datetime.year() as i64 * 12 + self.datetime.month() as i64
- (rhs.datetime.year() as i64 * 12 + rhs.datetime.month() as i64)
}
DateTimeUnit::Day => {
(self.datetime.and_utc().timestamp() - rhs.datetime.and_utc().timestamp())
/ 60
/ 60
/ 24
}
DateTimeUnit::Hour => {
(self.datetime.and_utc().timestamp() - rhs.datetime.and_utc().timestamp()) / 60 / 60
}
DateTimeUnit::Minute => {
(self.datetime.and_utc().timestamp() - rhs.datetime.and_utc().timestamp()) / 60
}
DateTimeUnit::Second => {
self.datetime.and_utc().timestamp() - rhs.datetime.and_utc().timestamp()
}
}
.abs())
}
fn clear_unit(&self, unit: DateTimeUnit) -> Result<Self, SpanError> {
let datetime = match unit {
DateTimeUnit::Year => self.datetime.with_year(1970).ok_or(SpanError::ClearUnit(
"Error while setting year to 1970".to_string(),
)),
DateTimeUnit::Month => self.datetime.with_month(1).ok_or(SpanError::ClearUnit(
"Error while setting month to 1".to_string(),
)),
DateTimeUnit::Day => self.datetime.with_day(1).ok_or(SpanError::ClearUnit(
"Error while setting day to 1".to_string(),
)),
DateTimeUnit::Hour => self.datetime.with_hour(0).ok_or(SpanError::ClearUnit(
"Error while setting hour to 0".to_string(),
)),
DateTimeUnit::Minute => self.datetime.with_minute(0).ok_or(SpanError::ClearUnit(
"Error while setting minute to 0".to_string(),
)),
DateTimeUnit::Second => self.datetime.with_second(0).ok_or(SpanError::ClearUnit(
"Error while setting second to 0".to_string(),
)),
}
.err_ctx(DateTimeError)?;
Ok(Self {
datetime,
format: self.format.clone(),
})
}
fn get_format(&self) -> String {
self.format.clone()
}
fn deserialize_with_format<'de, D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
Self: TryFrom<(String, String)>,
{
#[derive(Deserialize)]
struct Visitor {
datetime: String,
format: String,
}
let visitor: Visitor = Deserialize::deserialize(deserializer)?;
Self::try_from((visitor.datetime, visitor.format))
.map_err(|_| serde::de::Error::custom("Invalid datetime"))
}
}
impl From<NaiveDateTime> for DateTime {
fn from(datetime: NaiveDateTime) -> Self {
Self {
datetime,
format: BASE_DATETIME_FORMAT.get().to_string(),
}
}
}
impl TryFrom<TimestampMilli> for crate::datetime::DateTime {
type Error = crate::error::SpanError;
fn try_from(timestamp: TimestampMilli) -> Result<Self, Self::Error> {
let datetime = chrono::DateTime::from_timestamp(*timestamp, 0).ok_or(
crate::error::SpanError::ParseFromTimestamp(
"Error while parsing timestamp".to_string(),
),
)?;
Ok(Self {
datetime: datetime.naive_utc(),
format: crate::datetime::BASE_DATETIME_FORMAT.get(),
})
}
}
impl TryFrom<TimestampMicro> for crate::datetime::DateTime {
type Error = crate::error::SpanError;
fn try_from(timestamp: TimestampMicro) -> Result<Self, Self::Error> {
let datetime = chrono::DateTime::from_timestamp(*timestamp / 1_000, 0).ok_or(
crate::error::SpanError::ParseFromTimestamp(
"Error while parsing timestamp".to_string(),
),
)?;
Ok(Self {
datetime: datetime.naive_utc(),
format: crate::datetime::BASE_DATETIME_FORMAT.get(),
})
}
}
impl TryFrom<TimestampNano> for crate::datetime::DateTime {
type Error = crate::error::SpanError;
fn try_from(timestamp: TimestampNano) -> Result<Self, Self::Error> {
let datetime = chrono::DateTime::from_timestamp(*timestamp / 1_000_000, 0).ok_or(
crate::error::SpanError::ParseFromTimestamp(
"Error while parsing timestamp".to_string(),
),
)?;
Ok(Self {
datetime: datetime.naive_utc(),
format: crate::datetime::BASE_DATETIME_FORMAT.get(),
})
}
}
impl TryFrom<(String, String)> for DateTime {
type Error = SpanError;
fn try_from((datetime, format): (String, String)) -> Result<Self, Self::Error> {
let datetime = chrono::NaiveDateTime::parse_from_str(&datetime, &format)
.map_err(SpanError::ParseFromStr)
.err_ctx(DateTimeError)?;
Ok(Self { datetime, format })
}
}
impl TryFrom<(&str, &str)> for DateTime {
type Error = SpanError;
fn try_from((datetime, format): (&str, &str)) -> Result<Self, Self::Error> {
let datetime = chrono::NaiveDateTime::parse_from_str(datetime, format)
.map_err(SpanError::ParseFromStr)
.err_ctx(DateTimeError)?;
Ok(Self {
datetime,
format: format.to_string(),
})
}
}
impl TryFrom<&str> for DateTime {
type Error = SpanError;
fn try_from(datetime: &str) -> Result<Self, Self::Error> {
let datetime = chrono::NaiveDateTime::parse_from_str(datetime, &BASE_DATETIME_FORMAT.get())
.map_err(SpanError::ParseFromStr)
.err_ctx(DateTimeError)?;
Ok(Self {
datetime,
format: BASE_DATETIME_FORMAT.get().to_string(),
})
}
}
impl From<chrono::DateTime<Utc>> for DateTime {
fn from(value: chrono::DateTime<Utc>) -> Self {
Self::from(value.naive_utc())
}
}
impl TryFrom<&DateTime> for chrono::DateTime<Utc> {
type Error = SpanError;
fn try_from(value: &DateTime) -> Result<Self, Self::Error> {
let date = value.datetime;
match Utc::now()
.with_year(date.year())
.and_then(|utc| utc.with_month(date.month()))
.and_then(|utc| utc.with_day(date.day()))
.and_then(|utc| utc.with_hour(date.hour()))
.and_then(|utc| utc.with_minute(date.minute()))
.and_then(|utc| utc.with_second(date.second()))
{
Some(utc) => Ok(utc),
None => Err(SpanError::InvalidUtc).err_ctx(DateTimeError),
}
}
}
#[cfg(test)]
pub mod test {
use chrono::TimeDelta;
use super::*;
#[test]
fn datetime_add_overflow() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Day, i32::MAX);
assert_eq!(
new_datetime,
Err(SpanError::InvalidUpdate(
"Cannot Add/Remove 2147483647 Day to/from 2023-10-09 00:00:00".to_string()
))
.err_ctx(DateTimeError)
);
Ok(())
}
#[test]
fn datetime_add_one_year() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Year, 1)?;
assert_eq!(new_datetime.to_string(), "2024-10-09 00:00:00".to_string());
Ok(())
}
#[test]
fn datetime_remove_one_year() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Year, -1)?;
assert_eq!(new_datetime.to_string(), "2022-10-09 00:00:00".to_string());
Ok(())
}
#[test]
fn datetime_add_one_month() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Month, 1)?;
assert_eq!(new_datetime.to_string(), "2023-11-09 00:00:00".to_string());
Ok(())
}
#[test]
fn datetime_remove_one_month() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Month, -1)?;
assert_eq!(new_datetime.to_string(), "2023-09-09 00:00:00".to_string());
Ok(())
}
#[test]
fn datetime_add_one_day() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Day, 1)?;
assert_eq!(new_datetime.to_string(), "2023-10-10 00:00:00".to_string());
Ok(())
}
#[test]
fn datetime_remove_one_day() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Day, -1)?;
assert_eq!(new_datetime.to_string(), "2023-10-08 00:00:00".to_string());
Ok(())
}
#[test]
fn datetime_add_one_hour() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Hour, 1)?;
assert_eq!(new_datetime.to_string(), "2023-10-09 01:00:00".to_string());
Ok(())
}
#[test]
fn datetime_remove_one_hour() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Hour, -1)?;
assert_eq!(new_datetime.to_string(), "2023-10-08 23:00:00".to_string());
Ok(())
}
#[test]
fn datetime_add_one_minute() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Minute, 1)?;
assert_eq!(new_datetime.to_string(), "2023-10-09 00:01:00".to_string());
Ok(())
}
#[test]
fn datetime_remove_one_minute() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Minute, -1)?;
assert_eq!(new_datetime.to_string(), "2023-10-08 23:59:00".to_string());
Ok(())
}
#[test]
fn datetime_add_one_second() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Second, 1)?;
assert_eq!(new_datetime.to_string(), "2023-10-09 00:00:01".to_string());
Ok(())
}
#[test]
fn datetime_remove_one_second() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let new_datetime = datetime.update(DateTimeUnit::Second, -1)?;
assert_eq!(new_datetime.to_string(), "2023-10-08 23:59:59".to_string());
Ok(())
}
#[test]
fn datetime_serialize() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let Ok(serialized) = serde_json::to_string(&datetime) else {
panic!("Error while serializing datetime");
};
assert_eq!(
serialized,
"{\"datetime\":\"2023-10-09T00:00:00\"}".to_string()
);
Ok(())
}
#[test]
fn datetime_deserialize() -> Result<(), SpanError> {
let serialized = "{\"datetime\":\"2023-10-09T00:00:00\"}".to_string();
let Ok(datetime) = serde_json::from_str::<DateTime>(&serialized) else {
panic!("Error while deserializing datetime");
};
assert_eq!(datetime.to_string(), "2023-10-09 00:00:00".to_string());
assert_eq!(datetime.format, BASE_DATETIME_FORMAT.get().to_string());
Ok(())
}
#[test]
fn datetime_serialize_format() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?.format("%d/%m/%YT%H_%M_%S");
let Ok(serialized) = serde_json::to_string(&datetime) else {
panic!("Error while serializing datetime");
};
assert_eq!(
serialized,
"{\"datetime\":\"2023-10-09T00:00:00\"}".to_string()
);
Ok(())
}
#[test]
fn datetime_deserialize_format() -> Result<(), SpanError> {
#[derive(Deserialize)]
struct CustomFmt(#[serde(deserialize_with = "DateTime::deserialize_with_format")] DateTime);
let serialized =
"{\"datetime\":\"2023-10-09T00:00:00\",\"format\":\"%Y-%m-%dT%H:%M:%S\"}".to_string();
let Ok(CustomFmt(datetime)) = serde_json::from_str::<CustomFmt>(&serialized) else {
panic!("Error while deserializing datetime");
};
assert_eq!(
datetime.format("%d/%m/%YT%H_%M_%S").to_string(),
"09/10/2023T00_00_00".to_string()
);
Ok(())
}
#[test]
fn datetime_serialize_in_struct() -> Result<(), SpanError> {
#[derive(Serialize)]
struct Test {
begin_at: DateTime,
}
let test = Test {
begin_at: DateTime::new(2023, 10, 9)?,
};
let Ok(serialized) = serde_json::to_string(&test) else {
panic!("Error while serializing datetime");
};
assert_eq!(
serialized,
"{\"begin_at\":{\"datetime\":\"2023-10-09T00:00:00\"}}".to_string()
);
Ok(())
}
#[test]
fn datetime_deserialize_in_struct() -> Result<(), SpanError> {
#[derive(Deserialize)]
struct Test {
begin_at: DateTime,
}
let serialized = "{\"begin_at\":{\"datetime\":\"2023-10-09T00:00:00\"}}".to_string();
let Ok(test) = serde_json::from_str::<Test>(&serialized) else {
panic!("Error while deserializing datetime");
};
assert_eq!(test.begin_at.to_string(), "2023-10-09 00:00:00".to_string());
assert_eq!(test.begin_at.format, BASE_DATETIME_FORMAT.get().to_string());
Ok(())
}
#[test]
fn next_month_january_to_february() -> Result<(), SpanError> {
let mut datetime = DateTime::new(2023, 1, 31)?.with_time(12, 9, 27)?;
datetime = datetime.next(DateTimeUnit::Month)?;
assert_eq!(datetime.to_string(), "2023-02-28 12:09:27".to_string());
Ok(())
}
#[test]
fn next_month_february_to_march() -> Result<(), SpanError> {
let mut datetime = DateTime::new(2023, 2, 28)?.with_time(12, 9, 27)?;
datetime = datetime.next(DateTimeUnit::Month)?;
assert_eq!(datetime.to_string(), "2023-03-28 12:09:27".to_string());
Ok(())
}
#[test]
fn next_month() -> Result<(), SpanError> {
let mut datetime = DateTime::new(2023, 10, 9)?;
datetime = datetime.next(DateTimeUnit::Month)?;
assert_eq!(datetime.to_string(), "2023-11-09 00:00:00".to_string());
Ok(())
}
#[test]
fn next_minute() -> Result<(), SpanError> {
let mut datetime = DateTime::new(2023, 10, 9)?;
datetime = datetime.next(DateTimeUnit::Minute)?;
assert_eq!(datetime.to_string(), "2023-10-09 00:01:00".to_string());
Ok(())
}
#[test]
fn next_month_on_december() -> Result<(), SpanError> {
let mut datetime = DateTime::new(2023, 12, 9)?;
datetime = datetime.next(DateTimeUnit::Month)?;
assert_eq!(datetime.to_string(), "2024-01-09 00:00:00".to_string());
Ok(())
}
#[test]
fn next_hour_on_midnight() -> Result<(), SpanError> {
let mut datetime = DateTime::new(2023, 10, 9)?.with_time(23, 59, 34)?;
datetime = datetime.next(DateTimeUnit::Hour)?;
assert_eq!(datetime.to_string(), "2023-10-10 00:59:34".to_string());
Ok(())
}
#[test]
fn next_day_28_february_leap_year() -> Result<(), SpanError> {
let mut datetime = DateTime::new(2024, 2, 28)?;
datetime = datetime.next(DateTimeUnit::Day)?;
assert_eq!(datetime.to_string(), "2024-02-29 00:00:00".to_string());
Ok(())
}
#[test]
fn next_day_28_february_non_leap_year() -> Result<(), SpanError> {
let mut datetime = DateTime::new(2023, 2, 28)?;
datetime = datetime.next(DateTimeUnit::Day)?;
assert_eq!(datetime.to_string(), "2023-03-01 00:00:00".to_string());
Ok(())
}
#[test]
fn matches_every_unit_in_datetime() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?.with_time(5, 23, 18)?;
assert!(datetime.matches(DateTimeUnit::Year, 2023));
assert!(datetime.matches(DateTimeUnit::Month, 10));
assert!(datetime.matches(DateTimeUnit::Day, 9));
assert!(datetime.matches(DateTimeUnit::Hour, 5));
assert!(datetime.matches(DateTimeUnit::Minute, 23));
assert!(datetime.matches(DateTimeUnit::Second, 18));
Ok(())
}
#[test]
fn is_in_future_yesterday() -> Result<(), SpanError> {
let mut datetime = DateTime::now()?;
datetime = datetime.update(DateTimeUnit::Day, -1)?;
assert!(!datetime.is_in_future()?);
Ok(())
}
#[test]
fn is_in_future_tomorrow() -> Result<(), SpanError> {
let mut datetime = DateTime::now()?;
datetime = datetime.update(DateTimeUnit::Day, 1)?;
assert!(datetime.is_in_future()?);
Ok(())
}
#[test]
fn is_in_future_now() -> Result<(), SpanError> {
let datetime = DateTime::now()?;
assert!(!datetime.is_in_future()?);
Ok(())
}
#[test]
fn elapsed_one_year() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?;
let lhs = DateTime::new(2022, 10, 9)?;
assert_eq!(datetime.elapsed(&lhs), TimeDelta::try_days(365).unwrap());
Ok(())
}
#[test]
fn elapsed_one_second() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?.with_time(0, 0, 1)?;
let lhs = DateTime::new(2023, 10, 9)?.with_time(0, 0, 0)?;
assert_eq!(datetime.elapsed(&lhs), TimeDelta::try_seconds(1).unwrap());
Ok(())
}
#[test]
fn elapsed_multiple_units() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?.with_time(1, 1, 1)?;
let lhs = DateTime::new(2023, 10, 8)?;
assert_eq!(
datetime.elapsed(&lhs),
TimeDelta::try_days(1)
.unwrap()
.checked_add(&TimeDelta::try_hours(1).unwrap())
.unwrap()
.checked_add(&TimeDelta::try_minutes(1).unwrap())
.unwrap()
.checked_add(&TimeDelta::try_seconds(1).unwrap())
.unwrap()
);
Ok(())
}
#[test]
fn unit_elapsed() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?.with_time(1, 1, 1)?;
let rhs = DateTime::new(2023, 10, 8)?.with_time(0, 0, 0)?;
let years_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Year)?;
let months_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Month)?;
let days_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Day)?;
let hours_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Hour)?;
let minutes_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Minute)?;
let seconds_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Second)?;
assert_eq!(years_in_between, 0);
assert_eq!(months_in_between, 0);
assert_eq!(days_in_between, 1);
assert_eq!(hours_in_between, days_in_between * 24 + 1);
assert_eq!(minutes_in_between, hours_in_between * 60 + 1);
assert_eq!(seconds_in_between, minutes_in_between * 60 + 1);
Ok(())
}
#[test]
fn unit_elapsed_leap_year_days() -> Result<(), SpanError> {
let datetime = DateTime::new(2024, 3, 12)?;
let rhs = DateTime::new(2024, 1, 12)?;
let years_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Year)?;
let months_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Month)?;
let days_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Day)?;
assert_eq!(years_in_between, 0);
assert_eq!(months_in_between, years_in_between * 12 + 2);
assert_eq!(days_in_between, 60);
Ok(())
}
#[test]
fn unit_elapsed_non_leap_year_days() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 3, 12)?;
let rhs = DateTime::new(2023, 1, 12)?;
let years_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Year)?;
let months_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Month)?;
let days_in_between = datetime.unit_elapsed(&rhs, DateTimeUnit::Day)?;
assert_eq!(years_in_between, 0);
assert_eq!(months_in_between, years_in_between * 12 + 2);
assert_eq!(days_in_between, 59);
Ok(())
}
#[test]
fn clear_time() -> Result<(), SpanError> {
let datetime = DateTime::new(2023, 10, 9)?.with_time(1, 1, 1)?;
let datetime = datetime.clear_time();
assert_eq!(datetime.to_string(), "2023-10-09 00:00:00".to_string());
Ok(())
}
#[test]
fn datetime_from_timestamp_milli() -> Result<(), SpanError> {
let timestamp: TimestampMilli = 1735683010.into();
let datetime: DateTime = DateTime::try_from(timestamp)?;
assert_eq!(datetime.to_string(), "2024-12-31 22:10:10");
Ok(())
}
#[test]
fn datetime_from_timestamp_micro() -> Result<(), SpanError> {
let timestamp: TimestampMicro = 1735683010000.into();
let datetime: DateTime = DateTime::try_from(timestamp)?;
assert_eq!(datetime.to_string(), "2024-12-31 22:10:10");
Ok(())
}
#[test]
fn datetime_from_timestamp_nano() -> Result<(), SpanError> {
let timestamp: TimestampNano = 1735683010000000.into();
let datetime: DateTime = DateTime::try_from(timestamp)?;
assert_eq!(datetime.to_string(), "2024-12-31 22:10:10");
Ok(())
}
#[test]
fn timestamp_milli_into_datetime() -> Result<(), SpanError> {
let timestamp: TimestampMilli = 1735683010.into();
let datetime: DateTime = timestamp.try_into()?;
assert_eq!(datetime.to_string(), "2024-12-31 22:10:10");
Ok(())
}
#[test]
fn timestamp_micro_into_datetime() -> Result<(), SpanError> {
let timestamp: TimestampMicro = 1735683010000.into();
let datetime: DateTime = timestamp.try_into()?;
assert_eq!(datetime.to_string(), "2024-12-31 22:10:10");
Ok(())
}
#[test]
fn timestamp_nano_into_datetime() -> Result<(), SpanError> {
let timestamp: TimestampNano = 1735683010000000.into();
let datetime: DateTime = timestamp.try_into()?;
assert_eq!(datetime.to_string(), "2024-12-31 22:10:10");
Ok(())
}
#[test]
fn i64_into_datetime() -> Result<(), SpanError> {
let datetime: DateTime = TimestampMilli::from(1735683010).try_into()?;
assert_eq!(datetime.to_string(), "2024-12-31 22:10:10");
Ok(())
}
}
#[cfg(all(feature = "date", feature = "datetime"))]
mod date_into_datetime {
use crate::GetInner;
impl From<crate::date::Date> for crate::datetime::DateTime {
fn from(value: crate::date::Date) -> Self {
let datetime = chrono::NaiveDateTime::new(value.date(), chrono::NaiveTime::default());
Self {
datetime,
format: crate::datetime::BASE_DATETIME_FORMAT.get().to_string(),
}
}
}
#[cfg(test)]
mod test {
use crate::span::Span;
#[test]
fn date_into_datetime() -> Result<(), crate::error::SpanError> {
let date = crate::date::Date::new(2023, 10, 9)?;
let datetime = crate::datetime::DateTime::from(date);
assert_eq!(datetime.to_string(), "2023-10-09 00:00:00".to_string());
Ok(())
}
#[test]
#[ignore]
fn date_into_datetime_wrong_format() -> Result<(), crate::error::SpanError> {
crate::builder::SpanBuilder::builder()
.datetime_format("%Y-%m-%d %H:%M:%S")
.date_format("%d/%m/%Y")
.build();
let date = crate::date::Date::new(2023, 10, 9)?;
let datetime = crate::datetime::DateTime::from(date);
assert_eq!(datetime.to_string(), "2023-10-09 00:00:00".to_string());
Ok(())
}
}
}
#[cfg(all(feature = "time", feature = "datetime"))]
mod time_into_datetime {
use crate::GetInner;
impl From<crate::time::Time> for crate::datetime::DateTime {
fn from(value: crate::time::Time) -> Self {
let datetime = chrono::NaiveDateTime::new(chrono::NaiveDate::default(), value.time());
Self {
datetime,
format: crate::datetime::BASE_DATETIME_FORMAT.get().to_string(),
}
}
}
#[cfg(test)]
mod test {
use crate::span::Span;
#[test]
fn time_into_datetime() -> Result<(), crate::error::SpanError> {
let time = crate::time::Time::new(13, 27, 57)?;
let datetime = crate::datetime::DateTime::from(time);
assert_eq!(datetime.to_string(), "1970-01-01 13:27:57".to_string());
Ok(())
}
#[test]
#[ignore]
fn time_into_datetime_wrong_format() -> Result<(), crate::error::SpanError> {
crate::builder::SpanBuilder::builder()
.datetime_format("%Y-%m-%d %H_%M_%S")
.time_format("%H:%M:%S")
.build();
let time = crate::time::Time::new(13, 27, 57)?;
let datetime = crate::datetime::DateTime::from(time);
assert_eq!(datetime.to_string(), "1970-01-01 13_27_57".to_string());
Ok(())
}
}
}