1use crate::duration::{DateDuration, DateDurationUnit};
6use crate::error::{
7 range_check, range_check_with_overflow, DateFromFieldsError, EcmaReferenceYearError,
8 MonthCodeError, MonthCodeParseError, UnknownEraError,
9};
10use crate::options::{DateAddOptions, DateDifferenceOptions};
11use crate::options::{DateFromFieldsOptions, MissingFieldsStrategy, Overflow};
12use crate::types::{DateFields, ValidMonthCode};
13use crate::{types, Calendar, DateError, RangeError};
14use core::cmp::Ordering;
15use core::fmt::Debug;
16use core::hash::{Hash, Hasher};
17use core::ops::RangeInclusive;
18
19const VALID_YEAR_RANGE: RangeInclusive<i32> = (i32::MIN / 16)..=-(i32::MIN / 16);
26
27#[derive(Debug)]
28pub(crate) struct ArithmeticDate<C: DateFieldsResolver> {
29 pub year: C::YearInfo,
30 pub month: u8,
32 pub day: u8,
34}
35
36impl<C: DateFieldsResolver> Copy for ArithmeticDate<C> {}
39impl<C: DateFieldsResolver> Clone for ArithmeticDate<C> {
40 fn clone(&self) -> Self {
41 *self
42 }
43}
44
45impl<C: DateFieldsResolver> PartialEq for ArithmeticDate<C> {
46 fn eq(&self, other: &Self) -> bool {
47 self.year.to_extended_year() == other.year.to_extended_year()
48 && self.month == other.month
49 && self.day == other.day
50 }
51}
52
53impl<C: DateFieldsResolver> Eq for ArithmeticDate<C> {}
54
55impl<C: DateFieldsResolver> Ord for ArithmeticDate<C> {
56 fn cmp(&self, other: &Self) -> Ordering {
57 self.year
58 .to_extended_year()
59 .cmp(&other.year.to_extended_year())
60 .then(self.month.cmp(&other.month))
61 .then(self.day.cmp(&other.day))
62 }
63}
64
65impl<C: DateFieldsResolver> PartialOrd for ArithmeticDate<C> {
66 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
67 Some(self.cmp(other))
68 }
69}
70
71impl<C: DateFieldsResolver> Hash for ArithmeticDate<C> {
72 fn hash<H>(&self, state: &mut H)
73 where
74 H: Hasher,
75 {
76 self.year.to_extended_year().hash(state);
77 self.month.hash(state);
78 self.day.hash(state);
79 }
80}
81
82#[allow(dead_code)] pub(crate) const MAX_ITERS_FOR_DAYS_OF_MONTH: u8 = 33;
85
86pub(crate) trait ToExtendedYear {
87 fn to_extended_year(&self) -> i32;
88}
89
90impl ToExtendedYear for i32 {
91 fn to_extended_year(&self) -> i32 {
92 *self
93 }
94}
95
96pub(crate) trait DateFieldsResolver: Calendar {
98 type YearInfo: Copy + Debug + PartialEq + ToExtendedYear;
101
102 fn days_in_provided_month(year: Self::YearInfo, month: u8) -> u8;
103
104 fn months_in_provided_year(year: Self::YearInfo) -> u8;
105
106 fn year_info_from_era(
109 &self,
110 era: &[u8],
111 era_year: i32,
112 ) -> Result<Self::YearInfo, UnknownEraError>;
113
114 fn year_info_from_extended(&self, extended_year: i32) -> Self::YearInfo;
116
117 fn reference_year_from_month_day(
124 &self,
125 month_code: ValidMonthCode,
126 day: u8,
127 ) -> Result<Self::YearInfo, EcmaReferenceYearError>;
128
129 #[inline]
133 fn ordinal_month_from_code(
134 &self,
135 _year: &Self::YearInfo,
136 month_code: ValidMonthCode,
137 _options: DateFromFieldsOptions,
138 ) -> Result<u8, MonthCodeError> {
139 match month_code.to_tuple() {
140 (month_number @ 1..=12, false) => Ok(month_number),
141 _ => Err(MonthCodeError::NotInCalendar),
142 }
143 }
144
145 #[inline]
151 fn month_code_from_ordinal(&self, _year: &Self::YearInfo, ordinal_month: u8) -> ValidMonthCode {
152 ValidMonthCode::new_unchecked(ordinal_month, false)
153 }
154}
155
156impl<C: DateFieldsResolver> ArithmeticDate<C> {
157 #[inline]
158 pub(crate) const fn new_unchecked(year: C::YearInfo, month: u8, day: u8) -> Self {
159 ArithmeticDate { year, month, day }
160 }
161
162 pub(crate) const fn cast<C2: DateFieldsResolver<YearInfo = C::YearInfo>>(
163 self,
164 ) -> ArithmeticDate<C2> {
165 ArithmeticDate {
166 year: self.year,
167 month: self.month,
168 day: self.day,
169 }
170 }
171
172 pub(crate) fn from_codes(
173 era: Option<&str>,
174 year: i32,
175 month_code: types::MonthCode,
176 day: u8,
177 calendar: &C,
178 ) -> Result<Self, DateError> {
179 let year = range_check(year, "year", VALID_YEAR_RANGE)?;
180 let year = if let Some(era) = era {
181 calendar.year_info_from_era(era.as_bytes(), year)?
182 } else {
183 calendar.year_info_from_extended(year)
184 };
185 let validated =
186 ValidMonthCode::try_from_utf8(month_code.0.as_bytes()).map_err(|e| match e {
187 MonthCodeParseError::InvalidSyntax => DateError::UnknownMonthCode(month_code),
188 })?;
189 let month = calendar
190 .ordinal_month_from_code(&year, validated, Default::default())
191 .map_err(|e| match e {
192 MonthCodeError::NotInCalendar | MonthCodeError::NotInYear => {
193 DateError::UnknownMonthCode(month_code)
194 }
195 })?;
196
197 let day = range_check(day, "day", 1..=C::days_in_provided_month(year, month))?;
198
199 Ok(ArithmeticDate::new_unchecked(year, month, day))
200 }
201
202 pub(crate) fn from_fields(
203 fields: DateFields,
204 options: DateFromFieldsOptions,
205 calendar: &C,
206 ) -> Result<Self, DateFromFieldsError> {
207 let missing_fields_strategy = options.missing_fields_strategy.unwrap_or_default();
208
209 let day = match fields.day {
210 Some(day) => day,
211 None => match missing_fields_strategy {
212 MissingFieldsStrategy::Reject => return Err(DateFromFieldsError::NotEnoughFields),
213 MissingFieldsStrategy::Ecma => {
214 if fields.extended_year.is_some() || fields.era_year.is_some() {
215 1
218 } else {
219 return Err(DateFromFieldsError::NotEnoughFields);
220 }
221 }
222 },
223 };
224
225 if fields.month_code.is_none() && fields.ordinal_month.is_none() {
226 return Err(DateFromFieldsError::NotEnoughFields);
229 }
230
231 let mut valid_month_code = None;
232
233 let year = match (fields.era, fields.era_year) {
242 (None, None) => match fields.extended_year {
243 Some(extended_year) => calendar.year_info_from_extended(range_check(
244 extended_year,
245 "year",
246 VALID_YEAR_RANGE,
247 )?),
248 None => match missing_fields_strategy {
249 MissingFieldsStrategy::Reject => {
250 return Err(DateFromFieldsError::NotEnoughFields)
251 }
252 MissingFieldsStrategy::Ecma => {
253 match (fields.month_code, fields.ordinal_month) {
254 (Some(month_code), None) => {
255 let validated = ValidMonthCode::try_from_utf8(month_code)?;
256 valid_month_code = Some(validated);
257 calendar.reference_year_from_month_day(validated, day)?
258 }
259 _ => return Err(DateFromFieldsError::NotEnoughFields),
260 }
261 }
262 },
263 },
264 (Some(era), Some(era_year)) => {
265 let era_year_as_year_info = calendar
266 .year_info_from_era(era, range_check(era_year, "year", VALID_YEAR_RANGE)?)?;
267 if let Some(extended_year) = fields.extended_year {
268 if era_year_as_year_info
269 != calendar.year_info_from_extended(range_check(
270 extended_year,
271 "year",
272 VALID_YEAR_RANGE,
273 )?)
274 {
275 return Err(DateFromFieldsError::InconsistentYear);
276 }
277 }
278 era_year_as_year_info
279 }
280 (Some(_), None) | (None, Some(_)) => return Err(DateFromFieldsError::NotEnoughFields),
282 };
283
284 let month = match fields.month_code {
285 Some(month_code) => {
286 let validated = match valid_month_code {
287 Some(validated) => validated,
288 None => ValidMonthCode::try_from_utf8(month_code)?,
289 };
290 let computed_month = calendar.ordinal_month_from_code(&year, validated, options)?;
291 if let Some(ordinal_month) = fields.ordinal_month {
292 if computed_month != ordinal_month {
293 return Err(DateFromFieldsError::InconsistentMonth);
294 }
295 }
296 computed_month
297 }
298 None => match fields.ordinal_month {
299 Some(month) => month,
300 None => {
301 debug_assert!(false, "Already checked above");
302 return Err(DateFromFieldsError::NotEnoughFields);
303 }
304 },
305 };
306
307 let constrained_month = range_check_with_overflow(
308 month,
309 "month",
310 1..=C::months_in_provided_year(year),
311 options.overflow.unwrap_or_default(),
312 )?;
313 Ok(Self::new_unchecked(
314 year,
315 constrained_month,
316 range_check_with_overflow(
317 day,
318 "day",
319 1..=C::days_in_provided_month(year, constrained_month),
320 options.overflow.unwrap_or_default(),
321 )?,
322 ))
323 }
324
325 pub(crate) fn try_from_ymd(year: C::YearInfo, month: u8, day: u8) -> Result<Self, RangeError> {
326 range_check(month, "month", 1..=C::months_in_provided_year(year))?;
327 range_check(day, "day", 1..=C::days_in_provided_month(year, month))?;
328 Ok(ArithmeticDate::new_unchecked(year, month, day))
329 }
330
331 pub(crate) fn new_balanced(year: C::YearInfo, ordinal_month: i64, day: i64, cal: &C) -> Self {
336 let mut resolved_year = year;
339 let mut resolved_month = ordinal_month;
340 let mut months_in_year = C::months_in_provided_year(resolved_year);
342 while resolved_month <= 0 {
347 resolved_year = cal.year_info_from_extended(resolved_year.to_extended_year() - 1);
348 months_in_year = C::months_in_provided_year(resolved_year);
349 resolved_month += i64::from(months_in_year);
350 }
351 while resolved_month > i64::from(months_in_year) {
356 resolved_month -= i64::from(months_in_year);
357 resolved_year = cal.year_info_from_extended(resolved_year.to_extended_year() + 1);
358 months_in_year = C::months_in_provided_year(resolved_year);
359 }
360 debug_assert!(u8::try_from(resolved_month).is_ok());
361 let mut resolved_month = resolved_month as u8;
362 let mut resolved_day = day;
364 let mut days_in_month = C::days_in_provided_month(resolved_year, resolved_month);
366 while resolved_day <= 0 {
368 resolved_month -= 1;
371 if resolved_month == 0 {
372 resolved_year = cal.year_info_from_extended(resolved_year.to_extended_year() - 1);
376 months_in_year = C::months_in_provided_year(resolved_year);
377 resolved_month = months_in_year;
378 }
379 days_in_month = C::days_in_provided_month(resolved_year, resolved_month);
382 resolved_day += i64::from(days_in_month);
383 }
384 while resolved_day > i64::from(days_in_month) {
386 resolved_day -= i64::from(days_in_month);
390 resolved_month += 1;
391 if resolved_month > months_in_year {
392 resolved_year = cal.year_info_from_extended(resolved_year.to_extended_year() + 1);
396 months_in_year = C::months_in_provided_year(resolved_year);
397 resolved_month = 1;
398 }
399 days_in_month = C::days_in_provided_month(resolved_year, resolved_month);
401 }
402 debug_assert!(u8::try_from(resolved_day).is_ok());
403 let resolved_day = resolved_day as u8;
404 Self::new_unchecked(resolved_year, resolved_month, resolved_day)
406 }
407
408 pub(crate) fn surpasses(
414 &self,
415 other: &Self,
416 duration: DateDuration,
417 sign: i64,
418 cal: &C,
419 ) -> bool {
420 let y0 = cal.year_info_from_extended(duration.add_years_to(self.year.to_extended_year()));
423 let base_month_code = cal.month_code_from_ordinal(&self.year, self.month);
425 let constrain = DateFromFieldsOptions {
426 overflow: Some(Overflow::Constrain),
427 ..Default::default()
428 };
429 let m0_result = cal.ordinal_month_from_code(&y0, base_month_code, constrain);
430 let m0 = match m0_result {
431 Ok(m0) => m0,
432 Err(_) => {
433 debug_assert!(
434 false,
435 "valid month code for calendar, and constrained to the year"
436 );
437 1
438 }
439 };
440 let end_of_month = Self::new_balanced(y0, duration.add_months_to(m0) + 1, 0, cal);
442 let base_day = self.day;
444 let y1;
445 let m1;
446 let d1;
447 if duration.weeks != 0 || duration.days != 0 {
449 let regulated_day = if base_day < end_of_month.day {
454 base_day
455 } else {
456 end_of_month.day
457 };
458 let balanced_date = Self::new_balanced(
463 end_of_month.year,
464 i64::from(end_of_month.month),
465 duration.add_weeks_and_days_to(regulated_day),
466 cal,
467 );
468 y1 = balanced_date.year;
469 m1 = balanced_date.month;
470 d1 = balanced_date.day;
471 } else {
472 y1 = end_of_month.year;
477 m1 = end_of_month.month;
478 d1 = base_day;
479 }
480 #[allow(clippy::collapsible_if)] if y1 != other.year {
489 if sign * (i64::from(y1.to_extended_year()) - i64::from(other.year.to_extended_year()))
490 > 0
491 {
492 return true;
493 }
494 } else if m1 != other.month {
495 if sign * (i64::from(m1) - i64::from(other.month)) > 0 {
496 return true;
497 }
498 } else if d1 != other.day {
499 if sign * (i64::from(d1) - i64::from(other.day)) > 0 {
500 return true;
501 }
502 }
503 false
505 }
506
507 pub(crate) fn added(
512 &self,
513 duration: DateDuration,
514 cal: &C,
515 options: DateAddOptions,
516 ) -> Result<Self, DateError> {
517 let y0 = cal.year_info_from_extended(duration.add_years_to(self.year.to_extended_year()));
520 let base_month = cal.month_code_from_ordinal(&self.year, self.month);
522 let m0 = cal
523 .ordinal_month_from_code(
524 &y0,
525 base_month,
526 DateFromFieldsOptions::from_add_options(options),
527 )
528 .map_err(|e| {
529 match e {
531 MonthCodeError::NotInCalendar => {
532 DateError::UnknownMonthCode(base_month.to_month_code())
533 }
534 MonthCodeError::NotInYear => {
535 DateError::UnknownMonthCode(base_month.to_month_code())
536 }
537 }
538 })?;
539 let end_of_month = Self::new_balanced(y0, duration.add_months_to(m0) + 1, 0, cal);
541 let base_day = self.day;
543 let regulated_day = if base_day < end_of_month.day {
546 base_day
547 } else {
548 if matches!(options.overflow, Some(Overflow::Reject)) {
552 return Err(DateError::Range {
553 field: "day",
554 value: i32::from(base_day),
555 min: 1,
556 max: i32::from(end_of_month.day),
557 });
558 }
559 end_of_month.day
560 };
561 Ok(Self::new_balanced(
565 end_of_month.year,
566 i64::from(end_of_month.month),
567 duration.add_weeks_and_days_to(regulated_day),
568 cal,
569 ))
570 }
571
572 pub(crate) fn until(
577 &self,
578 other: &Self,
579 cal: &C,
580 options: DateDifferenceOptions,
581 ) -> DateDuration {
582 let sign = match other.cmp(self) {
585 Ordering::Greater => 1i64,
586 Ordering::Equal => return DateDuration::default(),
587 Ordering::Less => -1i64,
588 };
589 let mut years = 0;
596 if matches!(options.largest_unit, Some(DateDurationUnit::Years)) {
597 let mut candidate_years = sign;
598 while !self.surpasses(
599 other,
600 DateDuration::from_signed_ymwd(candidate_years, 0, 0, 0),
601 sign,
602 cal,
603 ) {
604 years = candidate_years;
605 candidate_years += sign;
606 }
607 }
608 let mut months = 0;
615 if matches!(
616 options.largest_unit,
617 Some(DateDurationUnit::Years) | Some(DateDurationUnit::Months)
618 ) {
619 let mut candidate_months = sign;
620 while !self.surpasses(
621 other,
622 DateDuration::from_signed_ymwd(years, candidate_months, 0, 0),
623 sign,
624 cal,
625 ) {
626 months = candidate_months;
627 candidate_months += sign;
628 }
629 }
630 let mut weeks = 0;
637 if matches!(options.largest_unit, Some(DateDurationUnit::Weeks)) {
638 let mut candidate_weeks = sign;
639 while !self.surpasses(
640 other,
641 DateDuration::from_signed_ymwd(years, months, candidate_weeks, 0),
642 sign,
643 cal,
644 ) {
645 weeks = candidate_weeks;
646 candidate_weeks += sign;
647 }
648 }
649 let mut days = 0;
655 let mut candidate_days = sign;
656 while !self.surpasses(
657 other,
658 DateDuration::from_signed_ymwd(years, months, weeks, candidate_days),
659 sign,
660 cal,
661 ) {
662 days = candidate_days;
663 candidate_days += sign;
664 }
665 DateDuration::from_signed_ymwd(years, months, weeks, days)
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
673 use crate::cal::{abstract_gregorian::AbstractGregorian, iso::IsoEra};
674
675 #[test]
676 fn test_ord() {
677 let dates_in_order = [
678 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(-10, 1, 1),
679 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(-10, 1, 2),
680 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(-10, 2, 1),
681 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(-1, 1, 1),
682 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(-1, 1, 2),
683 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(-1, 2, 1),
684 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(0, 1, 1),
685 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(0, 1, 2),
686 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(0, 2, 1),
687 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(1, 1, 1),
688 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(1, 1, 2),
689 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(1, 2, 1),
690 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(10, 1, 1),
691 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(10, 1, 2),
692 ArithmeticDate::<AbstractGregorian<IsoEra>>::new_unchecked(10, 2, 1),
693 ];
694 for (i, i_date) in dates_in_order.iter().enumerate() {
695 for (j, j_date) in dates_in_order.iter().enumerate() {
696 let result1 = i_date.cmp(j_date);
697 let result2 = j_date.cmp(i_date);
698 assert_eq!(result1.reverse(), result2);
699 assert_eq!(i.cmp(&j), i_date.cmp(j_date));
700 }
701 }
702 }
703
704 #[test]
705 pub fn zero() {
706 use crate::Date;
707 Date::try_new_iso(2024, 0, 1).unwrap_err();
708 Date::try_new_iso(2024, 1, 0).unwrap_err();
709 Date::try_new_iso(2024, 0, 0).unwrap_err();
710 }
711}