#[doc(no_inline)]
pub use calendrical_calculations::rata_die::RataDie;
use core::fmt;
use tinystr::TinyAsciiStr;
use zerovec::ule::AsULE;
#[cfg(feature = "unstable")]
pub use crate::duration::{DateDuration, DateDurationUnit};
use crate::error::MonthCodeParseError;
#[cfg(feature = "unstable")]
pub use unstable::DateFields;
#[cfg(not(feature = "unstable"))]
pub(crate) use unstable::DateFields;
mod unstable {
#[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_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,
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);
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 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)> {
ValidMonthCode::try_from_utf8(self.0.as_bytes())
.ok()
.map(ValidMonthCode::to_tuple)
}
pub fn new_normal(number: u8) -> Option<Self> {
(1..=99)
.contains(&number)
.then(|| ValidMonthCode::new_unchecked(number, false).to_month_code())
}
pub fn new_leap(number: u8) -> Option<Self> {
(1..=99)
.contains(&number)
.then(|| ValidMonthCode::new_unchecked(number, true).to_month_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)]
pub(crate) struct ValidMonthCode {
number: u8,
is_leap: bool,
}
impl ValidMonthCode {
#[inline]
pub(crate) fn try_from_utf8(bytes: &[u8]) -> Result<Self, MonthCodeParseError> {
match *bytes {
[b'M', tens, ones] => Ok(Self {
number: (tens - b'0') * 10 + ones - b'0',
is_leap: false,
}),
[b'M', tens, ones, b'L'] => Ok(Self {
number: (tens - b'0') * 10 + ones - b'0',
is_leap: true,
}),
_ => Err(MonthCodeParseError::InvalidSyntax),
}
}
#[inline]
pub(crate) const fn new_unchecked(number: u8, is_leap: bool) -> Self {
debug_assert!(1 <= number && number <= 99);
Self { number, is_leap }
}
#[inline]
pub fn number(self) -> u8 {
self.number
}
#[inline]
pub fn is_leap(self) -> bool {
self.is_leap
}
#[inline]
pub(crate) fn to_tuple(self) -> (u8, bool) {
(self.number, self.is_leap)
}
pub(crate) fn to_month_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(),
)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct MonthInfo {
pub ordinal: u8,
pub standard_code: MonthCode,
pub(crate) valid_standard_code: ValidMonthCode,
pub formatting_code: MonthCode,
pub(crate) valid_formatting_code: ValidMonthCode,
}
impl MonthInfo {
pub(crate) fn non_lunisolar(number: u8) -> Self {
Self::for_code_and_ordinal(ValidMonthCode::new_unchecked(number, false), number)
}
pub(crate) fn for_code_and_ordinal(code: ValidMonthCode, ordinal: u8) -> Self {
Self {
ordinal,
standard_code: code.to_month_code(),
valid_standard_code: code,
formatting_code: code.to_month_code(),
valid_formatting_code: code,
}
}
pub fn month_number(self) -> u8 {
self.valid_standard_code.number()
}
pub fn is_leap(self) -> bool {
self.valid_standard_code.is_leap()
}
#[doc(hidden)]
pub fn formatting_month_number(self) -> u8 {
self.valid_formatting_code.number()
}
#[doc(hidden)]
pub fn formatting_is_leap(self) -> bool {
self.valid_formatting_code.is_leap()
}
}
#[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,
}
}
}