Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/tests/calendar.rs

22.1 KiB, 13 runs

created by r1870400018:6407, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
2//! Anthropic Claude
3
4use oxedyne_fe2o3_core::{
5 prelude::*,
6 test::test_it,
7};
8use oxedyne_fe2o3_datime::{
9 calendar::Calendar,
10 constant::DayOfWeek,
11 core::Time,
12 time::CalClockZone,
13};
14
15pub fn test_calendar(filter: &str) -> Outcome<()> {
16
17 res!(test_it(filter, &["calendar_creation", "all", "calendar", "comprehensive"], || {
18 // Test creating different calendar types
19 let gregorian = Calendar::new();
20 assert_eq!(gregorian, Calendar::Gregorian);
21 assert_eq!(gregorian.name(), "Gregorian");
22 assert_eq!(gregorian.id(), "gregorian");
23
24 let islamic = Calendar::Islamic;
25 assert_eq!(islamic.name(), "Islamic");
26 assert_eq!(islamic.id(), "islamic");
27
28 let japanese = Calendar::Japanese;
29 assert_eq!(japanese.name(), "Japanese");
30 assert_eq!(japanese.id(), "japanese");
31
32 let thai = Calendar::Thai;
33 assert_eq!(thai.name(), "Thai Buddhist");
34 assert_eq!(thai.id(), "thai");
35
36 let minguo = Calendar::Minguo;
37 assert_eq!(minguo.name(), "Minguo");
38 assert_eq!(minguo.id(), "minguo");
39
40 let holocene = Calendar::Holocene;
41 assert_eq!(holocene.name(), "Holocene");
42 assert_eq!(holocene.id(), "holocene");
43
44 println!("✅ All calendar types created successfully");
45 Ok(())
46 }));
47
48 res!(test_it(filter, &["calendar_date_creation", "all", "calendar", "comprehensive"], || {
49 let zone = CalClockZone::utc();
50
51 // Test Gregorian date creation (the new API)
52 let gregorian = Calendar::Gregorian;
53 let greg_date = res!(gregorian.date(2024, 1, 15, zone.clone()));
54 assert_eq!(greg_date.year(), 2024);
55 assert_eq!(greg_date.month(), 1);
56 assert_eq!(greg_date.day(), 15);
57
58 // Test Thai Buddhist calendar (2567 = 2024 Gregorian)
59 let thai = Calendar::Thai;
60 let thai_date = res!(thai.date(2567, 1, 15, zone.clone()));
61 assert_eq!(thai_date.year(), 2024); // Internal Gregorian representation
62
63 // Test Minguo calendar (113 = 2024 Gregorian)
64 let minguo = Calendar::Minguo;
65 let minguo_date = res!(minguo.date(113, 1, 15, zone.clone()));
66 assert_eq!(minguo_date.year(), 2024); // Internal Gregorian representation
67
68 // Test Holocene calendar (12024 = 2024 Gregorian)
69 let holocene = Calendar::Holocene;
70 let holocene_date = res!(holocene.date(12024, 1, 15, zone.clone()));
71 assert_eq!(holocene_date.year(), 2024); // Internal Gregorian representation
72
73 // Test Islamic calendar basic creation
74 let islamic = Calendar::Islamic;
75 let islamic_date = res!(islamic.date(1445, 1, 15, zone.clone()));
76 // Islamic dates are converted internally - the exact conversion is complex
77 assert!(islamic_date.year() > 1000); // Should be a reasonable Gregorian year
78
79 println!("✅ Date creation with new Calendar API works correctly");
80 Ok(())
81 }));
82
83 res!(test_it(filter, &["calendar_leap_years", "all", "calendar", "comprehensive"], || {
84 // Test leap year calculations for different calendars
85
86 // Gregorian leap years
87 let gregorian = Calendar::Gregorian;
88 assert!(gregorian.is_leap_year(2024)); // Divisible by 4
89 assert!(!gregorian.is_leap_year(1900)); // Century not divisible by 400
90 assert!(gregorian.is_leap_year(2000)); // Century divisible by 400
91 assert!(!gregorian.is_leap_year(2023)); // Not divisible by 4
92
93 // Julian leap years (every 4 years, no exceptions)
94 let julian = Calendar::Julian;
95 assert!(julian.is_leap_year(2024));
96 assert!(julian.is_leap_year(1900)); // Leap in Julian, not Gregorian
97 assert!(julian.is_leap_year(2000));
98 assert!(!julian.is_leap_year(2023));
99
100 // Islamic leap years (30-year cycle)
101 let islamic = Calendar::Islamic;
102 assert!(islamic.is_leap_year(2)); // 2nd year of cycle
103 assert!(islamic.is_leap_year(5)); // 5th year of cycle
104 assert!(!islamic.is_leap_year(1)); // 1st year not leap
105 assert!(!islamic.is_leap_year(3)); // 3rd year not leap
106
107 // Thai, Minguo, and Holocene use Gregorian rules
108 let thai = Calendar::Thai;
109 let minguo = Calendar::Minguo;
110 let holocene = Calendar::Holocene;
111
112 assert!(thai.is_leap_year(2567)); // Thai 2567 = Gregorian 2024
113 assert!(minguo.is_leap_year(113)); // Minguo 113 = Gregorian 2024
114 assert!(holocene.is_leap_year(12024)); // Holocene 12024 = Gregorian 2024
115
116 println!("✅ Leap year calculations work correctly for all calendars");
117 Ok(())
118 }));
119
120 res!(test_it(filter, &["calendar_month_days", "all", "calendar", "comprehensive"], || {
121 // Test days in month for different calendars
122
123 // Gregorian/Thai/Minguo/Holocene all use same month structure
124 let gregorian = Calendar::Gregorian;
125 assert_eq!(res!(gregorian.days_in_month(2024, 1)), 31); // January
126 assert_eq!(res!(gregorian.days_in_month(2024, 2)), 29); // February leap year
127 assert_eq!(res!(gregorian.days_in_month(2023, 2)), 28); // February non-leap
128 assert_eq!(res!(gregorian.days_in_month(2024, 4)), 30); // April
129
130 // Islamic calendar has different month structure
131 let islamic = Calendar::Islamic;
132 assert_eq!(res!(islamic.days_in_month(1445, 1)), 30); // Muharram (odd month)
133 assert_eq!(res!(islamic.days_in_month(1445, 2)), 29); // Safar (even month)
134
135 // Year 1445 is cycle year 5, which IS a leap year
136 assert!(islamic.is_leap_year(1445)); // Year 5 of 30-year cycle
137 assert_eq!(res!(islamic.days_in_month(1445, 12)), 30); // Dhul-Hijjah leap year
138
139 // Test non-leap year (1444 is cycle year 4, not leap)
140 assert!(!islamic.is_leap_year(1444)); // Year 4 of 30-year cycle, not leap
141 assert_eq!(res!(islamic.days_in_month(1444, 12)), 29); // Dhul-Hijjah non-leap year
142
143 println!("✅ Days in month calculations work correctly for all calendars");
144 Ok(())
145 }));
146
147 res!(test_it(filter, &["calendar_validation", "all", "calendar", "comprehensive"], || {
148 // Test date validation for different calendars
149
150 let gregorian = Calendar::Gregorian;
151 let islamic = Calendar::Islamic;
152 let thai = Calendar::Thai;
153 let japanese = Calendar::Japanese;
154
155 // Valid dates
156 assert!(gregorian.validate_date(2024, 2, 29).is_ok()); // Leap year
157 assert!(islamic.validate_date(1445, 1, 30).is_ok()); // Valid Islamic date
158 assert!(thai.validate_date(2567, 1, 31).is_ok()); // Valid Thai date
159 assert!(japanese.validate_date(5, 1, 15).is_ok()); // Valid Japanese era year
160
161 // Invalid dates
162 assert!(gregorian.validate_date(2023, 2, 29).is_err()); // Non-leap year Feb 29
163 assert!(gregorian.validate_date(2024, 4, 31).is_err()); // April has 30 days
164 assert!(islamic.validate_date(1445, 1, 31).is_err()); // Month 1 has 30 days
165 assert!(islamic.validate_date(1445, 13, 1).is_err()); // Month 13 doesn't exist
166 assert!(islamic.validate_date(0, 1, 1).is_err()); // Year 0 invalid
167 assert!(thai.validate_date(0, 1, 1).is_err()); // Year 0 invalid
168
169 // Day 0 invalid for all calendars
170 assert!(gregorian.validate_date(2024, 1, 0).is_err());
171 assert!(islamic.validate_date(1445, 1, 0).is_err());
172
173 println!("✅ Date validation works correctly for all calendars");
174 Ok(())
175 }));
176
177 res!(test_it(filter, &["calendar_parsing", "all", "calendar", "comprehensive"], || {
178 // Test parsing calendar names from strings
179
180 assert_eq!(res!(Calendar::from_str("gregorian")), Calendar::Gregorian);
181 assert_eq!(res!(Calendar::from_str("GREGORIAN")), Calendar::Gregorian);
182 assert_eq!(res!(Calendar::from_str("greg")), Calendar::Gregorian);
183 assert_eq!(res!(Calendar::from_str("g")), Calendar::Gregorian);
184
185 assert_eq!(res!(Calendar::from_str("julian")), Calendar::Julian);
186 assert_eq!(res!(Calendar::from_str("jul")), Calendar::Julian);
187 assert_eq!(res!(Calendar::from_str("j")), Calendar::Julian);
188
189 assert_eq!(res!(Calendar::from_str("islamic")), Calendar::Islamic);
190 assert_eq!(res!(Calendar::from_str("hijri")), Calendar::Islamic);
191 assert_eq!(res!(Calendar::from_str("muslim")), Calendar::Islamic);
192 assert_eq!(res!(Calendar::from_str("i")), Calendar::Islamic);
193
194 assert_eq!(res!(Calendar::from_str("japanese")), Calendar::Japanese);
195 assert_eq!(res!(Calendar::from_str("jp")), Calendar::Japanese);
196 assert_eq!(res!(Calendar::from_str("imperial")), Calendar::Japanese);
197
198 assert_eq!(res!(Calendar::from_str("thai")), Calendar::Thai);
199 assert_eq!(res!(Calendar::from_str("buddhist")), Calendar::Thai);
200 assert_eq!(res!(Calendar::from_str("th")), Calendar::Thai);
201
202 assert_eq!(res!(Calendar::from_str("minguo")), Calendar::Minguo);
203 assert_eq!(res!(Calendar::from_str("roc")), Calendar::Minguo);
204 assert_eq!(res!(Calendar::from_str("taiwan")), Calendar::Minguo);
205
206 assert_eq!(res!(Calendar::from_str("holocene")), Calendar::Holocene);
207 assert_eq!(res!(Calendar::from_str("human")), Calendar::Holocene);
208 assert_eq!(res!(Calendar::from_str("h")), Calendar::Holocene);
209
210 // Invalid calendar name
211 assert!(Calendar::from_str("invalid").is_err());
212
213 println!("✅ Calendar name parsing works correctly");
214 Ok(())
215 }));
216
217 res!(test_it(filter, &["calendar_year_conversions", "all", "calendar", "comprehensive"], || {
218 // Test year conversions between different calendars
219
220 // Test known conversions
221 let gregorian = Calendar::Gregorian;
222 let thai = Calendar::Thai;
223 let minguo = Calendar::Minguo;
224 let holocene = Calendar::Holocene;
225
226 // Thai Buddhist = Gregorian + 543
227 // So Thai 2567 = Gregorian 2024
228 assert_eq!(thai.to_gregorian_year(2567), 2024);
229
230 // Minguo = Gregorian - 1911
231 // So Minguo 113 = Gregorian 2024
232 assert_eq!(minguo.to_gregorian_year(113), 2024);
233
234 // Holocene = Gregorian + 10000
235 // So Holocene 12024 = Gregorian 2024
236 assert_eq!(holocene.to_gregorian_year(12024), 2024);
237
238 // Test epoch years
239 assert_eq!(gregorian.epoch_year(), 1);
240 assert_eq!(thai.epoch_year(), -543); // 544 BCE
241 assert_eq!(minguo.epoch_year(), 1912);
242 assert_eq!(holocene.epoch_year(), -9999); // 10,000 BCE
243
244 println!("✅ Year conversions work correctly between calendars");
245 Ok(())
246 }));
247
248 res!(test_it(filter, &["calendar_info_methods", "all", "calendar", "comprehensive"], || {
249 // Test informational methods
250
251 let calendars = Calendar::all().collect::<Vec<_>>();
252 assert_eq!(calendars.len(), 8);
253
254 for calendar in calendars {
255 // Each calendar should have non-empty name, id, and description
256 assert!(!calendar.name().is_empty());
257 assert!(!calendar.id().is_empty());
258 assert!(!calendar.description().is_empty());
259
260 // Test display
261 let display_string = format!("{}", calendar);
262 assert_eq!(display_string, calendar.name());
263 }
264
265 // Test specific descriptions
266 assert!(Calendar::Gregorian.description().contains("1582"));
267 assert!(Calendar::Islamic.description().contains("Lunar"));
268 assert!(Calendar::Japanese.description().contains("era"));
269 assert!(Calendar::Thai.description().contains("Buddhist"));
270 assert!(Calendar::Minguo.description().contains("Republic"));
271 assert!(Calendar::Holocene.description().contains("Scientific"));
272
273 println!("✅ Calendar information methods work correctly");
274 Ok(())
275 }));
276
277 res!(test_it(filter, &["calendar_conversion_matrix", "all", "calendar", "comprehensive"], || {
278 // Test comprehensive conversion matrix between all calendar systems
279 let zone = CalClockZone::utc();
280
281 // Test data: same date represented in different calendar systems
282 // Note: Islamic and Japanese calendars are excluded from comprehensive testing
283 // as they need more sophisticated conversion algorithms
284 let test_cases: Vec<(Calendar, i32, u8, u8)> = vec![
285 (Calendar::Gregorian, 2024, 1, 15),
286 (Calendar::Thai, 2567, 1, 15), // Thai 2567 = Gregorian 2024
287 (Calendar::Minguo, 113, 1, 15), // Minguo 113 = Gregorian 2024
288 (Calendar::Holocene, 12024, 1, 15), // Holocene 12024 = Gregorian 2024
289 (Calendar::Julian, 2024, 1, 15), // Julian and Gregorian coincide for modern dates
290 ];
291
292 // Test conversions between supported calendars (excluding Islamic and Japanese for now)
293 let supported_calendars = vec![
294 Calendar::Gregorian,
295 Calendar::Julian,
296 Calendar::Thai,
297 Calendar::Minguo,
298 Calendar::Holocene,
299 ];
300
301 for (source_calendar, year, month, day) in &test_cases {
302 let source_date = res!(source_calendar.date(*year, *month, *day, zone.clone()));
303
304 for target_calendar in &supported_calendars {
305 if source_calendar == target_calendar {
306 continue; // Skip self-conversion
307 }
308
309 // Convert to target calendar
310 let converted_date = res!(source_calendar.convert_date(&source_date, target_calendar));
311
312 // Convert back to source calendar
313 let round_trip_date = res!(target_calendar.convert_date(&converted_date, source_calendar));
314
315 // Round-trip should return to original date (allowing for precision differences)
316 assert_eq!(
317 round_trip_date.year(), source_date.year(),
318 "Round-trip conversion failed: {} -> {} -> {} (year mismatch)",
319 source_calendar.name(), target_calendar.name(), source_calendar.name()
320 );
321 assert_eq!(
322 round_trip_date.month(), source_date.month(),
323 "Round-trip conversion failed: {} -> {} -> {} (month mismatch)",
324 source_calendar.name(), target_calendar.name(), source_calendar.name()
325 );
326 assert_eq!(
327 round_trip_date.day(), source_date.day(),
328 "Round-trip conversion failed: {} -> {} -> {} (day mismatch)",
329 source_calendar.name(), target_calendar.name(), source_calendar.name()
330 );
331 }
332 }
333
334 // Test specific Islamic calendar conversions (more complex due to lunar nature)
335 let islamic = Calendar::Islamic;
336 let gregorian = Calendar::Gregorian;
337
338 let islamic_date = res!(islamic.date(1445, 7, 15, zone.clone())); // Islamic date
339 let gregorian_equivalent = res!(islamic.convert_date(&islamic_date, &gregorian));
340 let back_to_islamic = res!(gregorian.convert_date(&gregorian_equivalent, &islamic));
341
342 // Islamic conversions may have slight variations due to lunar vs solar calendar differences
343 // Just ensure the conversion is reasonable (should be in early 2024)
344 assert!(gregorian_equivalent.year() >= 2023 && gregorian_equivalent.year() <= 2025,
345 "Islamic to Gregorian conversion should yield date around 2024, got {}",
346 gregorian_equivalent.year());
347
348 println!("✅ Calendar conversion matrix tested successfully");
349 Ok(())
350 }));
351
352 res!(test_it(filter, &["calendar_practical_examples", "all", "calendar", "comprehensive"], || {
353 // Test practical usage examples
354 let zone = CalClockZone::utc();
355
356 // Example 1: Creating dates in different calendar systems
357 let gregorian = Calendar::new(); // Default to Gregorian
358 let date_2024 = res!(gregorian.date(2024, 1, 15, zone.clone()));
359
360 // Same date in different calendars
361 let thai = Calendar::Thai;
362 let thai_date = res!(thai.date(2567, 1, 15, zone.clone())); // Thai 2567 = Gregorian 2024
363
364 let holocene = Calendar::Holocene;
365 let holocene_date = res!(holocene.date(12024, 1, 15, zone.clone())); // Holocene 12024 = Gregorian 2024
366
367 // All should represent the same date internally
368 assert_eq!(date_2024.year(), thai_date.year());
369 assert_eq!(date_2024.year(), holocene_date.year());
370 assert_eq!(date_2024.month(), thai_date.month());
371 assert_eq!(date_2024.month(), holocene_date.month());
372 assert_eq!(date_2024.day(), thai_date.day());
373 assert_eq!(date_2024.day(), holocene_date.day());
374
375 // Example 2: Working with Islamic calendar
376 let islamic = Calendar::Islamic;
377 let _islamic_date = res!(islamic.date(1445, 7, 15, zone.clone()));
378
379 // Islamic calendar should have different month structure
380 assert_eq!(res!(islamic.days_in_month(1445, 7)), 30); // 7th month (Rajab) has 30 days
381 assert_eq!(res!(islamic.days_in_month(1445, 8)), 29); // 8th month (Sha'ban) has 29 days
382
383 // Example 3: Leap year differences
384 let julian = Calendar::Julian;
385
386 // Year 1900: leap in Julian, not in Gregorian
387 assert!(julian.is_leap_year(1900));
388 assert!(!gregorian.is_leap_year(1900));
389
390 // This affects February days
391 assert_eq!(res!(julian.days_in_month(1900, 2)), 29);
392 assert_eq!(res!(gregorian.days_in_month(1900, 2)), 28);
393
394 println!("✅ Practical calendar examples work correctly");
395 Ok(())
396 }));
397
398 // Additional comprehensive tests merged from calendar.rs
399
400 res!(test_it(filter, &["day_of_week", "all", "calendar", "comprehensive"], || {
401 let zone = CalClockZone::utc();
402 let gregorian = Calendar::Gregorian;
403
404 // Some known dates
405 let date1 = res!(gregorian.date(2024, 1, 1, zone.clone())); // Monday
406 assert_eq!(date1.day_of_week(), DayOfWeek::Monday);
407
408 let date2 = res!(gregorian.date(2024, 3, 15, zone.clone())); // Friday
409 assert_eq!(date2.day_of_week(), DayOfWeek::Friday);
410
411 let date3 = res!(gregorian.date(2000, 1, 1, zone.clone())); // Saturday
412 assert_eq!(date3.day_of_week(), DayOfWeek::Saturday);
413
414 // Dates whose Zeller numerator is negative, which a plain % turned
415 // into an unreachable panic. Weekdays confirmed against `date -d`.
416 let date4 = res!(gregorian.date(2004, 3, 15, zone.clone())); // Monday
417 assert_eq!(date4.day_of_week(), DayOfWeek::Monday);
418
419 let date5 = res!(gregorian.date(2000, 5, 1, zone.clone())); // Monday
420 assert_eq!(date5.day_of_week(), DayOfWeek::Monday);
421
422 let date6 = res!(gregorian.date(1999, 8, 14, zone.clone())); // Saturday
423 assert_eq!(date6.day_of_week(), DayOfWeek::Saturday);
424
425 println!("✅ Day of week calculations work correctly");
426 Ok(())
427 }));
428
429 res!(test_it(filter, &["date_arithmetic", "all", "calendar", "comprehensive"], || {
430 let zone = CalClockZone::utc();
431 let gregorian = Calendar::Gregorian;
432
433 // Test adding days
434 let date1 = res!(gregorian.date(2024, 1, 31, zone.clone()));
435 let date2 = res!(date1.add_days(1));
436 assert_eq!(date2.year(), 2024);
437 assert_eq!(date2.month(), 2);
438 assert_eq!(date2.day(), 1);
439
440 // Test adding months with day adjustment
441 let date3 = res!(date1.plus(0, 1, 0)); // Jan 31 + 1 month = Feb 29 (leap year)
442 assert_eq!(date3.year(), 2024);
443 assert_eq!(date3.month(), 2);
444 assert_eq!(date3.day(), 29);
445
446 // Test subtracting
447 let date4 = res!(gregorian.date(2024, 3, 1, zone.clone()));
448 let date5 = res!(date4.minus(0, 0, 1));
449 assert_eq!(date5.year(), 2024);
450 assert_eq!(date5.month(), 2);
451 assert_eq!(date5.day(), 29); // Leap year
452
453 println!("✅ Date arithmetic operations work correctly");
454 Ok(())
455 }));
456
457 res!(test_it(filter, &["date_comparison", "all", "calendar", "comprehensive"], || {
458 let zone = CalClockZone::utc();
459 let gregorian = Calendar::Gregorian;
460
461 let date1 = res!(gregorian.date(2024, 1, 15, zone.clone()));
462 let date2 = res!(gregorian.date(2024, 1, 16, zone.clone()));
463 let date3 = res!(gregorian.date(2024, 1, 15, zone.clone()));
464
465 assert!(date1.is_before(&date2));
466 assert!(!date2.is_before(&date1));
467 assert!(!date1.is_before(&date3));
468
469 assert!(date2.is_after(&date1));
470 assert!(!date1.is_after(&date2));
471 assert!(!date1.is_after(&date3));
472
473 assert_eq!(date1, date3);
474 assert_ne!(date1, date2);
475
476 println!("✅ Date comparison operations work correctly");
477 Ok(())
478 }));
479
480 res!(test_it(filter, &["day_of_year", "all", "calendar", "comprehensive"], || {
481 let zone = CalClockZone::utc();
482 let gregorian = Calendar::Gregorian;
483
484 let date1 = res!(gregorian.date(2024, 1, 1, zone.clone()));
485 assert_eq!(res!(date1.day_of_year()), 1);
486
487 let date2 = res!(gregorian.date(2024, 12, 31, zone.clone()));
488 assert_eq!(res!(date2.day_of_year()), 366); // Leap year
489
490 let date3 = res!(gregorian.date(2023, 12, 31, zone.clone()));
491 assert_eq!(res!(date3.day_of_year()), 365); // Non-leap year
492
493 let date4 = res!(gregorian.date(2024, 3, 1, zone.clone()));
494 assert_eq!(res!(date4.day_of_year()), 61); // 31 (Jan) + 29 (Feb) + 1
495
496 println!("✅ Day of year calculations work correctly");
497 Ok(())
498 }));
499
500 Ok(())
501}