#[doc(no_inline)]
pub use calendrical_calculations::rata_die::RataDie;
use core::fmt;
use tinystr::TinyAsciiStr;
use zerovec::ule::AsULE;
pub use crate::duration::DateDuration;
use crate::{calendar_arithmetic::ArithmeticDate, error::MonthCodeParseError};
#[cfg(doc)]
use crate::Date;
#[derive(Copy, Clone, PartialEq, Default)]
#[non_exhaustive]
pub struct DateFields<'a> {
pub era: Option<&'a [u8]>,
pub era_year: Option<i32>,
pub extended_year: Option<i32>,
pub month: Option<Month>,
pub month_code: Option<&'a [u8]>,
pub ordinal_month: Option<u8>,
pub day: Option<u8>,
}
impl fmt::Debug for DateFields<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self {
era,
era_year,
extended_year,
month_code,
month,
ordinal_month,
day,
} = *self;
let mut builder = f.debug_struct("DateFields");
if let Some(s) = era.and_then(|s| core::str::from_utf8(s).ok()) {
builder.field("era", &Some(s));
} else {
builder.field("era", &era);
}
builder.field("era_year", &era_year);
builder.field("extended_year", &extended_year);
builder.field("month", &month);
if let Some(s) = month_code.and_then(|s| core::str::from_utf8(s).ok()) {
builder.field("month_code", &Some(s));
} else {
builder.field("month_code", &month_code);
}
builder.field("ordinal_month", &ordinal_month);
builder.field("day", &day);
builder.finish()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum YearInput<'a> {
Extended(i32),
EraYear(&'a str, i32),
}
impl From<i32> for YearInput<'_> {
fn from(year: i32) -> Self {
Self::Extended(year)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum YearInfo {
Era(EraYear),
Cyclic(CyclicYear),
}
impl From<EraYear> for YearInfo {
fn from(value: EraYear) -> Self {
Self::Era(value)
}
}
impl From<CyclicYear> for YearInfo {
fn from(value: CyclicYear) -> Self {
Self::Cyclic(value)
}
}
impl YearInfo {
pub fn era_year_or_related_iso(self) -> i32 {
match self {
YearInfo::Era(e) => e.year,
YearInfo::Cyclic(c) => c.related_iso,
}
}
pub fn extended_year(self) -> i32 {
match self {
YearInfo::Era(e) => e.extended_year,
YearInfo::Cyclic(c) => c.related_iso,
}
}
pub fn era(self) -> Option<EraYear> {
match self {
Self::Era(e) => Some(e),
Self::Cyclic(_) => None,
}
}
pub fn cyclic(self) -> Option<CyclicYear> {
match self {
Self::Era(_) => None,
Self::Cyclic(c) => Some(c),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[allow(clippy::exhaustive_enums)] pub enum YearAmbiguity {
Unambiguous,
CenturyRequired,
EraRequired,
EraAndCenturyRequired,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct EraYear {
pub year: i32,
pub extended_year: i32,
pub era: TinyAsciiStr<16>,
pub era_index: Option<u8>,
pub ambiguity: YearAmbiguity,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct CyclicYear {
pub year: u8,
pub related_iso: i32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[allow(clippy::exhaustive_structs)] #[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
#[cfg_attr(feature = "datagen", databake(path = icu_calendar::types))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct MonthCode(pub TinyAsciiStr<4>);
impl MonthCode {
#[deprecated(since = "2.1.0")]
pub fn get_normal_if_leap(self) -> Option<MonthCode> {
let bytes = self.0.all_bytes();
if bytes[3] == b'L' {
Some(MonthCode(TinyAsciiStr::try_from_utf8(&bytes[0..3]).ok()?))
} else {
None
}
}
#[deprecated(since = "2.1.0")]
pub fn parsed(self) -> Option<(u8, bool)> {
Month::try_from_utf8(self.0.as_bytes())
.ok()
.map(|m| (m.number(), m.is_leap()))
}
#[deprecated(since = "2.2.0", note = "use `Month::new(m).code()`")]
pub fn new_normal(number: u8) -> Option<Self> {
(1..=99)
.contains(&number)
.then(|| Month::new_unchecked(number, false).code())
}
#[deprecated(since = "2.2.0", note = "use `Month::leap(m).code()`")]
pub fn new_leap(number: u8) -> Option<Self> {
(1..=99)
.contains(&number)
.then(|| Month::new_unchecked(number, true).code())
}
}
#[test]
fn test_get_normal_month_code_if_leap() {
#![allow(deprecated)]
assert_eq!(
MonthCode::new_leap(1).unwrap().get_normal_if_leap(),
MonthCode::new_normal(1)
);
assert_eq!(
MonthCode::new_leap(11).unwrap().get_normal_if_leap(),
MonthCode::new_normal(11)
);
assert_eq!(
MonthCode::new_normal(10).unwrap().get_normal_if_leap(),
None
);
}
impl AsULE for MonthCode {
type ULE = TinyAsciiStr<4>;
fn to_unaligned(self) -> TinyAsciiStr<4> {
self.0
}
fn from_unaligned(u: TinyAsciiStr<4>) -> Self {
Self(u)
}
}
#[cfg(feature = "alloc")]
impl<'a> zerovec::maps::ZeroMapKV<'a> for MonthCode {
type Container = zerovec::ZeroVec<'a, MonthCode>;
type Slice = zerovec::ZeroSlice<MonthCode>;
type GetType = <MonthCode as AsULE>::ULE;
type OwnedType = MonthCode;
}
impl fmt::Display for MonthCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Hash, Eq, PartialOrd)]
pub struct Month {
number: u8,
is_leap: bool,
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Hash, Eq)]
pub enum LeapStatus {
Normal,
Leap,
Base,
}
impl Month {
pub const fn new(number: u8) -> Self {
Self {
number: if number > 99 { 99 } else { number },
is_leap: false,
}
}
pub const fn leap(number: u8) -> Self {
Self {
number: if number > 99 { 99 } else { number },
is_leap: true,
}
}
pub fn try_from_str(s: &str) -> Result<Self, MonthCodeParseError> {
Self::try_from_utf8(s.as_bytes())
}
pub fn try_from_utf8(bytes: &[u8]) -> Result<Self, MonthCodeParseError> {
match *bytes {
[b'M', tens @ b'0'..=b'9', ones @ b'0'..=b'9'] => Ok(Self {
number: (tens - b'0') * 10 + ones - b'0',
is_leap: false,
}),
[b'M', tens @ b'0'..=b'9', ones @ b'0'..=b'9', b'L'] => Ok(Self {
number: (tens - b'0') * 10 + ones - b'0',
is_leap: true,
}),
_ => Err(MonthCodeParseError::InvalidSyntax),
}
}
pub(crate) const fn new_unchecked(number: u8, is_leap: bool) -> Self {
debug_assert!(1 <= number && number <= 99);
Self { number, is_leap }
}
pub fn number(self) -> u8 {
self.number
}
pub fn is_leap(self) -> bool {
self.is_leap
}
pub fn code(self) -> MonthCode {
#[allow(clippy::unwrap_used)] MonthCode(
TinyAsciiStr::try_from_raw([
b'M',
b'0' + self.number / 10,
b'0' + self.number % 10,
if self.is_leap { b'L' } else { 0 },
])
.unwrap(),
)
}
}
impl From<u8> for Month {
#[inline]
fn from(number: u8) -> Self {
Self::new(number)
}
}
#[test]
fn test_month_cmp() {
let months_in_order = [
Month::new(1),
Month::new(2),
Month::leap(2),
Month::new(3),
Month::new(10),
Month::leap(10),
];
for i in 0..months_in_order.len() - 1 {
for j in i + 1..months_in_order.len() {
let a = months_in_order[i];
let b = months_in_order[j];
assert!(a == a);
assert!(a < b);
assert!(b > a);
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct MonthInfo {
pub ordinal: u8,
number: u8,
pub(crate) leap_status: LeapStatus,
#[deprecated(since = "2.2.0", note = "use `to_input().code()`")]
pub standard_code: MonthCode,
#[deprecated(since = "2.2.0")]
pub formatting_code: MonthCode,
}
impl MonthInfo {
pub(crate) fn new<C: crate::calendar_arithmetic::DateFieldsResolver>(
c: &C,
date: ArithmeticDate<C>,
) -> Self {
let ordinal = date.month();
let value = c.month_from_ordinal(date.year(), ordinal);
#[allow(deprecated)] Self {
ordinal,
number: value.number,
leap_status: if value.is_leap {
LeapStatus::Leap
} else {
LeapStatus::Normal
},
#[allow(deprecated)]
standard_code: value.code(),
#[allow(deprecated)]
formatting_code: value.code(),
}
}
pub fn number(self) -> u8 {
self.number
}
pub fn leap_status(self) -> LeapStatus {
self.leap_status
}
pub fn to_input(&self) -> Month {
Month::new_unchecked(self.number, self.leap_status == LeapStatus::Leap)
}
#[deprecated(since = "2.2.0", note = "use `.to_input().is_leap()`")]
pub fn is_leap(self) -> bool {
self.to_input().is_leap()
}
#[deprecated(since = "2.2.0", note = "use `number`")]
pub fn month_number(self) -> u8 {
self.number
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[allow(clippy::exhaustive_structs)] pub struct DayOfYear(pub u16);
#[allow(clippy::exhaustive_structs)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct DayOfMonth(pub u8);
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(clippy::exhaustive_structs)] pub struct IsoWeekOfYear {
pub week_number: u8,
pub iso_year: i32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(clippy::exhaustive_structs)] pub struct DayOfWeekInMonth(pub u8);
impl From<DayOfMonth> for DayOfWeekInMonth {
fn from(day_of_month: DayOfMonth) -> Self {
DayOfWeekInMonth(1 + ((day_of_month.0 - 1) / 7))
}
}
#[test]
fn test_day_of_week_in_month() {
assert_eq!(DayOfWeekInMonth::from(DayOfMonth(1)).0, 1);
assert_eq!(DayOfWeekInMonth::from(DayOfMonth(7)).0, 1);
assert_eq!(DayOfWeekInMonth::from(DayOfMonth(8)).0, 2);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(missing_docs)] #[repr(i8)]
#[cfg_attr(feature = "datagen", derive(serde::Serialize, databake::Bake))]
#[cfg_attr(feature = "datagen", databake(path = icu_calendar::types))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
#[allow(clippy::exhaustive_enums)] pub enum Weekday {
Monday = 1,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
const SUNDAY: RataDie = RataDie::new(0);
impl From<RataDie> for Weekday {
fn from(value: RataDie) -> Self {
use Weekday::*;
match (value - SUNDAY).rem_euclid(7) {
0 => Sunday,
1 => Monday,
2 => Tuesday,
3 => Wednesday,
4 => Thursday,
5 => Friday,
6 => Saturday,
_ => unreachable!(),
}
}
}
impl Weekday {
pub fn from_days_since_sunday(input: isize) -> Self {
(SUNDAY + input as i64).into()
}
pub(crate) fn next_day(self) -> Weekday {
use Weekday::*;
match self {
Monday => Tuesday,
Tuesday => Wednesday,
Wednesday => Thursday,
Thursday => Friday,
Friday => Saturday,
Saturday => Sunday,
Sunday => Monday,
}
}
}