use crate::constants::MONTH_NAMES_PERSIAN;
use crate::date::ParsiDate;
use crate::error::{DateError, ParseErrorKind};
use crate::season::Season;
use chrono::{Duration, Local, NaiveDateTime, Timelike};
use std::fmt;
use std::ops::{Add, Sub};
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ParsiDateTime {
date: ParsiDate,
hour: u32,
minute: u32,
second: u32,
}
impl ParsiDateTime {
pub fn new(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
) -> Result<Self, DateError> {
let date = ParsiDate::new(year, month, day)?;
if hour > 23 || minute > 59 || second > 59 {
return Err(DateError::InvalidTime);
}
Ok(ParsiDateTime {
date,
hour,
minute,
second,
})
}
pub const unsafe fn new_unchecked(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
) -> Self {
ParsiDateTime {
date: unsafe { ParsiDate::new_unchecked(year, month, day) },
hour,
minute,
second,
}
}
pub fn from_date_and_time(
date: ParsiDate,
hour: u32,
minute: u32,
second: u32,
) -> Result<Self, DateError> {
if hour > 23 || minute > 59 || second > 59 {
return Err(DateError::InvalidTime);
}
Ok(ParsiDateTime {
date,
hour,
minute,
second,
})
}
pub fn from_gregorian(gregorian_dt: NaiveDateTime) -> Result<Self, DateError> {
let parsi_date = ParsiDate::from_gregorian(gregorian_dt.date())?;
let hour = gregorian_dt.hour();
let minute = gregorian_dt.minute();
let second = gregorian_dt.second();
Ok(ParsiDateTime {
date: parsi_date,
hour,
minute,
second,
})
}
pub fn to_gregorian(&self) -> Result<NaiveDateTime, DateError> {
if !self.is_valid() {
if !self.date.is_valid() {
return Err(DateError::InvalidDate);
} else {
return Err(DateError::InvalidTime);
}
}
let gregorian_date = self.date.to_gregorian_internal()?;
gregorian_date
.and_hms_opt(self.hour, self.minute, self.second)
.ok_or(DateError::GregorianConversionError)
}
pub fn now() -> Result<Self, DateError> {
let now_local: chrono::DateTime<Local> = Local::now();
let naive_local: NaiveDateTime = now_local.naive_local();
Self::from_gregorian(naive_local)
}
#[inline]
pub const fn date(&self) -> ParsiDate {
self.date
}
#[inline]
pub const fn year(&self) -> i32 {
self.date.year()
}
#[inline]
pub const fn month(&self) -> u32 {
self.date.month()
}
#[inline]
pub const fn day(&self) -> u32 {
self.date.day()
}
#[inline]
pub const fn hour(&self) -> u32 {
self.hour
}
#[inline]
pub const fn minute(&self) -> u32 {
self.minute
}
#[inline]
pub const fn second(&self) -> u32 {
self.second
}
#[inline]
pub const fn time(&self) -> (u32, u32, u32) {
(self.hour, self.minute, self.second)
}
#[inline]
pub fn season(&self) -> Result<Season, DateError> {
self.date.season() }
pub fn is_valid(&self) -> bool {
self.date.is_valid() && self.hour <= 23
&& self.minute <= 59
&& self.second <= 59
}
#[inline]
pub fn week_of_year(&self) -> Result<u32, DateError> {
self.date.week_of_year() }
pub fn format(&self, pattern: &str) -> String {
let mut result = String::with_capacity(pattern.len() + 20); let mut chars = pattern.chars().peekable();
let mut weekday_name_cache: Option<Result<String, DateError>> = None;
let mut ordinal_day_cache: Option<Result<u32, DateError>> = None;
let mut weekday_num_cache: Option<Result<u32, DateError>> = None;
let mut season_cache: Option<Result<Season, DateError>> = None;
let mut week_of_year_cache: Option<Result<u32, DateError>> = None;
while let Some(c) = chars.next() {
if c == '%' {
match chars.next() {
Some('H') => result.push_str(&format!("{:02}", self.hour)),
Some('M') => result.push_str(&format!("{:02}", self.minute)),
Some('S') => result.push_str(&format!("{:02}", self.second)),
Some('T') => result.push_str(&format!(
"{:02}:{:02}:{:02}",
self.hour, self.minute, self.second
)),
Some('%') => result.push('%'),
Some('Y') => result.push_str(&self.year().to_string()),
Some('m') => result.push_str(&format!("{:02}", self.month())),
Some('d') => result.push_str(&format!("{:02}", self.day())),
Some('B') => {
let month_index = self.month().saturating_sub(1) as usize;
if let Some(name) = MONTH_NAMES_PERSIAN.get(month_index) {
result.push_str(name);
} else {
result.push_str("?InvalidMonth?");
}
}
Some('A') => {
if weekday_name_cache.is_none() {
weekday_name_cache = Some(self.date.weekday_internal());
}
match weekday_name_cache.as_ref().unwrap() {
Ok(name) => result.push_str(name),
Err(_) => result.push_str("?WeekdayError?"),
}
}
Some('w') => {
if weekday_num_cache.is_none() {
weekday_num_cache = Some(self.date.weekday_num_sat_0());
}
match weekday_num_cache.as_ref().unwrap() {
Ok(num) => result.push_str(&num.to_string()),
Err(_) => result.push('?'),
}
}
Some('j') => {
if ordinal_day_cache.is_none() {
ordinal_day_cache = Some(self.date.ordinal_internal());
}
match ordinal_day_cache.as_ref().unwrap() {
Ok(ord) => result.push_str(&format!("{:03}", ord)),
Err(_) => result.push_str("???"),
}
}
Some('K') => {
if season_cache.is_none() {
season_cache = Some(self.date.season()); }
match season_cache.as_ref().unwrap() {
Ok(season) => result.push_str(season.name_persian()),
Err(_) => result.push_str("?SeasonError?"),
}
}
Some('W') => {
if week_of_year_cache.is_none() {
week_of_year_cache = Some(self.date.week_of_year());
}
match week_of_year_cache.as_ref().unwrap() {
Ok(week_num) => result.push_str(&format!("{:02}", week_num)), Err(_) => result.push_str("?WeekError?"),
}
}
Some(other) => {
result.push('%');
result.push(other);
}
None => {
result.push('%');
break;
}
}
} else {
result.push(c);
}
}
result }
pub fn parse(s: &str, format: &str) -> Result<Self, DateError> {
let mut parsed_year: Option<i32> = None;
let mut parsed_month: Option<u32> = None;
let mut parsed_day: Option<u32> = None;
let mut parsed_hour: Option<u32> = None;
let mut parsed_minute: Option<u32> = None;
let mut parsed_second: Option<u32> = None;
let mut s_bytes = s.as_bytes();
let mut fmt_bytes = format.as_bytes();
while !fmt_bytes.is_empty() {
if fmt_bytes[0] == b'%' {
if fmt_bytes.len() < 2 {
return Err(DateError::ParseError(ParseErrorKind::FormatMismatch));
}
match fmt_bytes[1] {
b'H' | b'M' | b'S' => {
if s_bytes.len() < 2 || !s_bytes[0..2].iter().all(|b| b.is_ascii_digit()) {
return Err(DateError::ParseError(ParseErrorKind::InvalidNumber));
}
let num_str = unsafe { std::str::from_utf8_unchecked(&s_bytes[0..2]) };
let val: u32 = num_str
.parse()
.map_err(|_| DateError::ParseError(ParseErrorKind::InvalidNumber))?;
match fmt_bytes[1] {
b'H' => parsed_hour = Some(val),
b'M' => parsed_minute = Some(val),
b'S' => parsed_second = Some(val),
_ => unreachable!(),
}
s_bytes = &s_bytes[2..];
fmt_bytes = &fmt_bytes[2..];
}
b'T' => {
if s_bytes.len() < 8
|| !s_bytes[0..2].iter().all(|b| b.is_ascii_digit())
|| s_bytes[2] != b':'
|| !s_bytes[3..5].iter().all(|b| b.is_ascii_digit())
|| s_bytes[5] != b':'
|| !s_bytes[6..8].iter().all(|b| b.is_ascii_digit())
{
return Err(DateError::ParseError(ParseErrorKind::FormatMismatch));
}
let h_str = unsafe { std::str::from_utf8_unchecked(&s_bytes[0..2]) };
let m_str = unsafe { std::str::from_utf8_unchecked(&s_bytes[3..5]) };
let s_str = unsafe { std::str::from_utf8_unchecked(&s_bytes[6..8]) };
parsed_hour =
Some(h_str.parse().map_err(|_| {
DateError::ParseError(ParseErrorKind::InvalidNumber)
})?);
parsed_minute =
Some(m_str.parse().map_err(|_| {
DateError::ParseError(ParseErrorKind::InvalidNumber)
})?);
parsed_second =
Some(s_str.parse().map_err(|_| {
DateError::ParseError(ParseErrorKind::InvalidNumber)
})?);
s_bytes = &s_bytes[8..];
fmt_bytes = &fmt_bytes[2..];
}
b'%' => {
if s_bytes.is_empty() || s_bytes[0] != b'%' {
return Err(DateError::ParseError(ParseErrorKind::FormatMismatch));
}
s_bytes = &s_bytes[1..];
fmt_bytes = &fmt_bytes[2..];
}
b'Y' => {
if s_bytes.len() < 4 || !s_bytes[0..4].iter().all(|b| b.is_ascii_digit()) {
return Err(DateError::ParseError(ParseErrorKind::InvalidNumber));
}
let year_str = unsafe { std::str::from_utf8_unchecked(&s_bytes[0..4]) };
parsed_year =
Some(year_str.parse().map_err(|_| {
DateError::ParseError(ParseErrorKind::InvalidNumber)
})?);
s_bytes = &s_bytes[4..];
fmt_bytes = &fmt_bytes[2..];
}
b'm' | b'd' => {
if s_bytes.len() < 2 || !s_bytes[0..2].iter().all(|b| b.is_ascii_digit()) {
return Err(DateError::ParseError(ParseErrorKind::InvalidNumber));
}
let num_str = unsafe { std::str::from_utf8_unchecked(&s_bytes[0..2]) };
let val: u32 = num_str
.parse()
.map_err(|_| DateError::ParseError(ParseErrorKind::InvalidNumber))?;
if fmt_bytes[1] == b'm' {
parsed_month = Some(val);
} else {
parsed_day = Some(val);
}
s_bytes = &s_bytes[2..];
fmt_bytes = &fmt_bytes[2..];
}
b'B' => {
fmt_bytes = &fmt_bytes[2..];
let mut found_month = false;
let mut best_match_len = 0;
let mut matched_month_idx = 0;
let current_s_str = match std::str::from_utf8(s_bytes) {
Ok(s_str) => s_str,
Err(_) => {
return Err(DateError::ParseError(
ParseErrorKind::InvalidMonthName,
));
}
};
for (idx, month_name) in MONTH_NAMES_PERSIAN.iter().enumerate() {
if current_s_str.starts_with(month_name) {
best_match_len = month_name.len();
matched_month_idx = idx;
found_month = true;
break;
}
}
if !found_month {
return Err(DateError::ParseError(ParseErrorKind::InvalidMonthName));
}
parsed_month = Some((matched_month_idx + 1) as u32);
s_bytes = &s_bytes[best_match_len..];
}
b'A' | b'w' | b'j' | b'K' | b'W' => {
return Err(DateError::ParseError(ParseErrorKind::UnsupportedSpecifier));
}
_ => return Err(DateError::ParseError(ParseErrorKind::UnsupportedSpecifier)),
}
} else {
if s_bytes.is_empty() || s_bytes[0] != fmt_bytes[0] {
return Err(DateError::ParseError(ParseErrorKind::FormatMismatch));
}
s_bytes = &s_bytes[1..];
fmt_bytes = &fmt_bytes[1..];
}
}
if !s_bytes.is_empty() {
return Err(DateError::ParseError(ParseErrorKind::FormatMismatch));
}
match (
parsed_year,
parsed_month,
parsed_day,
parsed_hour,
parsed_minute,
parsed_second,
) {
(Some(y), Some(m), Some(d), Some(h), Some(min), Some(s)) => {
ParsiDateTime::new(y, m, d, h, min, s).map_err(|e| match e {
DateError::InvalidDate => {
DateError::ParseError(ParseErrorKind::InvalidDateValue)
}
DateError::InvalidTime => {
DateError::ParseError(ParseErrorKind::InvalidTimeValue)
}
other_error => other_error,
})
}
_ => Err(DateError::ParseError(ParseErrorKind::FormatMismatch)),
}
}
pub fn add_duration(&self, duration: Duration) -> Result<Self, DateError> {
if !self.is_valid() {
return Err(if !self.date.is_valid() {
DateError::InvalidDate
} else {
DateError::InvalidTime
});
}
let gregorian_dt = self.to_gregorian()?;
let new_gregorian_dt = gregorian_dt
.checked_add_signed(duration)
.ok_or(DateError::ArithmeticOverflow)?;
Self::from_gregorian(new_gregorian_dt)
}
pub fn sub_duration(&self, duration: Duration) -> Result<Self, DateError> {
self.add_duration(-duration)
}
pub fn add_days(&self, days: i64) -> Result<Self, DateError> {
if !self.is_valid() {
return Err(if !self.date.is_valid() {
DateError::InvalidDate
} else {
DateError::InvalidTime
});
}
let new_date = self.date.add_days(days)?;
Ok(ParsiDateTime {
date: new_date,
..*self
}) }
pub fn sub_days(&self, days: u64) -> Result<Self, DateError> {
if !self.is_valid() {
return Err(if !self.date.is_valid() {
DateError::InvalidDate
} else {
DateError::InvalidTime
});
}
let new_date = self.date.sub_days(days)?;
Ok(ParsiDateTime {
date: new_date,
..*self
})
}
pub fn add_months(&self, months: i32) -> Result<Self, DateError> {
if !self.is_valid() {
return Err(if !self.date.is_valid() {
DateError::InvalidDate
} else {
DateError::InvalidTime
});
}
let new_date = self.date.add_months(months)?;
Ok(ParsiDateTime {
date: new_date,
..*self
})
}
pub fn sub_months(&self, months: u32) -> Result<Self, DateError> {
if !self.is_valid() {
return Err(if !self.date.is_valid() {
DateError::InvalidDate
} else {
DateError::InvalidTime
});
}
let new_date = self.date.sub_months(months)?;
Ok(ParsiDateTime {
date: new_date,
..*self
})
}
pub fn add_years(&self, years: i32) -> Result<Self, DateError> {
if !self.is_valid() {
return Err(if !self.date.is_valid() {
DateError::InvalidDate
} else {
DateError::InvalidTime
});
}
let new_date = self.date.add_years(years)?;
Ok(ParsiDateTime {
date: new_date,
..*self
})
}
pub fn sub_years(&self, years: u32) -> Result<Self, DateError> {
if !self.is_valid() {
return Err(if !self.date.is_valid() {
DateError::InvalidDate
} else {
DateError::InvalidTime
});
}
let new_date = self.date.sub_years(years)?;
Ok(ParsiDateTime {
date: new_date,
..*self
})
}
pub fn with_hour(&self, hour: u32) -> Result<Self, DateError> {
if !self.date.is_valid() {
return Err(DateError::InvalidDate);
}
if hour > 23 {
return Err(DateError::InvalidTime);
}
Ok(ParsiDateTime { hour, ..*self })
}
pub fn with_minute(&self, minute: u32) -> Result<Self, DateError> {
if !self.date.is_valid() {
return Err(DateError::InvalidDate);
}
if minute > 59 {
return Err(DateError::InvalidTime);
}
Ok(ParsiDateTime { minute, ..*self })
}
pub fn with_second(&self, second: u32) -> Result<Self, DateError> {
if !self.date.is_valid() {
return Err(DateError::InvalidDate);
}
if second > 59 {
return Err(DateError::InvalidTime);
}
Ok(ParsiDateTime { second, ..*self })
}
pub fn with_time(&self, hour: u32, minute: u32, second: u32) -> Result<Self, DateError> {
if !self.date.is_valid() {
return Err(DateError::InvalidDate);
}
if hour > 23 || minute > 59 || second > 59 {
return Err(DateError::InvalidTime);
}
Ok(ParsiDateTime {
date: self.date,
hour,
minute,
second,
})
}
pub fn with_year(&self, year: i32) -> Result<Self, DateError> {
let new_date = self.date.with_year(year)?;
Ok(ParsiDateTime {
date: new_date,
..*self
})
}
pub fn with_month(&self, month: u32) -> Result<Self, DateError> {
let new_date = self.date.with_month(month)?;
Ok(ParsiDateTime {
date: new_date,
..*self
})
}
pub fn with_day(&self, day: u32) -> Result<Self, DateError> {
let new_date = self.date.with_day(day)?;
Ok(ParsiDateTime {
date: new_date,
..*self
})
}
pub fn start_of_season(&self) -> Result<Self, DateError> {
let new_date = self.date.start_of_season()?; Ok(ParsiDateTime {
date: new_date,
..*self
}) }
pub fn end_of_season(&self) -> Result<Self, DateError> {
let new_date = self.date.end_of_season()?; Ok(ParsiDateTime {
date: new_date,
..*self
}) }
}
impl fmt::Display for ParsiDateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {:02}:{:02}:{:02}",
self.date, self.hour,
self.minute,
self.second
)
}
}
impl Add<Duration> for ParsiDateTime {
type Output = Result<ParsiDateTime, DateError>;
#[inline]
fn add(self, duration: Duration) -> Self::Output {
self.add_duration(duration)
}
}
impl Sub<Duration> for ParsiDateTime {
type Output = Result<ParsiDateTime, DateError>;
#[inline]
fn sub(self, duration: Duration) -> Self::Output {
self.sub_duration(duration)
}
}
impl Sub<ParsiDateTime> for ParsiDateTime {
type Output = Result<Duration, DateError>;
fn sub(self, other: ParsiDateTime) -> Self::Output {
let self_gregorian = self.to_gregorian()?;
let other_gregorian = other.to_gregorian()?;
Ok(self_gregorian.signed_duration_since(other_gregorian))
}
}