1use crate::{types, Calendar, CalendarError, DateDuration, DateDurationUnit};
6use core::cmp::Ordering;
7use core::convert::TryInto;
8use core::fmt::Debug;
9use core::hash::{Hash, Hasher};
10use core::marker::PhantomData;
11use tinystr::tinystr;
12
13#[derive(Debug)]
15#[allow(clippy::exhaustive_structs)] pub(crate) struct ArithmeticDate<C: CalendarArithmetic> {
17 pub year: i32,
18 pub month: u8,
20 pub day: u8,
22 pub year_info: C::YearInfo,
24 marker: PhantomData<C>,
25}
26
27impl<C: CalendarArithmetic> Copy for ArithmeticDate<C> {}
30impl<C: CalendarArithmetic> Clone for ArithmeticDate<C> {
31 fn clone(&self) -> Self {
32 *self
33 }
34}
35
36impl<C: CalendarArithmetic> PartialEq for ArithmeticDate<C> {
37 fn eq(&self, other: &Self) -> bool {
38 self.year == other.year && self.month == other.month && self.day == other.day
39 }
40}
41
42impl<C: CalendarArithmetic> Eq for ArithmeticDate<C> {}
43
44impl<C: CalendarArithmetic> Ord for ArithmeticDate<C> {
45 fn cmp(&self, other: &Self) -> Ordering {
46 self.year
47 .cmp(&other.year)
48 .then(self.month.cmp(&other.month))
49 .then(self.day.cmp(&other.day))
50 }
51}
52
53impl<C: CalendarArithmetic> PartialOrd for ArithmeticDate<C> {
54 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
55 Some(self.cmp(other))
56 }
57}
58
59impl<C: CalendarArithmetic> Hash for ArithmeticDate<C> {
60 fn hash<H>(&self, state: &mut H)
61 where
62 H: Hasher,
63 {
64 self.year.hash(state);
65 self.month.hash(state);
66 self.day.hash(state);
67 }
68}
69
70#[allow(dead_code)] pub(crate) const MAX_ITERS_FOR_DAYS_OF_MONTH: u8 = 33;
73
74pub(crate) trait CalendarArithmetic: Calendar {
75 type YearInfo: Copy + Debug;
79
80 fn month_days(year: i32, month: u8, year_info: Self::YearInfo) -> u8;
83 fn months_for_every_year(year: i32, year_info: Self::YearInfo) -> u8;
84 fn is_leap_year(year: i32, year_info: Self::YearInfo) -> bool;
85 fn last_month_day_in_year(year: i32, year_info: Self::YearInfo) -> (u8, u8);
86
87 fn days_in_provided_year(year: i32, year_info: Self::YearInfo) -> u16 {
94 let months_in_year = Self::months_for_every_year(year, year_info);
95 let mut days: u16 = 0;
96 for month in 1..=months_in_year {
97 days += Self::month_days(year, month, year_info) as u16;
98 }
99 days
100 }
101}
102
103pub(crate) trait PrecomputedDataSource<YearInfo> {
104 fn load_or_compute_info(&self, year: i32) -> YearInfo;
109}
110
111impl PrecomputedDataSource<()> for () {
112 fn load_or_compute_info(&self, _year: i32) {}
113}
114
115impl<C: CalendarArithmetic> ArithmeticDate<C> {
116 #[inline]
118 pub const fn new_unchecked(year: i32, month: u8, day: u8) -> Self
119 where
120 C: CalendarArithmetic<YearInfo = ()>,
121 {
122 Self::new_unchecked_with_info(year, month, day, ())
123 }
124 #[inline]
126 pub const fn new_unchecked_with_info(
127 year: i32,
128 month: u8,
129 day: u8,
130 year_info: C::YearInfo,
131 ) -> Self {
132 ArithmeticDate {
133 year,
134 month,
135 day,
136 year_info,
137 marker: PhantomData,
138 }
139 }
140
141 #[inline]
142 pub fn min_date() -> Self
143 where
144 C: CalendarArithmetic<YearInfo = ()>,
145 {
146 ArithmeticDate {
147 year: i32::MIN,
148 month: 1,
149 day: 1,
150 year_info: (),
151 marker: PhantomData,
152 }
153 }
154
155 #[inline]
156 pub fn max_date() -> Self
157 where
158 C: CalendarArithmetic<YearInfo = ()>,
159 {
160 let year = i32::MAX;
161 let (month, day) = C::last_month_day_in_year(year, ());
162 ArithmeticDate {
163 year: i32::MAX,
164 month,
165 day,
166 year_info: (),
167 marker: PhantomData,
168 }
169 }
170
171 #[inline]
172 fn offset_days(&mut self, mut day_offset: i32, data: &impl PrecomputedDataSource<C::YearInfo>) {
173 while day_offset != 0 {
174 let month_days = C::month_days(self.year, self.month, self.year_info);
175 if self.day as i32 + day_offset > month_days as i32 {
176 self.offset_months(1, data);
177 day_offset -= month_days as i32;
178 } else if self.day as i32 + day_offset < 1 {
179 self.offset_months(-1, data);
180 day_offset += C::month_days(self.year, self.month, self.year_info) as i32;
181 } else {
182 self.day = (self.day as i32 + day_offset) as u8;
183 day_offset = 0;
184 }
185 }
186 }
187
188 #[inline]
189 fn offset_months(
190 &mut self,
191 mut month_offset: i32,
192 data: &impl PrecomputedDataSource<C::YearInfo>,
193 ) {
194 while month_offset != 0 {
195 let year_months = C::months_for_every_year(self.year, self.year_info);
196 if self.month as i32 + month_offset > year_months as i32 {
197 self.year += 1;
198 self.year_info = data.load_or_compute_info(self.year);
199 month_offset -= year_months as i32;
200 } else if self.month as i32 + month_offset < 1 {
201 self.year -= 1;
202 self.year_info = data.load_or_compute_info(self.year);
203 month_offset += C::months_for_every_year(self.year, self.year_info) as i32;
204 } else {
205 self.month = (self.month as i32 + month_offset) as u8;
206 month_offset = 0
207 }
208 }
209 }
210
211 #[inline]
212 pub fn offset_date(
213 &mut self,
214 offset: DateDuration<C>,
215 data: &impl PrecomputedDataSource<C::YearInfo>,
216 ) {
217 if offset.years != 0 {
218 self.year += offset.years;
220 self.year_info = data.load_or_compute_info(self.year);
221 }
222
223 self.offset_months(offset.months, data);
224
225 let day_offset = offset.days + offset.weeks * 7 + self.day as i32 - 1;
226 self.day = 1;
227 self.offset_days(day_offset, data);
228 }
229
230 #[inline]
231 pub fn until(
232 &self,
233 date2: ArithmeticDate<C>,
234 _largest_unit: DateDurationUnit,
235 _smaller_unit: DateDurationUnit,
236 ) -> DateDuration<C> {
237 DateDuration::new(
240 self.year - date2.year,
241 self.month as i32 - date2.month as i32,
242 0,
243 self.day as i32 - date2.day as i32,
244 )
245 }
246
247 #[inline]
248 pub fn days_in_year(&self) -> u16 {
249 C::days_in_provided_year(self.year, self.year_info)
250 }
251
252 #[inline]
253 pub fn months_in_year(&self) -> u8 {
254 C::months_for_every_year(self.year, self.year_info)
255 }
256
257 #[inline]
258 pub fn days_in_month(&self) -> u8 {
259 C::month_days(self.year, self.month, self.year_info)
260 }
261
262 #[inline]
263 pub fn day_of_year(&self) -> u16 {
264 let mut day_of_year = 0;
265 for month in 1..self.month {
266 day_of_year += C::month_days(self.year, month, self.year_info) as u16;
267 }
268 day_of_year + (self.day as u16)
269 }
270
271 #[inline]
272 pub fn date_from_year_day(year: i32, year_day: u32) -> ArithmeticDate<C>
273 where
274 C: CalendarArithmetic<YearInfo = ()>,
275 {
276 let mut month = 1;
277 let mut day = year_day as i32;
278 while month <= C::months_for_every_year(year, ()) {
279 let month_days = C::month_days(year, month, ()) as i32;
280 if day <= month_days {
281 break;
282 } else {
283 day -= month_days;
284 month += 1;
285 }
286 }
287
288 debug_assert!(day <= C::month_days(year, month, ()) as i32);
289 #[allow(clippy::unwrap_used)]
290 ArithmeticDate {
292 year,
293 month,
294 day: day.try_into().unwrap_or(0),
295 year_info: (),
296 marker: PhantomData,
297 }
298 }
299
300 #[inline]
301 pub fn day_of_month(&self) -> types::DayOfMonth {
302 types::DayOfMonth(self.day.into())
303 }
304
305 #[inline]
313 pub fn month(&self) -> types::FormattableMonth {
314 let code = match self.month {
315 a if a > C::months_for_every_year(self.year, self.year_info) => tinystr!(4, "und"),
316 1 => tinystr!(4, "M01"),
317 2 => tinystr!(4, "M02"),
318 3 => tinystr!(4, "M03"),
319 4 => tinystr!(4, "M04"),
320 5 => tinystr!(4, "M05"),
321 6 => tinystr!(4, "M06"),
322 7 => tinystr!(4, "M07"),
323 8 => tinystr!(4, "M08"),
324 9 => tinystr!(4, "M09"),
325 10 => tinystr!(4, "M10"),
326 11 => tinystr!(4, "M11"),
327 12 => tinystr!(4, "M12"),
328 13 => tinystr!(4, "M13"),
329 _ => tinystr!(4, "und"),
330 };
331 types::FormattableMonth {
332 ordinal: self.month as u32,
333 code: types::MonthCode(code),
334 }
335 }
336
337 pub fn new_from_codes<C2: Calendar>(
341 cal: &C2,
344 year: i32,
345 month_code: types::MonthCode,
346 day: u8,
347 ) -> Result<Self, CalendarError>
348 where
349 C: CalendarArithmetic<YearInfo = ()>,
350 {
351 let month = if let Some((ordinal, false)) = month_code.parsed() {
352 ordinal
353 } else {
354 return Err(CalendarError::UnknownMonthCode(
355 month_code.0,
356 cal.debug_name(),
357 ));
358 };
359
360 if month > C::months_for_every_year(year, ()) {
361 return Err(CalendarError::UnknownMonthCode(
362 month_code.0,
363 cal.debug_name(),
364 ));
365 }
366
367 let max_day = C::month_days(year, month, ());
368 if day > max_day {
369 return Err(CalendarError::Overflow {
370 field: "day",
371 max: max_day as usize,
372 });
373 }
374
375 Ok(Self::new_unchecked(year, month, day))
376 }
377
378 pub fn new_from_ordinals(year: i32, month: u8, day: u8) -> Result<Self, CalendarError>
382 where
383 C: CalendarArithmetic<YearInfo = ()>,
384 {
385 Self::new_from_ordinals_with_info(year, month, day, ())
386 }
387
388 pub fn new_from_ordinals_with_info(
391 year: i32,
392 month: u8,
393 day: u8,
394 info: C::YearInfo,
395 ) -> Result<Self, CalendarError> {
396 let max_month = C::months_for_every_year(year, info);
397 if month > max_month {
398 return Err(CalendarError::Overflow {
399 field: "month",
400 max: max_month as usize,
401 });
402 }
403 let max_day = C::month_days(year, month, info);
404 if day > max_day {
405 return Err(CalendarError::Overflow {
406 field: "day",
407 max: max_day as usize,
408 });
409 }
410
411 Ok(Self::new_unchecked_with_info(year, month, day, info))
412 }
413}
414
415#[cfg(test)]
416mod tests {
417 use super::*;
418 use crate::Iso;
419
420 #[test]
421 fn test_ord() {
422 let dates_in_order = [
423 ArithmeticDate::<Iso>::new_unchecked(-10, 1, 1),
424 ArithmeticDate::<Iso>::new_unchecked(-10, 1, 2),
425 ArithmeticDate::<Iso>::new_unchecked(-10, 2, 1),
426 ArithmeticDate::<Iso>::new_unchecked(-1, 1, 1),
427 ArithmeticDate::<Iso>::new_unchecked(-1, 1, 2),
428 ArithmeticDate::<Iso>::new_unchecked(-1, 2, 1),
429 ArithmeticDate::<Iso>::new_unchecked(0, 1, 1),
430 ArithmeticDate::<Iso>::new_unchecked(0, 1, 2),
431 ArithmeticDate::<Iso>::new_unchecked(0, 2, 1),
432 ArithmeticDate::<Iso>::new_unchecked(1, 1, 1),
433 ArithmeticDate::<Iso>::new_unchecked(1, 1, 2),
434 ArithmeticDate::<Iso>::new_unchecked(1, 2, 1),
435 ArithmeticDate::<Iso>::new_unchecked(10, 1, 1),
436 ArithmeticDate::<Iso>::new_unchecked(10, 1, 2),
437 ArithmeticDate::<Iso>::new_unchecked(10, 2, 1),
438 ];
439 for (i, i_date) in dates_in_order.iter().enumerate() {
440 for (j, j_date) in dates_in_order.iter().enumerate() {
441 let result1 = i_date.cmp(j_date);
442 let result2 = j_date.cmp(i_date);
443 assert_eq!(result1.reverse(), result2);
444 assert_eq!(i.cmp(&j), i_date.cmp(j_date));
445 }
446 }
447 }
448}