diff --git a/zino-core/src/datetime/date.rs b/zino-core/src/datetime/date.rs new file mode 100644 index 00000000..5bb9a533 --- /dev/null +++ b/zino-core/src/datetime/date.rs @@ -0,0 +1,306 @@ +use crate::{AvroValue, JsonValue}; +use chrono::{format::ParseError, Datelike, Local, NaiveDate}; +use serde::{Deserialize, Serialize, Serializer}; +use std::{ + fmt, + ops::{Add, AddAssign, Sub, SubAssign}, + str::FromStr, + time::Duration, +}; + +/// A wrapper type for [`chrono::NaiveDate`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize)] +pub struct Date(NaiveDate); + +impl Date { + /// Returns a new instance which corresponds to the current date. + #[inline] + pub fn today() -> Self { + Self(Local::now().date_naive()) + } + + /// Returns a new instance which corresponds to the tomorrow date. + #[inline] + pub fn tomorrow() -> Self { + let date = Local::now() + .date_naive() + .succ_opt() + .unwrap_or(NaiveDate::MAX); + Self(date) + } + + /// Returns a new instance which corresponds to the yesterday date. + #[inline] + pub fn yesterday() -> Self { + let date = Local::now() + .date_naive() + .pred_opt() + .unwrap_or(NaiveDate::MIN); + Self(date) + } + + /// Returns a new instance which corresponds to 1st of January 1970. + #[inline] + pub fn epoch() -> Self { + Self(NaiveDate::default()) + } + + /// Counts the days from the 1st of January 1970. + #[inline] + pub fn num_days_from_epoch(&self) -> i32 { + let unix_epoch = NaiveDate::default(); + self.0 + .signed_duration_since(unix_epoch) + .num_days() + .try_into() + .unwrap_or_default() + } + + /// Formats the date with the specified format string. + /// See [`format::strftime`](chrono::format::strftime) for the supported escape sequences. + #[inline] + pub fn format(&self, fmt: &str) -> String { + format!("{}", self.0.format(fmt)) + } + + /// Returns the amount of time elapsed from another date to this one, + /// or zero duration if that date is later than this one. + #[inline] + pub fn duration_since(&self, earlier: Date) -> Duration { + (self.0 - earlier.0).to_std().unwrap_or_default() + } + + /// Returns the duration of time between `self` and `other`. + #[inline] + pub fn span_between(&self, other: Date) -> Duration { + let duration = if self > &other { + self.0 - other.0 + } else { + other.0 - self.0 + }; + duration.to_std().unwrap_or_default() + } + + /// Returns the year number in the calendar date. + #[inline] + pub fn year(&self) -> i32 { + self.0.year() + } + + /// Returns the month number starting from 1. + /// + /// The return value ranges from 1 to 12. + #[inline] + pub fn month(&self) -> u32 { + self.0.month() + } + + /// Returns the day of month starting from 1. + /// + /// The return value ranges from 1 to 31. (The last day of month differs by months.) + #[inline] + pub fn day(&self) -> u32 { + self.0.day() + } + + /// Returns the ISO week number starting from 1. + /// + /// The return value ranges from 1 to 53. (The last week of year differs by years.) + #[inline] + pub fn week(&self) -> u32 { + self.0.iso_week().week() + } + + /// Returns the day of year starting from 1. + /// + /// The return value ranges from 1 to 366. (The last day of year differs by years.) + #[inline] + pub fn day_of_year(&self) -> u32 { + self.0.ordinal() + } + + /// Returns the day of week starting from 0 (Sunday) to 6 (Saturday). + #[inline] + pub fn day_of_week(&self) -> u8 { + self.0.weekday() as u8 + } + + /// Returns `true` if the current year is a leap year. + #[inline] + pub fn is_leap_year(&self) -> bool { + self.0.leap_year() + } + + /// Returns the number of days in the current year. + #[inline] + pub fn days_in_current_year(&self) -> u32 { + if self.is_leap_year() { + 366 + } else { + 365 + } + } + + /// Returns the number of days in the current month. + pub fn days_in_current_month(&self) -> u32 { + let month = self.month(); + match month { + 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, + 4 | 6 | 9 | 11 => 30, + 2 => { + if self.is_leap_year() { + 29 + } else { + 28 + } + } + _ => panic!("invalid month: {month}"), + } + } + + /// Returns the start of the current year. + pub fn start_of_current_year(&self) -> Self { + let year = self.year(); + Self(NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default()) + } + + /// Returns the end of the current year. + pub fn end_of_current_year(&self) -> Self { + let year = self.year(); + Self(NaiveDate::from_ymd_opt(year, 12, 31).unwrap_or_default()) + } + + /// Returns the start of the next year. + pub fn start_of_next_year(&self) -> Self { + let year = self.year() + 1; + Self(NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default()) + } + + /// Returns the start of the current month. + pub fn start_of_current_month(&self) -> Self { + let year = self.year(); + let month = self.month(); + Self(NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default()) + } + + /// Returns the end of the current month. + pub fn end_of_current_month(&self) -> Self { + let year = self.year(); + let month = self.month(); + let day = self.days_in_current_month(); + Self(NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default()) + } + + /// Returns the start of the next month. + pub fn start_of_next_month(&self) -> Self { + let year = self.year(); + let month = self.month(); + let date_opt = if month == 12 { + NaiveDate::from_ymd_opt(year + 1, 1, 1) + } else { + NaiveDate::from_ymd_opt(year, month, 1) + }; + Self(date_opt.unwrap_or_default()) + } +} + +impl Default for Date { + /// Returns an instance which corresponds to **the current date**. + #[inline] + fn default() -> Self { + Self::today() + } +} + +impl fmt::Display for Date { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0.format("%Y-%m-%d")) + } +} + +impl Serialize for Date { + #[inline] + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } +} + +impl From for Date { + #[inline] + fn from(d: NaiveDate) -> Self { + Self(d) + } +} + +impl From for NaiveDate { + #[inline] + fn from(d: Date) -> Self { + d.0 + } +} + +impl From for AvroValue { + #[inline] + fn from(d: Date) -> Self { + AvroValue::Date(d.num_days_from_epoch()) + } +} + +impl From for JsonValue { + #[inline] + fn from(d: Date) -> Self { + JsonValue::String(d.to_string()) + } +} + +impl FromStr for Date { + type Err = ParseError; + + #[inline] + fn from_str(s: &str) -> Result { + s.parse::().map(Self) + } +} + +impl Add for Date { + type Output = Self; + + #[inline] + fn add(self, rhs: Duration) -> Self { + let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range"); + let date = self + .0 + .checked_add_signed(duration) + .expect("`Date + Duration` overflowed"); + Self(date) + } +} + +impl AddAssign for Date { + #[inline] + fn add_assign(&mut self, rhs: Duration) { + *self = *self + rhs; + } +} + +impl Sub for Date { + type Output = Self; + + #[inline] + fn sub(self, rhs: Duration) -> Self { + let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range"); + let date = self + .0 + .checked_sub_signed(duration) + .expect("`Date - Duration` overflowed"); + Self(date) + } +} + +impl SubAssign for Date { + #[inline] + fn sub_assign(&mut self, rhs: Duration) { + *self = *self - rhs; + } +} diff --git a/zino-core/src/datetime/mod.rs b/zino-core/src/datetime/mod.rs index 4f6ca15e..bc31f109 100644 --- a/zino-core/src/datetime/mod.rs +++ b/zino-core/src/datetime/mod.rs @@ -3,7 +3,7 @@ use crate::{AvroValue, JsonValue}; use chrono::{ format::ParseError, Datelike, Days, Local, Months, NaiveDate, NaiveDateTime, NaiveTime, - SecondsFormat, TimeZone, Utc, + SecondsFormat, TimeZone, Timelike, Utc, }; use serde::{Deserialize, Serialize, Serializer}; use std::{ @@ -13,9 +13,13 @@ use std::{ time::Duration, }; +mod date; mod duration; +mod time; +pub use date::Date; pub use duration::{parse_duration, ParseDurationError}; +pub use time::Time; /// Alias for [`chrono::DateTime`](chrono::DateTime). type LocalDateTime = chrono::DateTime; @@ -25,7 +29,7 @@ type LocalDateTime = chrono::DateTime; pub struct DateTime(LocalDateTime); impl DateTime { - /// Returns a new instance which corresponds to the current date. + /// Returns a new instance which corresponds to the current date and time. #[inline] pub fn now() -> Self { Self(Local::now()) @@ -183,45 +187,54 @@ impl DateTime { /// Returns the duration of time between `self` and `other`. #[inline] pub fn span_between(&self, other: DateTime) -> Duration { - let timestamp = self.timestamp_micros(); - let current_timestamp = other.timestamp_micros(); - Duration::from_micros(current_timestamp.abs_diff(timestamp)) + let duration = if self > &other { + self.0 - other.0 + } else { + other.0 - self.0 + }; + duration.to_std().unwrap_or_default() } /// Returns the duration of time between `self` and `DateTime::now()`. #[inline] pub fn span_between_now(&self) -> Duration { - let timestamp = self.timestamp_micros(); - let current_timestamp = Local::now().timestamp_micros(); - Duration::from_micros(current_timestamp.abs_diff(timestamp)) + self.span_between(Self::now()) } /// Returns the duration of time from `self` to `DateTime::now()`. + #[inline] pub fn span_before_now(&self) -> Option { - let timestamp = self.timestamp_micros(); - let current_timestamp = Local::now().timestamp_micros(); - if current_timestamp >= timestamp { - u64::try_from(current_timestamp - timestamp) - .ok() - .map(Duration::from_micros) + let current = Self::now(); + if self <= ¤t { + (current.0 - self.0).to_std().ok() } else { None } } /// Returns the duration of time from `DateTime::now()` to `self`. + #[inline] pub fn span_after_now(&self) -> Option { - let timestamp = self.timestamp_micros(); - let current_timestamp = Local::now().timestamp_micros(); - if current_timestamp <= timestamp { - u64::try_from(timestamp - current_timestamp) - .ok() - .map(Duration::from_micros) + let current = Self::now(); + if self >= ¤t { + (self.0 - current.0).to_std().ok() } else { None } } + /// Retrieves the date component. + #[inline] + pub fn date(&self) -> Date { + self.0.date_naive().into() + } + + /// Retrieves the time component. + #[inline] + pub fn time(&self) -> Time { + self.0.time().into() + } + /// Returns the year number in the calendar date. #[inline] pub fn year(&self) -> i32 { @@ -244,6 +257,46 @@ impl DateTime { self.0.day() } + /// Returns the hour number from 0 to 23. + #[inline] + pub fn hour(&self) -> u32 { + self.0.hour() + } + + /// Returns the minute number from 0 to 59. + #[inline] + pub fn minute(&self) -> u32 { + self.0.minute() + } + + /// Returns the second number from 0 to 59. + #[inline] + pub fn second(&self) -> u32 { + self.0.second() + } + + /// Returns the ISO week number starting from 1. + /// + /// The return value ranges from 1 to 53. (The last week of year differs by years.) + #[inline] + pub fn week(&self) -> u32 { + self.0.iso_week().week() + } + + /// Returns the day of year starting from 1. + /// + /// The return value ranges from 1 to 366. (The last day of year differs by years.) + #[inline] + pub fn day_of_year(&self) -> u32 { + self.0.ordinal() + } + + /// Returns the day of week starting from 0 (Sunday) to 6 (Saturday). + #[inline] + pub fn day_of_week(&self) -> u8 { + self.0.weekday() as u8 + } + /// Returns `true` if the current year is a leap year. #[inline] pub fn is_leap_year(&self) -> bool { @@ -283,7 +336,10 @@ impl DateTime { let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default(); let dt = NaiveDateTime::new(date, NaiveTime::default()); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the end of the current year. @@ -293,7 +349,10 @@ impl DateTime { .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000)) .unwrap_or_default(); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the start of the current month. @@ -303,7 +362,10 @@ impl DateTime { let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default(); let dt = NaiveDateTime::new(date, NaiveTime::default()); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the end of the current month. @@ -315,7 +377,10 @@ impl DateTime { .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000)) .unwrap_or_default(); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the start of the current day. @@ -323,7 +388,10 @@ impl DateTime { let date = self.0.date_naive(); let dt = NaiveDateTime::new(date, NaiveTime::default()); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the end of the current day. @@ -333,7 +401,10 @@ impl DateTime { .and_hms_milli_opt(23, 59, 59, 1_000) .unwrap_or_default(); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the start of the year. @@ -341,7 +412,10 @@ impl DateTime { let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default(); let dt = NaiveDateTime::new(date, NaiveTime::default()); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the end of the year. @@ -351,7 +425,10 @@ impl DateTime { .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000)) .unwrap_or_default(); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the start of the month. @@ -359,7 +436,10 @@ impl DateTime { let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default(); let dt = NaiveDateTime::new(date, NaiveTime::default()); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the end of the month. @@ -369,7 +449,10 @@ impl DateTime { .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000)) .unwrap_or_default(); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the start of the day. @@ -377,7 +460,10 @@ impl DateTime { let date = NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default(); let dt = NaiveDateTime::new(date, NaiveTime::default()); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Returns the end of the month. @@ -387,7 +473,10 @@ impl DateTime { .and_hms_milli_opt(23, 59, 59, 1_000) .unwrap_or_default(); let offset = Local.offset_from_utc_datetime(&dt); - Self(LocalDateTime::from_naive_utc_and_offset(dt, offset)) + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) } /// Adds a duration in months to the date part of the `DateTime`. @@ -445,6 +534,17 @@ impl Serialize for DateTime { } } +impl From for DateTime { + fn from(d: Date) -> Self { + let dt = NaiveDateTime::new(d.into(), NaiveTime::default()); + let offset = Local.offset_from_utc_datetime(&dt); + Self(LocalDateTime::from_naive_utc_and_offset( + dt - offset, + offset, + )) + } +} + impl From for DateTime { #[inline] fn from(dt: LocalDateTime) -> Self { @@ -543,7 +643,7 @@ impl SubAssign for DateTime { #[cfg(test)] mod tests { - use super::DateTime; + use super::{Date, DateTime}; #[test] fn it_parses_datetime() { @@ -553,5 +653,20 @@ mod tests { assert!("2023-06-10 05:17:23.713071 +0800" .parse::() .is_ok()); + + let datetime = "2023-11-30 16:24:30.654321 +0800" + .parse::() + .unwrap(); + let start_day = datetime.start_of_current_day(); + let end_day = datetime.end_of_current_day(); + assert_eq!("2023-11-30", start_day.format_date()); + assert_eq!("00:00:00", start_day.format_time()); + assert_eq!("2023-11-30", end_day.format_date()); + assert_eq!("23:59:60", end_day.format_time()); + + let date = "2023-11-30".parse::().unwrap(); + let datetime = DateTime::from(date); + assert_eq!("2023-11-30", datetime.format_date()); + assert_eq!("00:00:00", datetime.format_time()); } } diff --git a/zino-core/src/datetime/time.rs b/zino-core/src/datetime/time.rs new file mode 100644 index 00000000..b2b3cc58 --- /dev/null +++ b/zino-core/src/datetime/time.rs @@ -0,0 +1,210 @@ +use crate::{AvroValue, JsonValue}; +use chrono::{format::ParseError, Local, NaiveTime, Timelike}; +use serde::{Deserialize, Serialize, Serializer}; +use std::{ + fmt, + ops::{Add, AddAssign, Sub, SubAssign}, + str::FromStr, + time::Duration, +}; + +/// A wrapper type for [`chrono::NaiveTime`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize)] +pub struct Time(NaiveTime); + +impl Time { + /// Returns a new instance which corresponds to the current time. + #[inline] + pub fn now() -> Self { + Self(Local::now().time()) + } + + /// Returns a new instance which corresponds to the midnight. + #[inline] + pub fn midnight() -> Self { + Self(NaiveTime::default()) + } + + /// Returns the number of non-leap seconds past the last midnight. + #[inline] + pub fn num_secs_from_midnight(&self) -> u32 { + self.0.num_seconds_from_midnight() + } + + /// Returns the number of non-leap milliseconds past the last midnight. + #[inline] + pub fn num_millis_from_midnight(&self) -> u32 { + self.0.num_seconds_from_midnight() * 1000 + self.0.nanosecond() / 1_000_000 + } + + /// Returns the number of non-leap microseconds past the last midnight. + #[inline] + pub fn num_micros_from_midnight(&self) -> u32 { + self.0.num_seconds_from_midnight() * 1_000_000 + self.0.nanosecond() / 1000 + } + + /// Formats the time with the specified format string. + /// See [`format::strftime`](chrono::format::strftime) for the supported escape sequences. + #[inline] + pub fn format(&self, fmt: &str) -> String { + format!("{}", self.0.format(fmt)) + } + + /// Returns the amount of time elapsed from another time to this one, + /// or zero duration if that time is later than this one. + #[inline] + pub fn duration_since(&self, earlier: Time) -> Duration { + (self.0 - earlier.0).to_std().unwrap_or_default() + } + + /// Returns the duration of time between `self` and `other`. + #[inline] + pub fn span_between(&self, other: Time) -> Duration { + let duration = if self > &other { + self.0 - other.0 + } else { + other.0 - self.0 + }; + duration.to_std().unwrap_or_default() + } + + /// Returns the duration of time between `self` and `Time::now()`. + #[inline] + pub fn span_between_now(&self) -> Duration { + self.span_between(Self::now()) + } + + /// Returns the duration of time from `self` to `Time::now()`. + #[inline] + pub fn span_before_now(&self) -> Option { + let current = Self::now(); + if self <= ¤t { + (current.0 - self.0).to_std().ok() + } else { + None + } + } + + /// Returns the duration of time from `Time::now()` to `self`. + #[inline] + pub fn span_after_now(&self) -> Option { + let current = Self::now(); + if self >= ¤t { + (self.0 - current.0).to_std().ok() + } else { + None + } + } + + /// Returns the hour number from 0 to 23. + #[inline] + pub fn hour(&self) -> u32 { + self.0.hour() + } + + /// Returns the minute number from 0 to 59. + #[inline] + pub fn minute(&self) -> u32 { + self.0.minute() + } + + /// Returns the second number from 0 to 59. + #[inline] + pub fn second(&self) -> u32 { + self.0.second() + } +} + +impl Default for Time { + /// Returns an instance which corresponds to **the current time**. + #[inline] + fn default() -> Self { + Self::now() + } +} + +impl fmt::Display for Time { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0.format("%H:%M:%S%.f")) + } +} + +impl Serialize for Time { + #[inline] + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&self.to_string()) + } +} + +impl From for Time { + #[inline] + fn from(t: NaiveTime) -> Self { + Self(t) + } +} + +impl From