use crate::time::{UTCDay, UTCTimestamp, UTCTransformations};
use crate::util::StrWriter;
use core::error::Error;
use core::fmt::{Display, Formatter, Write};
use core::num::ParseIntError;
use core::time::Duration;
#[cfg(feature = "alloc")]
use alloc::{format, string::String};
#[cfg_attr(not(feature = "std"), doc = "```rust,ignore")]
#[cfg_attr(feature = "std", doc = "```rust")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UTCDate {
era: u32,
yoe: u16,
month: u8,
day: u8,
}
impl Default for UTCDate {
fn default() -> Self {
Self::MIN
}
}
impl Display for UTCDate {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let (year, month, day) = self.as_components();
write!(f, "{:04}-{:02}-{:02}", year, month, day)
}
}
impl UTCDate {
pub const MIN: Self = Self {
era: 4,
yoe: 369,
month: 1,
day: 1,
};
pub const MAX: Self = Self {
era: 1_461_385_128,
yoe: 23,
month: 11,
day: 9,
};
pub const MAX_YEAR: u64 = 584_554_051_223;
pub const MIN_YEAR: u64 = 1970;
pub const ISO_DATE_LEN: usize = 10;
#[inline]
pub const unsafe fn from_components_unchecked(year: u64, month: u8, day: u8) -> Self {
let year = year - (month <= 2) as u64;
let era = year / 400;
let yoe = year - (era * 400);
Self {
era: era as u32,
yoe: yoe as u16,
month,
day,
}
}
pub fn try_from_components(year: u64, month: u8, day: u8) -> Result<Self, UTCDateError> {
if !(Self::MIN_YEAR..=Self::MAX_YEAR).contains(&year) {
return Err(UTCDateError::YearOutOfRange(year));
}
if month == 0 || month > 12 {
return Err(UTCDateError::MonthOutOfRange(month));
}
let date = unsafe { Self::from_components_unchecked(year, month, day) };
if date.day == 0 || date.day > date.days_in_month() {
return Err(UTCDateError::DayOutOfRange(date));
}
if date > UTCDate::MAX {
return Err(UTCDateError::DateOutOfRange(date));
}
Ok(date)
}
pub const fn from_day(utc_day: UTCDay) -> Self {
let z: u64 = utc_day.as_u64() + 719468;
let era: u32 = (z / 146097) as u32;
let doe = (z - (era as u64 * 146097)) as u32;
let yoe = (doe - (doe / 1460) + (doe / 36524) - (doe / 146096)) / 365;
let doy = doe - (365 * yoe) - (yoe / 4) + (yoe / 100);
let mp = ((5 * doy) + 2) / 153;
let day = (doy - (((153 * mp) + 2) / 5) + 1) as u8;
let month = if mp < 10 { mp + 3 } else { mp - 9 } as u8;
Self {
era,
yoe: yoe as u16,
month,
day,
}
}
pub const fn as_day(&self) -> UTCDay {
let m = self.month as u16;
let d = self.day as u16;
let era = self.era;
let yoe = self.yoe as u32;
let doy = ((153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5) + d - 1;
let doe = (yoe * 365) + (yoe / 4) - (yoe / 100) + doy as u32;
let days = (era as u64 * 146097) + doe as u64 - 719468;
unsafe { UTCDay::from_u64_unchecked(days) }
}
#[inline]
pub const fn as_components(&self) -> (u64, u8, u8) {
let year = self.yoe as u64 + (self.era as u64 * 400) + (self.month <= 2) as u64;
(year, self.month, self.day)
}
#[inline]
pub const fn to_components(self) -> (u64, u8, u8) {
let year = self.yoe as u64 + (self.era as u64 * 400) + (self.month <= 2) as u64;
(year, self.month, self.day)
}
#[inline]
pub const fn is_leap_year(&self) -> bool {
let yoe_adj = self.yoe + (self.month <= 2) as u16;
(yoe_adj % 4 == 0) && ((yoe_adj % 100 != 0) || (yoe_adj % 400 == 0))
}
pub fn days_in_month(&self) -> u8 {
match self.month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
_ => {
if self.is_leap_year() {
29
} else {
28
}
}
}
}
pub fn try_from_iso_date(iso: &str) -> Result<Self, UTCDateError> {
let len = iso.len();
if len != Self::ISO_DATE_LEN {
return Err(UTCDateError::InvalidStrLen(len));
}
let (year_str, rem) = iso.split_at(4); let (month_str, rem) = rem[1..].split_at(2); let day_str = &rem[1..];
let year: u64 = year_str.parse()?;
let month: u8 = month_str.parse()?;
let day: u8 = day_str.parse()?;
Self::try_from_components(year, month, day)
}
#[cfg(feature = "alloc")]
pub fn as_iso_date(&self) -> String {
format!("{self}")
}
#[inline]
pub(crate) fn _write_iso_date_trunc(&self, w: &mut StrWriter) {
write!(w, "{self}").unwrap();
}
pub fn write_iso_date(&self, buf: &mut [u8]) -> Result<usize, UTCDateError> {
let write_len = Self::ISO_DATE_LEN;
if write_len > buf.len() {
return Err(UTCDateError::InvalidStrLen(buf.len()));
}
let mut writer = StrWriter::new(&mut buf[..write_len]);
self._write_iso_date_trunc(&mut writer);
Ok(writer.written)
}
}
impl UTCTransformations for UTCDate {
fn from_secs(secs: u64) -> Self {
let utc_day = UTCDay::from_secs(secs);
Self::from_day(utc_day)
}
fn as_secs(&self) -> u64 {
self.as_day().as_secs()
}
fn from_millis(millis: u64) -> Self {
let utc_day = UTCDay::from_millis(millis);
Self::from_day(utc_day)
}
fn as_millis(&self) -> u128 {
self.as_day().as_millis()
}
fn from_micros(micros: u64) -> Self {
let utc_day = UTCDay::from_micros(micros);
Self::from_day(utc_day)
}
fn as_micros(&self) -> u128 {
self.as_day().as_micros()
}
fn from_nanos(nanos: u64) -> Self {
let utc_day = UTCDay::from_nanos(nanos);
Self::from_day(utc_day)
}
fn as_nanos(&self) -> u128 {
self.as_day().as_nanos()
}
fn from_timestamp(timestamp: UTCTimestamp) -> Self {
let utc_day = UTCDay::from_timestamp(timestamp);
Self::from_day(utc_day)
}
fn as_timestamp(&self) -> UTCTimestamp {
self.as_day().as_timestamp()
}
}
impl From<Duration> for UTCDate {
fn from(duration: Duration) -> Self {
Self::from_duration(duration)
}
}
impl From<UTCTimestamp> for UTCDate {
fn from(timestamp: UTCTimestamp) -> Self {
Self::from_timestamp(timestamp)
}
}
impl From<UTCDay> for UTCDate {
fn from(utc_day: UTCDay) -> Self {
Self::from_day(utc_day)
}
}
#[derive(Debug, Clone)]
pub enum UTCDateError {
ParseErr(ParseIntError),
YearOutOfRange(u64),
MonthOutOfRange(u8),
DayOutOfRange(UTCDate),
DateOutOfRange(UTCDate),
InvalidStrLen(usize),
}
impl Display for UTCDateError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Self::ParseErr(e) => e.fmt(f),
Self::YearOutOfRange(y) => write!(f, "year ({y}) out of range!"),
Self::MonthOutOfRange(m) => write!(f, "month ({m}) out of range!"),
Self::DayOutOfRange(d) => write!(f, "day ({d}) out of range!"),
Self::DateOutOfRange(date) => write!(f, "date ({date}) out of range!"),
Self::InvalidStrLen(l) => write!(f, "invalid ISO date str length ({l}), 10 required"),
}
}
}
impl Error for UTCDateError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::ParseErr(e) => e.source(),
_ => None,
}
}
}
impl From<ParseIntError> for UTCDateError {
fn from(value: ParseIntError) -> Self {
Self::ParseErr(value)
}
}