#![cfg_attr(not(test), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod errors;
mod inner;
pub mod iter;
pub mod ncal;
use crate::errors::*;
use crate::iter::*;
use core::cmp::Ordering;
use core::fmt;
use core::ops::RangeInclusive;
use core::str::FromStr;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
pub type Jdnum = i32;
pub const REFORM1582_JDN: Jdnum = 2299161;
pub const UNIX_EPOCH_JDN: Jdnum = 2440588;
pub const RATA_DIE_ZERO_JDN: Jdnum = 1721425;
const SECONDS_IN_DAY: i64 = 24 * 60 * 60;
const COMMON_YEAR_LENGTH: Jdnum = 365;
const LEAP_YEAR_LENGTH: Jdnum = 366;
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum YearKind {
Common,
Leap,
ReformCommon,
ReformLeap,
Skipped,
}
impl YearKind {
pub const fn is_common(&self) -> bool {
use YearKind::*;
matches!(self, Common | ReformCommon)
}
pub const fn is_leap(&self) -> bool {
use YearKind::*;
matches!(self, Leap | ReformLeap)
}
pub const fn is_reform(&self) -> bool {
use YearKind::*;
matches!(self, ReformCommon | ReformLeap)
}
pub const fn is_skipped(&self) -> bool {
matches!(self, YearKind::Skipped)
}
}
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub struct Calendar(inner::Calendar);
impl Calendar {
pub const JULIAN: Calendar = Calendar(inner::Calendar::Julian);
pub const GREGORIAN: Calendar = Calendar(inner::Calendar::Gregorian);
pub const REFORM1582: Calendar = Calendar(inner::Calendar::Reforming {
reformation: 2299161,
gap: inner::ReformGap {
pre_reform: inner::Date {
year: 1582,
ordinal: 277,
month: Month::October,
day: 4,
},
post_reform: inner::Date {
year: 1582,
ordinal: 278,
month: Month::October,
day: 15,
},
kind: inner::GapKind::IntraMonth,
ordinal_gap_start: 287,
ordinal_gap: 10,
},
});
pub const fn reforming(reformation: Jdnum) -> Result<Calendar, ReformingError> {
let pre_reform = Calendar::JULIAN.at_jdn(match reformation.checked_sub(1) {
Some(jdn) => jdn,
None => return Err(ReformingError::InvalidReformation),
});
let post_reform = Calendar::GREGORIAN.at_jdn(reformation);
let mut ordinal = post_reform.ordinal();
if post_reform.year % 100 == 0
&& post_reform.year % 400 != 0
&& Month::February.lt(post_reform.month)
{
ordinal += 1;
}
match Calendar::JULIAN.get_jdn(post_reform.year(), ordinal) {
Ok(date) if date <= reformation => return Err(ReformingError::InvalidReformation),
Ok(_) => (),
Err(ArithmeticError) => return Err(ReformingError::Arithmetic),
};
let kind = inner::GapKind::for_dates(
pre_reform.year,
pre_reform.month,
post_reform.year,
post_reform.month,
);
let pre_reform = inner::Date {
year: pre_reform.year,
ordinal: pre_reform.ordinal,
month: pre_reform.month,
day: pre_reform.day,
};
let (post_ordinal, ordinal_gap_start, ordinal_gap) = match kind {
inner::GapKind::IntraMonth | inner::GapKind::CrossMonth => (
pre_reform.ordinal + 1,
post_reform.ordinal - 1,
post_reform.ordinal - pre_reform.ordinal - 1,
),
_ => (1, 0, post_reform.ordinal - 1),
};
let post_reform = inner::Date {
year: post_reform.year,
ordinal: post_ordinal,
month: post_reform.month,
day: post_reform.day,
};
Ok(Calendar(inner::Calendar::Reforming {
reformation,
gap: inner::ReformGap {
pre_reform,
post_reform,
kind,
ordinal_gap_start,
ordinal_gap,
},
}))
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn now(&self) -> Result<(Date, u32), ArithmeticError> {
self.at_system_time(SystemTime::now())
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn at_system_time(&self, t: SystemTime) -> Result<(Date, u32), ArithmeticError> {
let (jdn, secs) = system2jdn(t)?;
Ok((self.at_jdn(jdn), secs))
}
pub const fn at_unix_time(&self, unix_time: i64) -> Result<(Date, u32), ArithmeticError> {
match unix2jdn(unix_time) {
Ok((jdn, secs)) => Ok((self.at_jdn(jdn), secs)),
Err(e) => Err(e),
}
}
pub const fn at_ymd(&self, year: i32, month: Month, day: u32) -> Result<Date, DateError> {
let day_ordinal = match self.get_day_ordinal(year, month, day) {
Ok(d) => d,
Err(e) => return Err(e),
};
let ordinal = self.ymdo2ordinal(year, month, day_ordinal);
let jdn = match self.get_jdn(year, ordinal) {
Ok(jdn) => jdn,
Err(ArithmeticError) => return Err(DateError::Arithmetic),
};
Ok(Date {
calendar: *self,
year,
ordinal,
month,
day,
day_ordinal,
jdn,
})
}
pub const fn at_ordinal_date(&self, year: i32, ordinal: u32) -> Result<Date, DateError> {
let (month, day, day_ordinal) = match self.ordinal2ymddo(year, ordinal) {
Ok(mdo) => mdo,
Err(e) => return Err(e),
};
let jdn = match self.get_jdn(year, ordinal) {
Ok(jdn) => jdn,
Err(ArithmeticError) => return Err(DateError::Arithmetic),
};
Ok(Date {
calendar: *self,
year,
ordinal,
month,
day,
day_ordinal,
jdn,
})
}
pub const fn at_jdn(&self, jdn: Jdnum) -> Date {
use inner::Calendar::*;
let (year, mut ordinal) = if matches!(self.0, Julian)
|| matches!(self.0, Reforming { reformation, .. } if jdn < reformation)
{
inner::jdn2julian(jdn)
} else {
inner::jdn2gregorian(jdn)
};
if let Some(gap) = self.gap() {
if year == gap.post_reform.year && ordinal > gap.ordinal_gap_start {
ordinal -= gap.ordinal_gap;
}
}
let Ok((month, day, day_ordinal)) = self.ordinal2ymddo(year, ordinal) else {
unreachable!();
};
Date {
calendar: *self,
year,
ordinal,
month,
day,
day_ordinal,
jdn,
}
}
pub fn parse_date(&self, s: &str) -> Result<Date, ParseDateError> {
let mut parser = inner::DateParser::new(s);
let year = parser.parse_int()?;
parser.scan_char('-')?;
let diny = parser.parse_day_in_year()?;
if !parser.is_empty() {
return Err(ParseDateError::Trailing);
}
match diny {
inner::DayInYear::Ordinal(ordinal) => Ok(self.at_ordinal_date(year, ordinal)?),
inner::DayInYear::Date { month, day } => Ok(self.at_ymd(year, month, day)?),
}
}
pub const fn is_proleptic(&self) -> bool {
matches!(self.0, inner::Calendar::Julian | inner::Calendar::Gregorian)
}
pub const fn is_reforming(&self) -> bool {
matches!(self.0, inner::Calendar::Reforming { .. })
}
pub const fn reformation(&self) -> Option<Jdnum> {
if let inner::Calendar::Reforming { reformation, .. } = self.0 {
Some(reformation)
} else {
None
}
}
pub const fn last_julian_date(&self) -> Option<Date> {
if let inner::Calendar::Reforming { reformation, gap } = self.0 {
Some(Date {
calendar: *self,
year: gap.pre_reform.year,
ordinal: gap.pre_reform.ordinal,
month: gap.pre_reform.month,
day: gap.pre_reform.day,
day_ordinal: gap.pre_reform.day,
jdn: reformation - 1,
})
} else {
None
}
}
pub const fn first_gregorian_date(&self) -> Option<Date> {
if let inner::Calendar::Reforming { reformation, gap } = self.0 {
let day_ordinal = if matches!(gap.kind, inner::GapKind::IntraMonth) {
gap.pre_reform.day + 1
} else {
1
};
Some(Date {
calendar: *self,
year: gap.post_reform.year,
ordinal: gap.post_reform.ordinal,
month: gap.post_reform.month,
day: gap.post_reform.day,
day_ordinal,
jdn: reformation,
})
} else {
None
}
}
pub const fn year_kind(&self, year: i32) -> YearKind {
match self.0 {
inner::Calendar::Julian => {
if inner::is_julian_leap_year(year) {
YearKind::Leap
} else {
YearKind::Common
}
}
inner::Calendar::Gregorian => {
if inner::is_gregorian_leap_year(year) {
YearKind::Leap
} else {
YearKind::Common
}
}
inner::Calendar::Reforming { gap, .. } => {
use inner::RangeOrdering::*;
match gap.cmp_year(year) {
Less => {
if inner::is_julian_leap_year(year) {
YearKind::Leap
} else {
YearKind::Common
}
}
EqLower => {
if matches!(
(gap.pre_reform.month, gap.pre_reform.day),
(Month::December, 31)
) {
if inner::is_julian_leap_year(year) {
YearKind::Leap
} else {
YearKind::Common
}
} else if Month::February.lt(gap.pre_reform.month)
&& inner::is_julian_leap_year(year)
{
YearKind::ReformLeap
} else {
YearKind::ReformCommon
}
}
Between => YearKind::Skipped,
EqBoth => {
if (Month::February.lt(gap.pre_reform.month)
&& inner::is_julian_leap_year(year))
|| (gap.post_reform.month.le(Month::February)
&& inner::is_gregorian_leap_year(year))
{
YearKind::ReformLeap
} else {
YearKind::ReformCommon
}
}
EqUpper => {
if matches!(
(gap.post_reform.month, gap.post_reform.day),
(Month::January, 1)
) {
if inner::is_gregorian_leap_year(year) {
YearKind::Leap
} else {
YearKind::Common
}
} else if gap.post_reform.month.le(Month::February)
&& inner::is_gregorian_leap_year(year)
{
YearKind::ReformLeap
} else {
YearKind::ReformCommon
}
}
Greater => {
if inner::is_gregorian_leap_year(year) {
YearKind::Leap
} else {
YearKind::Common
}
}
}
}
}
}
pub const fn year_length(&self, year: i32) -> u32 {
match self.0 {
inner::Calendar::Julian | inner::Calendar::Gregorian => match self.year_kind(year) {
YearKind::Common => COMMON_YEAR_LENGTH as u32,
YearKind::Leap => LEAP_YEAR_LENGTH as u32,
_ => unreachable!(),
},
inner::Calendar::Reforming { gap, .. } => match self.year_kind(year) {
YearKind::Common => COMMON_YEAR_LENGTH as u32,
YearKind::Leap => LEAP_YEAR_LENGTH as u32,
k @ (YearKind::ReformCommon | YearKind::ReformLeap) => {
let length = if matches!(k, YearKind::ReformCommon) {
COMMON_YEAR_LENGTH as u32
} else {
LEAP_YEAR_LENGTH as u32
};
if year == gap.post_reform.year {
let correction = (year % 100 == 0 && year % 400 != 0 && k.is_leap()) as u32;
length - gap.ordinal_gap - correction
} else {
debug_assert!(year == gap.pre_reform.year);
gap.pre_reform.ordinal
}
}
YearKind::Skipped => 0,
},
}
}
pub const fn month_shape(&self, year: i32, month: Month) -> Option<MonthShape> {
use inner::RangeOrdering::*;
use Month::*;
let length = match month {
January => 31,
February => {
if self.year_kind(year).is_leap() {
29
} else if let Some(gap) = self.gap() {
if matches!(gap.cmp_year_month(year, February), EqLower) {
29
} else {
28
}
} else {
28
}
}
March => 31,
April => 30,
May => 31,
June => 30,
July => 31,
August => 31,
September => 30,
October => 31,
November => 30,
December => 31,
};
let inshape = if let Some(gap) = self.gap() {
match gap.cmp_year_month(year, month) {
EqLower | EqBoth => {
if matches!(gap.kind, inner::GapKind::IntraMonth) {
inner::MonthShape::Gapped {
gap_start: gap.pre_reform.day + 1,
gap_end: gap.post_reform.day - 1,
max_day: length,
}
} else if gap.pre_reform.day == length {
inner::MonthShape::Normal { max_day: length }
} else {
inner::MonthShape::Tailless {
max_day: gap.pre_reform.day,
natural_max_day: length,
}
}
}
Between => return None,
EqUpper if gap.post_reform.day > 1 => inner::MonthShape::Headless {
min_day: gap.post_reform.day,
max_day: length,
},
_ => inner::MonthShape::Normal { max_day: length },
}
} else {
inner::MonthShape::Normal { max_day: length }
};
Some(MonthShape {
calendar: *self,
year,
month,
inner: inshape,
})
}
#[allow(unused_assignments)]
const fn ordinal2ymddo(&self, year: i32, ordinal: u32) -> Result<(Month, u32, u32), DateError> {
use Month::*;
let max_ordinal = self.year_length(year);
if ordinal < 1 || ordinal > max_ordinal {
return Err(DateError::OrdinalOutOfRange {
year,
ordinal,
max_ordinal,
});
}
let mut days = ordinal;
macro_rules! for_month {
($($m:expr),*) => {
$(
if let Some(shape) = self.month_shape(year, $m) {
if let Some(day) = shape.nth_day(days) {
return Ok(($m, day, days));
}
days -= shape.len();
}
)*
}
}
for_month!(
January, February, March, April, May, June, July, August, September, October, November,
December
);
unreachable!()
}
#[allow(unused_assignments)]
const fn ymdo2ordinal(&self, year: i32, month: Month, day_ordinal: u32) -> u32 {
use Month::*;
let mut result = 0;
macro_rules! for_month {
($($m:expr),*) => {
$(
if $m.eq(month) {
return result + day_ordinal;
}
if let Some(ms) = self.month_shape(year, $m) {
result += ms.len();
}
)*
}
}
for_month!(
January, February, March, April, May, June, July, August, September, October, November,
December
);
unreachable!()
}
const fn get_day_ordinal(&self, year: i32, month: Month, day: u32) -> Result<u32, DateError> {
if let Some(shape) = self.month_shape(year, month) {
shape.day_ordinal_err(day)
} else {
Err(DateError::SkippedDate { year, month, day })
}
}
const fn get_jdn(&self, year: i32, mut ordinal: u32) -> Result<Jdnum, ArithmeticError> {
use inner::Calendar::*;
if let Some(gap) = self.gap() {
if year == gap.post_reform.year && ordinal >= gap.post_reform.ordinal {
ordinal += gap.ordinal_gap;
}
}
let r = if matches!(self.0, Julian)
|| matches!(self.0, Reforming {gap, ..} if year < gap.post_reform.year || (year == gap.post_reform.year && ordinal < gap.post_reform.ordinal))
{
inner::julian2jdn(year, ordinal)
} else {
inner::gregorian2jdn(year, ordinal)
};
match r {
Some(jdn) => Ok(jdn),
None => Err(ArithmeticError),
}
}
const fn gap(&self) -> Option<inner::ReformGap> {
match self.0 {
inner::Calendar::Reforming { gap, .. } => Some(gap),
_ => None,
}
}
const fn next_year_after(&self, year: i32) -> i32 {
if let Some(gap) = self.gap() {
if year == gap.pre_reform.year && gap.post_reform.year > gap.pre_reform.year {
return gap.post_reform.year;
}
}
year + 1
}
const fn prev_year_before(&self, year: i32) -> i32 {
if let Some(gap) = self.gap() {
if year == gap.post_reform.year && gap.post_reform.year > gap.pre_reform.year {
return gap.pre_reform.year;
}
}
year - 1
}
}
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
pub struct MonthShape {
calendar: Calendar,
year: i32,
month: Month,
inner: inner::MonthShape,
}
impl MonthShape {
pub const fn calendar(&self) -> Calendar {
self.calendar
}
pub const fn year(&self) -> i32 {
self.year
}
pub const fn month(&self) -> Month {
self.month
}
#[allow(clippy::len_without_is_empty)]
pub const fn len(&self) -> u32 {
use inner::MonthShape::*;
match self.inner {
Normal { max_day } => max_day,
Headless { min_day, max_day } => max_day - min_day + 1,
Tailless { max_day, .. } => max_day,
Gapped {
gap_start,
gap_end,
max_day,
} => max_day - (gap_end - gap_start + 1),
}
}
pub const fn contains(&self, day: u32) -> bool {
macro_rules! contains_day {
($lower:expr, $upper:expr) => {
$lower <= day && day <= $upper
};
}
use inner::MonthShape::*;
match self.inner {
Normal { max_day } | Tailless { max_day, .. } => contains_day!(1, max_day),
Headless { min_day, max_day } => contains_day!(min_day, max_day),
Gapped {
gap_start,
gap_end,
max_day,
} => contains_day!(1, max_day) && !contains_day!(gap_start, gap_end),
}
}
pub const fn first_day(&self) -> u32 {
use inner::MonthShape::*;
match self.inner {
Headless { min_day, .. } => min_day,
_ => 1,
}
}
pub const fn last_day(&self) -> u32 {
use inner::MonthShape::*;
match self.inner {
Normal { max_day } => max_day,
Headless { max_day, .. } => max_day,
Tailless { max_day, .. } => max_day,
Gapped { max_day, .. } => max_day,
}
}
pub const fn day_ordinal(&self, day: u32) -> Option<u32> {
match self.day_ordinal_err(day) {
Ok(ordinal) => Some(ordinal),
Err(_) => None,
}
}
const fn day_ordinal_err(&self, day: u32) -> Result<u32, DateError> {
use inner::MonthShape::*;
match self.inner {
Normal { max_day } if 1 <= day && day <= max_day => Ok(day),
Normal { max_day } => Err(DateError::DayOutOfRange {
year: self.year,
month: self.month,
day,
min_day: 1,
max_day,
}),
Headless { min_day, max_day } if min_day <= day && day <= max_day => {
Ok(day - min_day + 1)
}
Headless { min_day, .. } if 1 <= day && day < min_day => Err(DateError::SkippedDate {
year: self.year,
month: self.month,
day,
}),
Headless { min_day, max_day } => Err(DateError::DayOutOfRange {
year: self.year,
month: self.month,
day,
min_day,
max_day,
}),
Tailless { max_day, .. } if 1 <= day && day <= max_day => Ok(day),
Tailless {
max_day,
natural_max_day,
} if (max_day + 1) <= day && day <= natural_max_day => Err(DateError::SkippedDate {
year: self.year,
month: self.month,
day,
}),
Tailless { max_day, .. } => Err(DateError::DayOutOfRange {
year: self.year,
month: self.month,
day,
min_day: 1,
max_day,
}),
Gapped { max_day, .. } if day == 0 || day > max_day => Err(DateError::DayOutOfRange {
year: self.year,
month: self.month,
day,
min_day: 1,
max_day,
}),
Gapped { gap_start, .. } if day < gap_start => Ok(day),
Gapped { gap_end, .. } if day <= gap_end => Err(DateError::SkippedDate {
year: self.year,
month: self.month,
day,
}),
Gapped {
gap_start, gap_end, ..
} => Ok(day - (gap_end - gap_start + 1)),
}
}
#[allow(clippy::if_then_some_else_none)] pub const fn nth_day(&self, day_ordinal: u32) -> Option<u32> {
use inner::MonthShape::*;
match self.inner {
Normal { max_day } | Tailless { max_day, .. }
if 1 <= day_ordinal && day_ordinal <= max_day =>
{
Some(day_ordinal)
}
Headless { min_day, max_day }
if 1 <= day_ordinal && day_ordinal <= (max_day - min_day + 1) =>
{
Some(day_ordinal + min_day - 1)
}
Gapped { .. } if day_ordinal == 0 => None,
Gapped { gap_start, .. } if day_ordinal < gap_start => Some(day_ordinal),
Gapped {
gap_start,
gap_end,
max_day,
} => {
let day = day_ordinal + (gap_end - gap_start + 1);
if day <= max_day {
Some(day)
} else {
None
}
}
_ => None,
}
}
pub const fn nth_date(&self, day_ordinal: u32) -> Option<Date> {
let Some(day) = self.nth_day(day_ordinal) else {
return None;
};
let Ok(date) = self.calendar.at_ymd(self.year, self.month, day) else {
unreachable!();
};
Some(date)
}
#[allow(clippy::range_minus_one)]
pub const fn gap(&self) -> Option<RangeInclusive<u32>> {
use inner::MonthShape::*;
match self.inner {
Normal { .. } => None,
Headless { min_day, .. } => Some(1..=(min_day - 1)),
Tailless {
max_day,
natural_max_day,
} => Some((max_day + 1)..=natural_max_day),
Gapped {
gap_start, gap_end, ..
} => Some(gap_start..=gap_end),
}
}
pub const fn kind(&self) -> MonthKind {
use inner::MonthShape::*;
match self.inner {
Normal { .. } => MonthKind::Normal,
Headless { .. } => MonthKind::Headless,
Tailless { .. } => MonthKind::Tailless,
Gapped { .. } => MonthKind::Gapped,
}
}
pub const fn days(&self) -> Days {
Days::new(*self)
}
pub const fn dates(&self) -> Dates {
Dates::new(*self)
}
}
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum MonthKind {
Normal,
Headless,
Tailless,
Gapped,
}
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
pub struct Date {
calendar: Calendar,
year: i32,
ordinal: u32,
month: Month,
day: u32,
day_ordinal: u32,
jdn: Jdnum,
}
impl Date {
pub const fn calendar(&self) -> Calendar {
self.calendar
}
pub const fn year(&self) -> i32 {
self.year
}
pub const fn month(&self) -> Month {
self.month
}
pub const fn day(&self) -> u32 {
self.day
}
pub const fn day_ordinal(&self) -> u32 {
self.day_ordinal
}
pub const fn day_ordinal0(&self) -> u32 {
self.day_ordinal - 1
}
pub const fn ordinal(&self) -> u32 {
self.ordinal
}
pub const fn ordinal0(&self) -> u32 {
self.ordinal - 1
}
pub const fn julian_day_number(&self) -> Jdnum {
self.jdn
}
pub const fn weekday(&self) -> Weekday {
Weekday::for_jdn(self.jdn)
}
pub const fn is_julian(&self) -> bool {
match self.calendar.0 {
inner::Calendar::Julian => true,
inner::Calendar::Reforming { reformation, .. } => {
self.julian_day_number() < reformation
}
inner::Calendar::Gregorian => false,
}
}
pub const fn is_gregorian(&self) -> bool {
match self.calendar.0 {
inner::Calendar::Julian => false,
inner::Calendar::Reforming { reformation, .. } => {
reformation <= self.julian_day_number()
}
inner::Calendar::Gregorian => true,
}
}
pub const fn convert_to(&self, calendar: Calendar) -> Date {
calendar.at_jdn(self.julian_day_number())
}
pub const fn succ(&self) -> Option<Date> {
let Some(jdn) = self.jdn.checked_add(1) else {
return None;
};
let mut year = self.year;
let mut ordinal = self.ordinal + 1;
let (month, day, day_ordinal) = match self.calendar().ordinal2ymddo(year, ordinal) {
Ok(values) => values,
Err(DateError::OrdinalOutOfRange { .. }) => {
year = self.calendar().next_year_after(year);
ordinal = 1;
match self.calendar().ordinal2ymddo(year, ordinal) {
Ok(mdo) => mdo,
Err(_) => return None,
}
}
_ => return None,
};
Some(Date {
calendar: self.calendar,
year,
ordinal,
month,
day,
day_ordinal,
jdn,
})
}
pub const fn pred(&self) -> Option<Date> {
let Some(jdn) = self.jdn.checked_sub(1) else {
return None;
};
let mut year = self.year;
let ordinal = if self.ordinal > 1 {
self.ordinal - 1
} else {
year = self.calendar().prev_year_before(year);
self.calendar().year_length(year)
};
let Ok((month, day, day_ordinal)) = self.calendar().ordinal2ymddo(year, ordinal) else {
return None;
};
Some(Date {
calendar: self.calendar,
year,
ordinal,
month,
day,
day_ordinal,
jdn,
})
}
pub const fn later(&self) -> Later {
Later::new(*self)
}
pub const fn earlier(&self) -> Earlier {
Earlier::new(*self)
}
pub const fn and_later(&self) -> AndLater {
AndLater::new(*self)
}
pub const fn and_earlier(&self) -> AndEarlier {
AndEarlier::new(*self)
}
}
impl PartialOrd for Date {
fn partial_cmp(&self, other: &Date) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Date {
fn cmp(&self, other: &Date) -> Ordering {
(self.julian_day_number(), self.calendar())
.cmp(&(other.julian_day_number(), other.calendar()))
}
}
impl fmt::Display for Date {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04}-", self.year())?;
if f.alternate() {
write!(f, "{:03}", self.ordinal())?;
} else {
write!(f, "{:02}-{:02}", self.month().number(), self.day())?;
}
Ok(())
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl From<chrono::NaiveDate> for Date {
fn from(date: chrono::NaiveDate) -> Date {
use chrono::Datelike;
Calendar::GREGORIAN
.at_ymd(
date.year(),
Month::try_from(date.month())
.expect("chrono month value should be within range for Month"),
date.day(),
)
.expect("chrono date should be valid for proleptic Gregorian calendar")
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl TryFrom<Date> for chrono::naive::NaiveDate {
type Error = TryFromDateError;
fn try_from(mut date: Date) -> Result<chrono::naive::NaiveDate, TryFromDateError> {
if !date.is_gregorian() {
date = date.convert_to(Calendar::GREGORIAN);
}
chrono::naive::NaiveDate::from_ymd_opt(date.year(), date.month().number(), date.day())
.ok_or(TryFromDateError)
}
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl From<time::Date> for Date {
fn from(date: time::Date) -> Date {
Calendar::GREGORIAN
.at_ymd(date.year(), Month::from(date.month()), date.day().into())
.expect("time date should be valid for proleptic Gregorian calendar")
}
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl TryFrom<Date> for time::Date {
type Error = TryFromDateError;
fn try_from(mut date: Date) -> Result<time::Date, TryFromDateError> {
if !date.is_gregorian() {
date = date.convert_to(Calendar::GREGORIAN);
}
let Ok(day) = u8::try_from(date.day()) else {
unreachable!("day-of-month should fit in a u8");
};
time::Date::from_calendar_date(date.year(), date.month().into(), day)
.map_err(|_| TryFromDateError)
}
}
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum Month {
January = 1,
February = 2,
March = 3,
April = 4,
May = 5,
June = 6,
July = 7,
August = 8,
September = 9,
October = 10,
November = 11,
December = 12,
}
impl Month {
pub const fn name(&self) -> &'static str {
use Month::*;
match self {
January => "January",
February => "February",
March => "March",
April => "April",
May => "May",
June => "June",
July => "July",
August => "August",
September => "September",
October => "October",
November => "November",
December => "December",
}
}
pub const fn short_name(&self) -> &'static str {
use Month::*;
match self {
January => "Jan",
February => "Feb",
March => "Mar",
April => "Apr",
May => "May",
June => "Jun",
July => "Jul",
August => "Aug",
September => "Sep",
October => "Oct",
November => "Nov",
December => "Dec",
}
}
pub const fn number(&self) -> u32 {
*self as u32
}
pub const fn number0(&self) -> u32 {
self.number() - 1
}
pub const fn pred(&self) -> Option<Month> {
use Month::*;
match self {
January => None,
February => Some(January),
March => Some(February),
April => Some(March),
May => Some(April),
June => Some(May),
July => Some(June),
August => Some(July),
September => Some(August),
October => Some(September),
November => Some(October),
December => Some(November),
}
}
pub const fn succ(&self) -> Option<Month> {
use Month::*;
match self {
January => Some(February),
February => Some(March),
March => Some(April),
April => Some(May),
May => Some(June),
June => Some(July),
July => Some(August),
August => Some(September),
September => Some(October),
October => Some(November),
November => Some(December),
December => None,
}
}
const fn eq(&self, other: Month) -> bool {
(*self as u32) == (other as u32)
}
const fn lt(&self, other: Month) -> bool {
(*self as u32) < (other as u32)
}
const fn le(&self, other: Month) -> bool {
(*self as u32) <= (other as u32)
}
}
impl fmt::Display for Month {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.pad(self.short_name())
} else {
f.pad(self.name())
}
}
}
impl FromStr for Month {
type Err = ParseMonthError;
fn from_str(s: &str) -> Result<Month, ParseMonthError> {
if s.eq_ignore_ascii_case("january") || s.eq_ignore_ascii_case("jan") {
Ok(Month::January)
} else if s.eq_ignore_ascii_case("february") || s.eq_ignore_ascii_case("feb") {
Ok(Month::February)
} else if s.eq_ignore_ascii_case("march") || s.eq_ignore_ascii_case("mar") {
Ok(Month::March)
} else if s.eq_ignore_ascii_case("april") || s.eq_ignore_ascii_case("apr") {
Ok(Month::April)
} else if s.eq_ignore_ascii_case("may") {
Ok(Month::May)
} else if s.eq_ignore_ascii_case("june") || s.eq_ignore_ascii_case("jun") {
Ok(Month::June)
} else if s.eq_ignore_ascii_case("july") || s.eq_ignore_ascii_case("jul") {
Ok(Month::July)
} else if s.eq_ignore_ascii_case("august") || s.eq_ignore_ascii_case("aug") {
Ok(Month::August)
} else if s.eq_ignore_ascii_case("september") || s.eq_ignore_ascii_case("sep") {
Ok(Month::September)
} else if s.eq_ignore_ascii_case("october") || s.eq_ignore_ascii_case("oct") {
Ok(Month::October)
} else if s.eq_ignore_ascii_case("november") || s.eq_ignore_ascii_case("nov") {
Ok(Month::November)
} else if s.eq_ignore_ascii_case("december") || s.eq_ignore_ascii_case("dec") {
Ok(Month::December)
} else {
Err(ParseMonthError)
}
}
}
macro_rules! impl_month_try_from {
($($t:ty),* $(,)?) => {
$(
impl TryFrom<$t> for Month {
type Error = TryIntoMonthError;
fn try_from(value: $t) -> Result<Month, TryIntoMonthError> {
use Month::*;
match value {
1 => Ok(January),
2 => Ok(February),
3 => Ok(March),
4 => Ok(April),
5 => Ok(May),
6 => Ok(June),
7 => Ok(July),
8 => Ok(August),
9 => Ok(September),
10 => Ok(October),
11 => Ok(November),
12 => Ok(December),
_ => Err(TryIntoMonthError),
}
}
}
)*
}
}
impl_month_try_from!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl From<chrono::Month> for Month {
fn from(m: chrono::Month) -> Month {
match m {
chrono::Month::January => Month::January,
chrono::Month::February => Month::February,
chrono::Month::March => Month::March,
chrono::Month::April => Month::April,
chrono::Month::May => Month::May,
chrono::Month::June => Month::June,
chrono::Month::July => Month::July,
chrono::Month::August => Month::August,
chrono::Month::September => Month::September,
chrono::Month::October => Month::October,
chrono::Month::November => Month::November,
chrono::Month::December => Month::December,
}
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl From<Month> for chrono::Month {
fn from(m: Month) -> chrono::Month {
match m {
Month::January => chrono::Month::January,
Month::February => chrono::Month::February,
Month::March => chrono::Month::March,
Month::April => chrono::Month::April,
Month::May => chrono::Month::May,
Month::June => chrono::Month::June,
Month::July => chrono::Month::July,
Month::August => chrono::Month::August,
Month::September => chrono::Month::September,
Month::October => chrono::Month::October,
Month::November => chrono::Month::November,
Month::December => chrono::Month::December,
}
}
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl From<time::Month> for Month {
fn from(m: time::Month) -> Month {
match m {
time::Month::January => Month::January,
time::Month::February => Month::February,
time::Month::March => Month::March,
time::Month::April => Month::April,
time::Month::May => Month::May,
time::Month::June => Month::June,
time::Month::July => Month::July,
time::Month::August => Month::August,
time::Month::September => Month::September,
time::Month::October => Month::October,
time::Month::November => Month::November,
time::Month::December => Month::December,
}
}
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl From<Month> for time::Month {
fn from(m: Month) -> time::Month {
match m {
Month::January => time::Month::January,
Month::February => time::Month::February,
Month::March => time::Month::March,
Month::April => time::Month::April,
Month::May => time::Month::May,
Month::June => time::Month::June,
Month::July => time::Month::July,
Month::August => time::Month::August,
Month::September => time::Month::September,
Month::October => time::Month::October,
Month::November => time::Month::November,
Month::December => time::Month::December,
}
}
}
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub enum Weekday {
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
Sunday = 7,
}
impl Weekday {
pub const fn name(&self) -> &'static str {
use Weekday::*;
match self {
Monday => "Monday",
Tuesday => "Tuesday",
Wednesday => "Wednesday",
Thursday => "Thursday",
Friday => "Friday",
Saturday => "Saturday",
Sunday => "Sunday",
}
}
pub const fn short_name(&self) -> &'static str {
use Weekday::*;
match self {
Monday => "Mon",
Tuesday => "Tue",
Wednesday => "Wed",
Thursday => "Thu",
Friday => "Fri",
Saturday => "Sat",
Sunday => "Sun",
}
}
pub const fn number(&self) -> u32 {
*self as u32
}
pub const fn number0(&self) -> u32 {
self.number() - 1
}
pub const fn for_jdn(jdn: Jdnum) -> Weekday {
match Weekday::try_from_const(jdn.rem_euclid(7) + 1) {
Some(wd) => wd,
None => unreachable!(),
}
}
pub const fn pred(&self) -> Option<Weekday> {
use Weekday::*;
match self {
Monday => None,
Tuesday => Some(Monday),
Wednesday => Some(Tuesday),
Thursday => Some(Wednesday),
Friday => Some(Thursday),
Saturday => Some(Friday),
Sunday => Some(Saturday),
}
}
pub const fn succ(&self) -> Option<Weekday> {
use Weekday::*;
match self {
Monday => Some(Tuesday),
Tuesday => Some(Wednesday),
Wednesday => Some(Thursday),
Thursday => Some(Friday),
Friday => Some(Saturday),
Saturday => Some(Sunday),
Sunday => None,
}
}
const fn try_from_const(index: Jdnum) -> Option<Weekday> {
use Weekday::*;
match index {
1 => Some(Monday),
2 => Some(Tuesday),
3 => Some(Wednesday),
4 => Some(Thursday),
5 => Some(Friday),
6 => Some(Saturday),
7 => Some(Sunday),
_ => None,
}
}
}
impl fmt::Display for Weekday {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.pad(self.short_name())
} else {
f.pad(self.name())
}
}
}
impl FromStr for Weekday {
type Err = ParseWeekdayError;
fn from_str(s: &str) -> Result<Weekday, ParseWeekdayError> {
if s.eq_ignore_ascii_case("sunday") || s.eq_ignore_ascii_case("sun") {
Ok(Weekday::Sunday)
} else if s.eq_ignore_ascii_case("monday") || s.eq_ignore_ascii_case("mon") {
Ok(Weekday::Monday)
} else if s.eq_ignore_ascii_case("tuesday") || s.eq_ignore_ascii_case("tue") {
Ok(Weekday::Tuesday)
} else if s.eq_ignore_ascii_case("wednesday") || s.eq_ignore_ascii_case("wed") {
Ok(Weekday::Wednesday)
} else if s.eq_ignore_ascii_case("thursday") || s.eq_ignore_ascii_case("thu") {
Ok(Weekday::Thursday)
} else if s.eq_ignore_ascii_case("friday") || s.eq_ignore_ascii_case("fri") {
Ok(Weekday::Friday)
} else if s.eq_ignore_ascii_case("saturday") || s.eq_ignore_ascii_case("sat") {
Ok(Weekday::Saturday)
} else {
Err(ParseWeekdayError)
}
}
}
macro_rules! impl_weekday_try_from {
($($t:ty),* $(,)?) => {
$(
impl TryFrom<$t> for Weekday {
type Error = TryIntoWeekdayError;
fn try_from(value: $t) -> Result<Weekday, TryIntoWeekdayError> {
Jdnum::try_from(value).ok().and_then(Weekday::try_from_const).ok_or(TryIntoWeekdayError)
}
}
)*
}
}
impl_weekday_try_from!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl From<chrono::Weekday> for Weekday {
fn from(wd: chrono::Weekday) -> Weekday {
match wd {
chrono::Weekday::Sun => Weekday::Sunday,
chrono::Weekday::Mon => Weekday::Monday,
chrono::Weekday::Tue => Weekday::Tuesday,
chrono::Weekday::Wed => Weekday::Wednesday,
chrono::Weekday::Thu => Weekday::Thursday,
chrono::Weekday::Fri => Weekday::Friday,
chrono::Weekday::Sat => Weekday::Saturday,
}
}
}
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl From<Weekday> for chrono::Weekday {
fn from(wd: Weekday) -> chrono::Weekday {
match wd {
Weekday::Sunday => chrono::Weekday::Sun,
Weekday::Monday => chrono::Weekday::Mon,
Weekday::Tuesday => chrono::Weekday::Tue,
Weekday::Wednesday => chrono::Weekday::Wed,
Weekday::Thursday => chrono::Weekday::Thu,
Weekday::Friday => chrono::Weekday::Fri,
Weekday::Saturday => chrono::Weekday::Sat,
}
}
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl From<time::Weekday> for Weekday {
fn from(wd: time::Weekday) -> Weekday {
match wd {
time::Weekday::Sunday => Weekday::Sunday,
time::Weekday::Monday => Weekday::Monday,
time::Weekday::Tuesday => Weekday::Tuesday,
time::Weekday::Wednesday => Weekday::Wednesday,
time::Weekday::Thursday => Weekday::Thursday,
time::Weekday::Friday => Weekday::Friday,
time::Weekday::Saturday => Weekday::Saturday,
}
}
}
#[cfg(feature = "time")]
#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
impl From<Weekday> for time::Weekday {
fn from(wd: Weekday) -> time::Weekday {
match wd {
Weekday::Sunday => time::Weekday::Sunday,
Weekday::Monday => time::Weekday::Monday,
Weekday::Tuesday => time::Weekday::Tuesday,
Weekday::Wednesday => time::Weekday::Wednesday,
Weekday::Thursday => time::Weekday::Thursday,
Weekday::Friday => time::Weekday::Friday,
Weekday::Saturday => time::Weekday::Saturday,
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn system2jdn(t: SystemTime) -> Result<(Jdnum, u32), ArithmeticError> {
let ts = match t.duration_since(UNIX_EPOCH) {
Ok(d) => i64::try_from(d.as_secs()),
Err(e) => i64::try_from(e.duration().as_secs()).map(|i| -i),
}
.map_err(|_| ArithmeticError)?;
unix2jdn(ts)
}
#[allow(clippy::cast_possible_truncation)]
pub const fn unix2jdn(unix_time: i64) -> Result<(Jdnum, u32), ArithmeticError> {
let jd = unix_time.div_euclid(SECONDS_IN_DAY) + (UNIX_EPOCH_JDN as i64);
if Jdnum::MIN as i64 <= jd && jd <= Jdnum::MAX as i64 {
let jd = jd as Jdnum;
let secs = unix_time.rem_euclid(SECONDS_IN_DAY) as u32;
Ok((jd, secs))
} else {
Err(ArithmeticError)
}
}
pub const fn jdn2unix(jdn: Jdnum) -> i64 {
((jdn as i64) - (UNIX_EPOCH_JDN as i64)) * SECONDS_IN_DAY
}
#[cfg(test)]
mod tests {
mod at_ordinal_date;
mod at_ymd;
mod autogen;
mod calendar;
mod chrono;
mod date;
mod jdn;
mod month;
mod parse_date;
mod reformations;
mod time_crate;
mod unix;
mod weekday;
mod year_kind;
}