1use crate::cal::abstract_gregorian::{impl_with_abstract_gregorian, GregorianYears};
6use crate::calendar_arithmetic::ArithmeticDate;
7use crate::error::UnknownEraError;
8use crate::{types, Date, DateError, RangeError};
9use tinystr::tinystr;
10
11#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
22#[allow(clippy::exhaustive_structs)] pub struct Iso;
24
25impl_with_abstract_gregorian!(crate::cal::Iso, IsoDateInner, IsoEra, _x, IsoEra);
26
27#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub(crate) struct IsoEra;
29
30impl GregorianYears for IsoEra {
31 fn extended_from_era_year(
32 &self,
33 era: Option<&[u8]>,
34 year: i32,
35 ) -> Result<i32, UnknownEraError> {
36 match era {
37 Some(b"default") | None => Ok(year),
38 Some(_) => Err(UnknownEraError),
39 }
40 }
41
42 fn era_year_from_extended(&self, extended_year: i32, _month: u8, _day: u8) -> types::EraYear {
43 types::EraYear {
44 era_index: Some(0),
45 era: tinystr!(16, "default"),
46 year: extended_year,
47 extended_year,
48 ambiguity: types::YearAmbiguity::Unambiguous,
49 }
50 }
51
52 fn debug_name(&self) -> &'static str {
53 "ISO"
54 }
55}
56
57impl Date<Iso> {
58 pub fn try_new_iso(year: i32, month: u8, day: u8) -> Result<Date<Iso>, RangeError> {
71 ArithmeticDate::new_gregorian::<IsoEra>(year, month, day)
72 .map(IsoDateInner)
73 .map(|i| Date::from_raw(i, Iso))
74 }
75}
76
77impl Iso {
78 pub fn new() -> Self {
80 Self
81 }
82}
83
84#[cfg(test)]
85mod test {
86 use super::*;
87 use crate::types::{DateDuration, RataDie, Weekday};
88 use crate::Calendar;
89
90 #[test]
91 fn iso_overflow() {
92 #[derive(Debug)]
93 struct TestCase {
94 year: i32,
95 month: u8,
96 day: u8,
97 rd: RataDie,
98 saturating: bool,
99 }
100 let max_year = Iso.from_rata_die(RataDie::new(i32::MAX as i64)).0.year;
102
103 let min_year = -5879610;
106
107 let cases = [
108 TestCase {
109 year: min_year,
111 month: 6,
112 day: 22,
113 rd: RataDie::new(i32::MIN as i64),
114 saturating: false,
115 },
116 TestCase {
117 year: min_year,
118 month: 6,
119 day: 23,
120 rd: RataDie::new(i32::MIN as i64 + 1),
121 saturating: false,
122 },
123 TestCase {
124 year: min_year,
125 month: 6,
126 day: 21,
127 rd: RataDie::new(i32::MIN as i64 - 1),
128 saturating: false,
129 },
130 TestCase {
131 year: min_year,
132 month: 12,
133 day: 31,
134 rd: RataDie::new(-2147483456),
135 saturating: false,
136 },
137 TestCase {
138 year: min_year + 1,
139 month: 1,
140 day: 1,
141 rd: RataDie::new(-2147483455),
142 saturating: false,
143 },
144 TestCase {
145 year: max_year,
146 month: 6,
147 day: 11,
148 rd: RataDie::new(i32::MAX as i64 - 30),
149 saturating: false,
150 },
151 TestCase {
152 year: max_year,
153 month: 7,
154 day: 9,
155 rd: RataDie::new(i32::MAX as i64 - 2),
156 saturating: false,
157 },
158 TestCase {
159 year: max_year,
160 month: 7,
161 day: 10,
162 rd: RataDie::new(i32::MAX as i64 - 1),
163 saturating: false,
164 },
165 TestCase {
166 year: max_year,
168 month: 7,
169 day: 11,
170 rd: RataDie::new(i32::MAX as i64),
171 saturating: false,
172 },
173 TestCase {
174 year: max_year,
175 month: 7,
176 day: 12,
177 rd: RataDie::new(i32::MAX as i64 + 1),
178 saturating: false,
179 },
180 TestCase {
181 year: i32::MIN,
182 month: 1,
183 day: 2,
184 rd: RataDie::new(-784352296669),
185 saturating: false,
186 },
187 TestCase {
188 year: i32::MIN,
189 month: 1,
190 day: 1,
191 rd: RataDie::new(-784352296670),
192 saturating: false,
193 },
194 TestCase {
195 year: i32::MIN,
196 month: 1,
197 day: 1,
198 rd: RataDie::new(-784352296671),
199 saturating: true,
200 },
201 TestCase {
202 year: i32::MAX,
203 month: 12,
204 day: 30,
205 rd: RataDie::new(784352295938),
206 saturating: false,
207 },
208 TestCase {
209 year: i32::MAX,
210 month: 12,
211 day: 31,
212 rd: RataDie::new(784352295939),
213 saturating: false,
214 },
215 TestCase {
216 year: i32::MAX,
217 month: 12,
218 day: 31,
219 rd: RataDie::new(784352295940),
220 saturating: true,
221 },
222 ];
223
224 for case in cases {
225 let date = Date::try_new_iso(case.year, case.month, case.day).unwrap();
226 if !case.saturating {
227 assert_eq!(date.to_rata_die(), case.rd, "{case:?}");
228 }
229 assert_eq!(Date::from_rata_die(case.rd, Iso), date, "{case:?}");
230 }
231 }
232
233 #[test]
235 fn min_year() {
236 assert_eq!(
237 Date::from_rata_die(RataDie::big_negative(), Iso)
238 .year()
239 .era()
240 .unwrap()
241 .year,
242 i32::MIN
243 );
244 }
245
246 #[test]
247 fn test_day_of_week() {
248 assert_eq!(
250 Date::try_new_iso(2021, 6, 23).unwrap().day_of_week(),
251 Weekday::Wednesday,
252 );
253 assert_eq!(
255 Date::try_new_iso(1983, 2, 2).unwrap().day_of_week(),
256 Weekday::Wednesday,
257 );
258 assert_eq!(
260 Date::try_new_iso(2020, 1, 21).unwrap().day_of_week(),
261 Weekday::Tuesday,
262 );
263 }
264
265 #[test]
266 fn test_day_of_year() {
267 assert_eq!(Date::try_new_iso(2021, 6, 23).unwrap().day_of_year().0, 174,);
269 assert_eq!(Date::try_new_iso(2020, 6, 23).unwrap().day_of_year().0, 175,);
271 assert_eq!(Date::try_new_iso(1983, 2, 2).unwrap().day_of_year().0, 33,);
273 }
274
275 #[test]
276 fn test_offset() {
277 let today = Date::try_new_iso(2021, 6, 23).unwrap();
278 let today_plus_5000 = Date::try_new_iso(2035, 3, 2).unwrap();
279 let offset = today
280 .try_added_with_options(DateDuration::for_days(5000), Default::default())
281 .unwrap();
282 assert_eq!(offset, today_plus_5000);
283
284 let today = Date::try_new_iso(2021, 6, 23).unwrap();
285 let today_minus_5000 = Date::try_new_iso(2007, 10, 15).unwrap();
286 let offset = today
287 .try_added_with_options(DateDuration::for_days(-5000), Default::default())
288 .unwrap();
289 assert_eq!(offset, today_minus_5000);
290 }
291
292 #[test]
293 fn test_offset_at_month_boundary() {
294 let today = Date::try_new_iso(2020, 2, 28).unwrap();
295 let today_plus_2 = Date::try_new_iso(2020, 3, 1).unwrap();
296 let offset = today
297 .try_added_with_options(DateDuration::for_days(2), Default::default())
298 .unwrap();
299 assert_eq!(offset, today_plus_2);
300
301 let today = Date::try_new_iso(2020, 2, 28).unwrap();
302 let today_plus_3 = Date::try_new_iso(2020, 3, 2).unwrap();
303 let offset = today
304 .try_added_with_options(DateDuration::for_days(3), Default::default())
305 .unwrap();
306 assert_eq!(offset, today_plus_3);
307
308 let today = Date::try_new_iso(2020, 2, 28).unwrap();
309 let today_plus_1 = Date::try_new_iso(2020, 2, 29).unwrap();
310 let offset = today
311 .try_added_with_options(DateDuration::for_days(1), Default::default())
312 .unwrap();
313 assert_eq!(offset, today_plus_1);
314
315 let today = Date::try_new_iso(2019, 2, 28).unwrap();
316 let today_plus_2 = Date::try_new_iso(2019, 3, 2).unwrap();
317 let offset = today
318 .try_added_with_options(DateDuration::for_days(2), Default::default())
319 .unwrap();
320 assert_eq!(offset, today_plus_2);
321
322 let today = Date::try_new_iso(2019, 2, 28).unwrap();
323 let today_plus_1 = Date::try_new_iso(2019, 3, 1).unwrap();
324 let offset = today
325 .try_added_with_options(DateDuration::for_days(1), Default::default())
326 .unwrap();
327 assert_eq!(offset, today_plus_1);
328
329 let today = Date::try_new_iso(2020, 3, 1).unwrap();
330 let today_minus_1 = Date::try_new_iso(2020, 2, 29).unwrap();
331 let offset = today
332 .try_added_with_options(DateDuration::for_days(-1), Default::default())
333 .unwrap();
334 assert_eq!(offset, today_minus_1);
335 }
336
337 #[test]
338 fn test_offset_handles_negative_month_offset() {
339 let today = Date::try_new_iso(2020, 3, 1).unwrap();
340 let today_minus_2_months = Date::try_new_iso(2020, 1, 1).unwrap();
341 let offset = today
342 .try_added_with_options(DateDuration::for_months(-2), Default::default())
343 .unwrap();
344 assert_eq!(offset, today_minus_2_months);
345
346 let today = Date::try_new_iso(2020, 3, 1).unwrap();
347 let today_minus_4_months = Date::try_new_iso(2019, 11, 1).unwrap();
348 let offset = today
349 .try_added_with_options(DateDuration::for_months(-4), Default::default())
350 .unwrap();
351 assert_eq!(offset, today_minus_4_months);
352
353 let today = Date::try_new_iso(2020, 3, 1).unwrap();
354 let today_minus_24_months = Date::try_new_iso(2018, 3, 1).unwrap();
355 let offset = today
356 .try_added_with_options(DateDuration::for_months(-24), Default::default())
357 .unwrap();
358 assert_eq!(offset, today_minus_24_months);
359
360 let today = Date::try_new_iso(2020, 3, 1).unwrap();
361 let today_minus_27_months = Date::try_new_iso(2017, 12, 1).unwrap();
362 let offset = today
363 .try_added_with_options(DateDuration::for_months(-27), Default::default())
364 .unwrap();
365 assert_eq!(offset, today_minus_27_months);
366 }
367
368 #[test]
369 fn test_offset_handles_out_of_bound_month_offset() {
370 let today = Date::try_new_iso(2021, 1, 31).unwrap();
371 let today_plus_1_month = Date::try_new_iso(2021, 2, 28).unwrap();
373 let offset = today
374 .try_added_with_options(DateDuration::for_months(1), Default::default())
375 .unwrap();
376 assert_eq!(offset, today_plus_1_month);
377
378 let today = Date::try_new_iso(2021, 1, 31).unwrap();
379 let today_plus_1_month_1_day = Date::try_new_iso(2021, 3, 1).unwrap();
382 let offset = today
383 .try_added_with_options(
384 DateDuration {
385 months: 1,
386 days: 1,
387 ..Default::default()
388 },
389 Default::default(),
390 )
391 .unwrap();
392 assert_eq!(offset, today_plus_1_month_1_day);
393 }
394
395 #[test]
396 fn test_iso_to_from_rd() {
397 fn check(rd: i64, year: i32, month: u8, day: u8) {
400 let rd = RataDie::new(rd);
401
402 assert_eq!(
403 Date::from_rata_die(rd, Iso),
404 Date::try_new_iso(year, month, day).unwrap(),
405 "RD: {rd:?}"
406 );
407 }
408 check(-1828, -5, 12, 30);
409 check(-1827, -5, 12, 31); check(-1826, -4, 1, 1);
411 check(-1462, -4, 12, 30);
412 check(-1461, -4, 12, 31);
413 check(-1460, -3, 1, 1);
414 check(-1459, -3, 1, 2);
415 check(-732, -2, 12, 30);
416 check(-731, -2, 12, 31);
417 check(-730, -1, 1, 1);
418 check(-367, -1, 12, 30);
419 check(-366, -1, 12, 31);
420 check(-365, 0, 1, 1); check(-364, 0, 1, 2);
422 check(-1, 0, 12, 30);
423 check(0, 0, 12, 31);
424 check(1, 1, 1, 1);
425 check(2, 1, 1, 2);
426 check(364, 1, 12, 30);
427 check(365, 1, 12, 31);
428 check(366, 2, 1, 1);
429 check(1459, 4, 12, 29);
430 check(1460, 4, 12, 30);
431 check(1461, 4, 12, 31); check(1462, 5, 1, 1);
433 }
434}