use crate::{ATTOS_PER_SEC_I128, Dt, Scale};
#[cfg(feature = "jiff-tz")]
use crate::{DtErr, DtErrKind, an_err};
mod printer;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "js", derive(tsify::Tsify))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct YmdHms {
pub(crate) yr: i64,
pub(crate) mo: u8,
pub(crate) day: u8,
pub(crate) hr: u8,
pub(crate) min: u8,
pub(crate) sec: u8, pub(crate) attos: u64, pub(crate) scale: Scale,
}
impl YmdHms {
#[inline]
pub const fn to_dt(&self) -> Dt {
Dt::from_ymd(
self.yr, self.mo, self.day, self.hr, self.min, self.sec, self.attos, self.scale,
)
}
#[inline(always)]
const fn reconstruct(
yr: i64,
mo: u8,
day: u8,
hr: u8,
min: u8,
sec: u8,
attos: u64,
scale: Scale,
) -> Self {
Dt::from_ymd(yr, mo, day, hr, min, sec, attos, scale).to_ymd()
}
pub const fn add_yr(&self, n: i64) -> Self {
if n == 0 {
return *self;
}
let new_yr = self.yr.saturating_add(n);
let max_day = Dt::days_in_month(new_yr, self.mo);
let new_day = Dt::clamp_u8(self.day, 1, max_day);
Self::reconstruct(
new_yr, self.mo, new_day, self.hr, self.min, self.sec, self.attos, self.scale,
)
}
pub const fn add_mo(&self, n: i64) -> Self {
if n == 0 {
return *self;
}
let yr = self.yr as i128;
let mo = self.mo as i128;
let delta = n as i128;
let total_months = yr * 12 + (mo - 1) + delta;
let new_yr = Dt::i128_to_i64(total_months.div_euclid(12));
let new_mo = Dt::clamp_u8((total_months.rem_euclid(12) + 1) as u8, 1, 12);
let max_day = Dt::days_in_month(new_yr, new_mo);
let new_day = Dt::clamp_u8(self.day, 1, max_day);
Self::reconstruct(
new_yr, new_mo, new_day, self.hr, self.min, self.sec, self.attos, self.scale,
)
}
#[inline]
pub const fn add_wk(&self, n: i64) -> Self {
self.add_days(n.saturating_mul(7))
}
pub const fn add_days(&self, n: i64) -> Self {
if n == 0 {
return *self;
}
let jd = Dt::ymd_to_jd(self.yr, self.mo, self.day);
let new_jd = jd.saturating_add(n);
let (new_yr, new_mo, new_day) = Dt::jd_to_ymd(new_jd);
Self::reconstruct(
new_yr, new_mo, new_day, self.hr, self.min, self.sec, self.attos, self.scale,
)
}
#[inline(never)]
const fn _add_attos(&self, attos_delta: i128) -> Self {
let tai = Dt::from_ymd(
self.yr, self.mo, self.day, self.hr, self.min, self.sec, self.attos, self.scale,
);
let new_tai = tai.add(Dt::span(attos_delta));
new_tai.to_ymd()
}
#[inline]
pub const fn add_attos(&self, n: i128) -> Self {
self._add_attos(n)
}
#[inline]
pub const fn add_sec(&self, n: i64) -> Self {
self._add_attos((n as i128).saturating_mul(ATTOS_PER_SEC_I128))
}
#[inline]
pub const fn add_min(&self, n: i64) -> Self {
let delta = (n as i128)
.saturating_mul(60)
.saturating_mul(ATTOS_PER_SEC_I128);
self._add_attos(delta)
}
#[inline]
pub const fn add_hr(&self, n: i64) -> Self {
let delta = (n as i128)
.saturating_mul(3600)
.saturating_mul(ATTOS_PER_SEC_I128);
self._add_attos(delta)
}
#[inline]
pub const fn yr(&self) -> i64 {
self.yr
}
#[inline]
pub const fn mo(&self) -> u8 {
self.mo
}
#[inline]
pub const fn day(&self) -> u8 {
self.day
}
#[inline]
pub const fn hr(&self) -> u8 {
self.hr
}
#[inline]
pub const fn min(&self) -> u8 {
self.min
}
#[inline]
pub const fn sec(&self) -> u8 {
self.sec
}
#[inline]
pub const fn attos(&self) -> u64 {
self.attos
}
#[inline]
pub const fn scale(&self) -> Scale {
self.scale
}
#[inline]
pub const fn iso_yr(&self) -> i64 {
let (iso_yr, _, _) = Dt::_to_iso_wk_date(self.yr, self.mo, self.day);
iso_yr
}
#[inline]
pub const fn iso_wk(&self) -> u8 {
let (_, iso_wk, _) = Dt::_to_iso_wk_date(self.yr, self.mo, self.day);
iso_wk
}
#[inline]
pub const fn day_of_yr(&self) -> u16 {
Dt::_day_of_yr(self.yr, self.mo, self.day)
}
#[inline]
pub const fn wkday(&self) -> u8 {
let jd = Dt::ymd_to_jd(self.yr, self.mo, self.day);
Dt::jd_to_wkday(jd)
}
#[inline]
pub const fn wk_of_yr_sun(&self) -> u8 {
Dt::_wk_sun(self.yr, self.day_of_yr())
}
#[inline]
pub const fn wk_of_yr_mon(&self) -> u8 {
Dt::_wk_mon(self.yr, self.day_of_yr())
}
}
#[cfg(feature = "jiff-tz")]
impl YmdHms {
pub fn add_yr_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
let zoned = self
.to_jiff_zoned(tz)?
.checked_add(jiff::Span::new().years(n))
.map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
Ok(self.from_jiff_zoned(zoned))
}
pub fn add_mo_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
let zoned = self
.to_jiff_zoned(tz)?
.checked_add(jiff::Span::new().months(n))
.map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
Ok(self.from_jiff_zoned(zoned))
}
#[inline]
pub fn add_wk_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
self.add_days_tz(n.saturating_mul(7), tz)
}
pub fn add_days_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
let zoned = self
.to_jiff_zoned(tz)?
.checked_add(jiff::Span::new().days(n))
.map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
Ok(self.from_jiff_zoned(zoned))
}
pub fn add_hr_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
let new_zoned = self
.to_jiff_zoned(tz)?
.checked_add(jiff::Span::new().hours(n))
.map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
Ok(self.from_jiff_zoned(new_zoned))
}
pub fn add_min_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
let zoned = self
.to_jiff_zoned(tz)?
.checked_add(jiff::Span::new().minutes(n))
.map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
Ok(self.from_jiff_zoned(zoned))
}
pub fn add_sec_tz(&self, n: i64, tz: &str) -> Result<Self, DtErr> {
let zoned = self
.to_jiff_zoned(tz)?
.checked_add(jiff::Span::new().seconds(n))
.map_err(|e| an_err!(DtErrKind::OutOfRange, "{}", e))?;
Ok(self.from_jiff_zoned(zoned))
}
fn to_jiff_zoned(&self, tz: &str) -> Result<jiff::Zoned, DtErr> {
use jiff::civil;
if !(-9999..=9999).contains(&self.yr) {
return Err(an_err!(
DtErrKind::OutOfRange,
"yr {} is outside Jiff's supported range (-9999..=9999)",
self.yr
));
}
let hr: i8 = self
.hr
.try_into()
.map_err(|_| an_err!(DtErrKind::InvalidNumber, "hr: {} u8 -> i8", self.hr))?;
let min: i8 = self
.min
.try_into()
.map_err(|_| an_err!(DtErrKind::InvalidNumber, "min: {} u8 -> i8", self.min))?;
let sec_for_jiff: i8 = if self.sec == 60 {
59
} else {
self.sec
.try_into()
.map_err(|_| an_err!(DtErrKind::InvalidNumber, "sec: {} u8 -> i8", self.sec))?
};
let mo: i8 = self
.mo
.try_into()
.map_err(|_| an_err!(DtErrKind::InvalidNumber, "mo: {} u8 -> i8", self.mo))?;
let day: i8 = self
.day
.try_into()
.map_err(|_| an_err!(DtErrKind::InvalidNumber, "day: {} u8 -> i8", self.day))?;
let civil_time = civil::date(self.yr as i16, mo, day).at(hr, min, sec_for_jiff, 0);
civil_time
.in_tz(tz)
.map_err(|e| an_err!(DtErrKind::InvalidTimezoneOffset, "{}", e))
}
fn from_jiff_zoned(&self, zoned: jiff::Zoned) -> Self {
let civil = zoned.datetime();
Self::reconstruct(
civil.year() as i64,
civil.month() as u8,
civil.day() as u8,
civil.hour() as u8,
civil.minute() as u8,
civil.second() as u8,
self.attos,
self.scale,
)
}
}