Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/src/calendar/date.rs

31.8 KiB, 133 runs

created by r1870400018:6291, 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 crate::{
5 calendar::{
6 CalendarDay,
7 CalendarDuration,
8 CalendarInterval,
9 CalendarMonth,
10 CalendarYear,
11 MonthPeriod,
12 system::CalendarSystem,
13 },
14 constant::{
15 DayOfWeek,
16 MonthOfYear,
17 },
18 core::Time,
19 parser::Parser,
20 time::CalClockZone,
21};
22
23use oxedyne_fe2o3_core::prelude::*;
24
25use std::{
26 cmp::Ordering,
27 fmt::{self, Display},
28};
29
30#[derive(Clone, Debug, Eq, Hash, PartialEq)]
31pub struct CalendarDate {
32 year: i32,
33 month: MonthOfYear,
34 day: u8,
35 zone: CalClockZone,
36 calendar: CalendarSystem,
37}
38
39impl CalendarDate {
40 pub fn new(year: i32, month: u8, day: u8, zone: CalClockZone) -> Outcome<Self> {
41 Self::new_with_system(year, month, day, zone, CalendarSystem::default())
42 }
43
44 pub fn new_with_system(year: i32, month: u8, day: u8, zone: CalClockZone, calendar: CalendarSystem) -> Outcome<Self> {
45 let month = res!(MonthOfYear::from_number(month));
46
47 // Validate the date using the calendar system
48 res!(calendar.validate_date(year, month, day));
49
50 Ok(Self {
51 year,
52 month,
53 day,
54 zone,
55 calendar,
56 })
57 }
58
59 pub fn from_ymd(year: i32, month: MonthOfYear, day: u8, zone: CalClockZone) -> Outcome<Self> {
60 Self::from_ymd_with_system(year, month, day, zone, CalendarSystem::default())
61 }
62
63 pub fn from_ymd_with_system(year: i32, month: MonthOfYear, day: u8, zone: CalClockZone, calendar: CalendarSystem) -> Outcome<Self> {
64 res!(calendar.validate_date(year, month, day));
65
66 Ok(Self {
67 year,
68 month,
69 day,
70 zone,
71 calendar,
72 })
73 }
74
75 pub fn today(zone: CalClockZone) -> Outcome<Self> {
76 use std::time::{SystemTime, UNIX_EPOCH};
77
78 let now = SystemTime::now();
79 let duration = res!(now.duration_since(UNIX_EPOCH)
80 .map_err(|_| err!("System time is before Unix epoch"; System)));
81
82 let days_since_epoch = duration.as_secs() / (24 * 60 * 60);
83 Self::from_days_since_epoch(days_since_epoch as i64, zone)
84 }
85
86 pub fn parse(input: &str, zone: CalClockZone) -> Outcome<Self> {
87 Parser::parse_date(input, zone)
88 }
89
90 #[allow(dead_code)]
91 fn validate(year: i32, month: MonthOfYear, day: u8) -> Outcome<()> {
92 // Validate year range (somewhat arbitrary but reasonable)
93 if year < -9999 || year > 9999 {
94 return Err(err!(
95 "Year {} is out of range (-9999 to 9999)", year;
96 Invalid, Input, Range));
97 }
98
99 // Validate day
100 let days_in_month = month.days_in_month(year);
101 if day == 0 || day > days_in_month {
102 return Err(err!(
103 "Day {} is invalid for {}/{} (must be 1-{})",
104 day, month.of(), year, days_in_month;
105 Invalid, Input, Range));
106 }
107
108 Ok(())
109 }
110
111 // Getters
112 pub fn year(&self) -> i32 { self.year }
113 pub fn month(&self) -> u8 { self.month.of() }
114 pub fn month_of_year(&self) -> MonthOfYear { self.month }
115 pub fn day(&self) -> u8 { self.day }
116 pub fn zone(&self) -> &CalClockZone { &self.zone }
117 pub fn calendar_system(&self) -> &CalendarSystem { &self.calendar }
118
119 pub fn day_of_week(&self) -> DayOfWeek {
120 // Using Zeller's congruence algorithm
121 let mut year = self.year;
122 let mut month = self.month.of() as i32;
123 let day = self.day as i32;
124
125 // Adjust for Zeller's algorithm (Jan and Feb are months 13 and 14 of previous year)
126 if month <= 2 {
127 month += 12;
128 year -= 1;
129 }
130
131 let century = year / 100;
132 let year_of_century = year % 100;
133
134 // rem_euclid, not %: the numerator is negative for many ordinary dates
135 // (e.g. 2004-03-15), and a negative remainder would fall through every
136 // match arm below.
137 let h = (day + (13 * (month + 1)) / 5 + year_of_century + year_of_century / 4 + century / 4 - 2 * century).rem_euclid(7);
138
139 // Convert from Zeller's output (0 = Saturday) to our enum (1 = Monday)
140 match h {
141 0 => DayOfWeek::Saturday,
142 1 => DayOfWeek::Sunday,
143 2 => DayOfWeek::Monday,
144 3 => DayOfWeek::Tuesday,
145 4 => DayOfWeek::Wednesday,
146 5 => DayOfWeek::Thursday,
147 6 => DayOfWeek::Friday,
148 _ => unreachable!(),
149 }
150 }
151
152 pub fn is_leap_year(&self) -> bool {
153 self.calendar.is_leap_year(self.year)
154 }
155
156 pub fn day_of_year(&self) -> Outcome<u16> {
157 let mut days = self.day as u16;
158
159 for month_num in 1..self.month.of() {
160 let month = res!(MonthOfYear::from_number(month_num));
161 days += self.calendar.days_in_month(self.year, month) as u16;
162 }
163
164 Ok(days)
165 }
166
167 /// ISO 8601 week numbering, 1 to 53.
168 pub fn week_of_year(&self) -> Outcome<u8> {
169 // ISO 8601 week numbering - weeks start on Monday
170 // Week 1 is the first week with at least 4 days in the new year
171
172 // Get January 1st of this year
173 let jan1 = res!(Self::new(self.year, 1, 1, self.zone.clone()));
174 let jan1_dow = jan1.day_of_week() as u8; // Monday = 1, Sunday = 7
175
176 // Calculate the day of year
177 let day_of_year = res!(self.day_of_year());
178
179 // ISO week calculation
180 let jan1_iso_dow = if jan1_dow == 7 { 7 } else { jan1_dow }; // Sunday = 7 in ISO
181
182 // Days before the first Monday of the year
183 let days_before_first_monday = if jan1_iso_dow == 1 { 0u16 } else { (8 - jan1_iso_dow) as u16 };
184
185 // If January 1st is Friday, Saturday, or Sunday, week 1 starts in previous year
186 if jan1_iso_dow >= 5 {
187 // This date is in week 1 of next year if it's in the last few days
188 if day_of_year > (365 + if self.is_leap_year() { 1 } else { 0 } - (jan1_iso_dow - 1) as u16) {
189 return Ok(1); // Week 1 of next year
190 }
191
192 // Otherwise, calculate week number normally
193 let adjusted_day = day_of_year + (7u16 - days_before_first_monday);
194 Ok(((adjusted_day - 1) / 7 + 1) as u8)
195 } else {
196 // Week 1 starts in current year
197 if day_of_year <= days_before_first_monday {
198 // This date belongs to the last week of previous year
199 let prev_year_date = res!(Self::new(self.year - 1, 12, 31, self.zone.clone()));
200 return prev_year_date.week_of_year();
201 }
202
203 let adjusted_day = day_of_year - days_before_first_monday;
204 Ok(((adjusted_day - 1) / 7 + 1) as u8)
205 }
206 }
207
208 pub fn plus(&self, years: i32, months: i32, days: i32) -> Outcome<Self> {
209 // Add years
210 let mut new_year = self.year + years;
211
212 // Add months
213 let total_months = self.month.of() as i32 + months;
214 let new_month_num = ((total_months - 1) % 12 + 12) % 12 + 1;
215 new_year += (total_months - new_month_num) / 12;
216
217 let new_month = res!(MonthOfYear::from_number(new_month_num as u8));
218
219 // Adjust day if necessary (e.g., Jan 31 + 1 month = Feb 28/29)
220 let new_day = self.day.min(self.calendar.days_in_month(new_year, new_month));
221
222 // Create the intermediate date
223 let mut result = res!(Self::from_ymd_with_system(new_year, new_month, new_day, self.zone.clone(), self.calendar.clone()));
224
225 // Add days
226 if days != 0 {
227 result = res!(result.add_days(days));
228 }
229
230 Ok(result)
231 }
232
233 pub fn minus(&self, years: i32, months: i32, days: i32) -> Outcome<Self> {
234 self.plus(-years, -months, -days)
235 }
236
237 pub fn add_days(&self, days: i32) -> Outcome<Self> {
238 if days == 0 {
239 return Ok(self.clone());
240 }
241
242 // Use Julian day number for accurate date arithmetic
243 let julian_day = self.to_julian_day_number();
244 let new_julian_day = julian_day + days as i64;
245
246 Self::from_julian_day_number(new_julian_day, self.zone.clone())
247 }
248
249 pub fn inc(&self) -> Outcome<Self> {
250 self.add_days(1)
251 }
252
253 pub fn dec(&self) -> Outcome<Self> {
254 self.add_days(-1)
255 }
256
257 /// Days since year 0 in the proleptic Gregorian calendar.
258 pub fn to_day_number(&self) -> Outcome<i64> {
259 let mut days: i64 = 0;
260
261 // Add days for complete years
262 for y in 0..self.year {
263 days += if self.calendar.is_leap_year(y) { 366 } else { 365 };
264 }
265
266 // Subtract days for negative years
267 for y in self.year..0 {
268 days -= if self.calendar.is_leap_year(y) { 366 } else { 365 };
269 }
270
271 // Add days for complete months in this year
272 for m in 1..self.month.of() {
273 let month = res!(MonthOfYear::from_number(m));
274 days += month.days_in_month(self.year) as i64;
275 }
276
277 // Add days in this month
278 days += self.day as i64;
279
280 Ok(days)
281 }
282
283 #[allow(dead_code)]
284 fn from_day_number(mut day_number: i64, zone: CalClockZone) -> Outcome<Self> {
285 // Start from year 0 and count forward or backward
286 let mut year = 0;
287
288 // Find the year
289 if day_number > 0 {
290 while day_number > 0 {
291 let calendar = CalendarSystem::default();
292 let days_in_year = if calendar.is_leap_year(year) { 366 } else { 365 };
293 if day_number > days_in_year {
294 day_number -= days_in_year;
295 year += 1;
296 } else {
297 break;
298 }
299 }
300 } else {
301 while day_number <= 0 {
302 year -= 1;
303 let calendar = CalendarSystem::default();
304 let days_in_year = if calendar.is_leap_year(year) { 366 } else { 365 };
305 day_number += days_in_year;
306 }
307 }
308
309 // Find the month and day
310 for month_num in 1..=12 {
311 let month = res!(MonthOfYear::from_number(month_num));
312 let days_in_month = month.days_in_month(year) as i64;
313
314 if day_number <= days_in_month {
315 return Self::from_ymd(year, month, day_number as u8, zone);
316 }
317
318 day_number -= days_in_month;
319 }
320
321 Err(err!("Failed to convert day number to date"; Bug, Conversion))
322 }
323
324 pub fn minus_date(&self, other: &Self) -> Outcome<CalendarDuration> {
325 let days = self.to_julian_day_number() - other.to_julian_day_number();
326 Ok(CalendarDuration::from_days(days as i32))
327 }
328
329 pub fn to(&self, other: &Self) -> Outcome<CalendarInterval> {
330 if other.is_before(self) {
331 return Err(err!(
332 "End date {} is before start date {}",
333 other, self;
334 Invalid, Input, Order));
335 }
336 CalendarInterval::new(self.clone(), other.clone())
337 }
338
339 pub fn get_year(&self) -> CalendarYear {
340 CalendarYear::new(self.year)
341 }
342
343 pub fn get_month(&self) -> CalendarMonth {
344 CalendarMonth::new(self.month.of() as i32)
345 }
346
347 pub fn get_day(&self) -> CalendarDay {
348 CalendarDay::new(self.day as i32)
349 }
350
351 pub fn get_month_period(&self) -> Outcome<MonthPeriod> {
352 MonthPeriod::new(self.year, self.month.of(), self.zone.clone())
353 }
354
355 pub fn days_since_epoch(&self) -> Outcome<i64> {
356 // Unix epoch (1970-01-01) Julian day number is 2440588
357 let unix_epoch_julian_day = 2440588i64;
358 let this_julian_day = self.to_julian_day_number();
359
360 Ok(this_julian_day - unix_epoch_julian_day)
361 }
362
363 pub fn from_days_since_epoch(days: i64, zone: CalClockZone) -> Outcome<Self> {
364 // Unix epoch (1970-01-01) Julian day number is 2440588
365 let unix_epoch_julian_day = 2440588i64;
366 let julian_day = unix_epoch_julian_day + days;
367
368 Self::from_julian_day_number(julian_day, zone)
369 }
370
371 pub fn is_valid(&self) -> bool {
372 // Check year range
373 if self.year < -9999 || self.year > 9999 {
374 return false;
375 }
376
377 // Check day range for the month
378 let days_in_month = self.month.days_in_month(self.year);
379 self.day > 0 && self.day <= days_in_month
380 }
381
382 pub fn days_in_month(&self) -> Outcome<u8> {
383 Ok(self.month.days_in_month(self.year))
384 }
385
386 pub fn add_duration(&self, duration: &crate::time::CalClockDuration) -> Outcome<Self> {
387 // Extract days from duration and add them
388 let days = duration.days();
389 self.add_days(days)
390 }
391
392 pub fn add_calendar_duration(&self, duration: &CalendarDuration) -> Outcome<Self> {
393 self.plus(duration.years(), duration.months(), duration.days())
394 }
395
396 pub fn subtract_duration(&self, duration: &crate::time::CalClockDuration) -> Outcome<Self> {
397 let days = duration.days();
398 self.add_days(-days)
399 }
400
401 pub fn add_years(&self, years: i32) -> Outcome<Self> {
402 self.plus(years, 0, 0)
403 }
404
405 pub fn add_months(&self, months: i32) -> Outcome<Self> {
406 self.plus(0, months, 0)
407 }
408
409 // ========================================================================
410 // Advanced Calendar Arithmetic
411 // ========================================================================
412
413
414 // ========================================================================
415 // Calendar System Conversion Methods
416 // ========================================================================
417
418 pub fn to_calendar_system(&self, target_calendar: CalendarSystem) -> Outcome<Self> {
419 self.calendar.convert_to(&target_calendar, self.year, self.month, self.day, self.zone.clone())
420 }
421
422 pub fn to_gregorian(&self) -> Outcome<Self> {
423 self.to_calendar_system(CalendarSystem::Gregorian)
424 }
425
426 pub fn to_julian(&self) -> Outcome<Self> {
427 self.to_calendar_system(CalendarSystem::Julian)
428 }
429
430 pub fn is_gregorian(&self) -> bool {
431 self.calendar.is_gregorian()
432 }
433
434 pub fn is_julian(&self) -> bool {
435 self.calendar.is_julian()
436 }
437
438 pub fn is_weekday(&self) -> bool {
439 self.day_of_week().is_weekday()
440 }
441
442 pub fn is_business_day(&self) -> bool {
443 use crate::constant::DayOfWeek::*;
444 matches!(self.day_of_week(), Monday | Tuesday | Wednesday | Thursday | Friday)
445 }
446
447 pub fn is_weekend(&self) -> bool {
448 !self.is_business_day()
449 }
450
451 /// Weekends are skipped.
452 pub fn add_business_days(&self, business_days: i32) -> Outcome<Self> {
453 if business_days == 0 {
454 return Ok(self.clone());
455 }
456
457 let mut current = self.clone();
458 let mut remaining = business_days.abs();
459 let direction = if business_days > 0 { 1 } else { -1 };
460
461 while remaining > 0 {
462 current = res!(current.add_days(direction));
463 if current.is_business_day() {
464 remaining -= 1;
465 }
466 }
467
468 Ok(current)
469 }
470
471 pub fn business_days_until(&self, other: &Self) -> Outcome<i32> {
472 let start = if self <= other { self.clone() } else { other.clone() };
473 let end = if self <= other { other.clone() } else { self.clone() };
474 let sign = if self <= other { 1 } else { -1 };
475
476 let mut current = start;
477 let mut count = 0;
478
479 while current < end {
480 current = res!(current.add_days(1));
481 if current.is_business_day() {
482 count += 1;
483 }
484 }
485
486 Ok(count * sign)
487 }
488
489 pub fn next_business_day(&self) -> Outcome<Self> {
490 self.add_business_days(1)
491 }
492
493 pub fn previous_business_day(&self) -> Outcome<Self> {
494 self.add_business_days(-1)
495 }
496
497 pub fn end_of_month(&self) -> Outcome<Self> {
498 let days_in_month = self.month.days_in_month(self.year);
499 Self::new(self.year, self.month.of(), days_in_month, self.zone.clone())
500 }
501
502 pub fn start_of_month(&self) -> Outcome<Self> {
503 Self::new(self.year, self.month.of(), 1, self.zone.clone())
504 }
505
506 pub fn start_of_year(&self) -> Outcome<Self> {
507 Self::new(self.year, 1, 1, self.zone.clone())
508 }
509
510 pub fn end_of_year(&self) -> Outcome<Self> {
511 Self::new(self.year, 12, 31, self.zone.clone())
512 }
513
514 pub fn start_of_quarter(&self) -> Outcome<Self> {
515 let quarter_month = match self.month.of() {
516 1..=3 => 1, // Q1
517 4..=6 => 4, // Q2
518 7..=9 => 7, // Q3
519 10..=12 => 10, // Q4
520 _ => return Err(err!("Invalid month: {}", self.month.of(); Invalid, Input)),
521 };
522 Self::new(self.year, quarter_month, 1, self.zone.clone())
523 }
524
525 pub fn end_of_quarter(&self) -> Outcome<Self> {
526 let (quarter_month, day) = match self.month.of() {
527 1..=3 => (3, 31), // Q1 - March 31
528 4..=6 => (6, 30), // Q2 - June 30
529 7..=9 => (9, 30), // Q3 - September 30
530 10..=12 => (12, 31), // Q4 - December 31
531 _ => return Err(err!("Invalid month: {}", self.month.of(); Invalid, Input)),
532 };
533 Self::new(self.year, quarter_month, day, self.zone.clone())
534 }
535
536 pub fn quarter(&self) -> u8 {
537 match self.month.of() {
538 1..=3 => 1,
539 4..=6 => 2,
540 7..=9 => 3,
541 10..=12 => 4,
542 _ => 1, // Fallback, though this should never happen
543 }
544 }
545
546 pub fn nth_weekday_of_month(year: i32, month: u8, weekday: crate::constant::DayOfWeek, n: u8, zone: CalClockZone) -> Outcome<Self> {
547 if n == 0 || n > 5 {
548 return Err(err!("n must be between 1 and 5, got {}", n; Invalid, Input, Range));
549 }
550
551 // Start with the first day of the month
552 let first_day = res!(Self::new(year, month, 1, zone));
553 let first_weekday = first_day.day_of_week();
554
555 // Calculate days to add to get to the first occurrence of the target weekday
556 let target_weekday_num = weekday as u8;
557 let first_weekday_num = first_weekday as u8;
558
559 let days_to_first = (target_weekday_num + 7 - first_weekday_num) % 7;
560 let target_day = 1 + days_to_first + (n - 1) * 7;
561
562 // Check if this day exists in the month
563 let month_enum = res!(crate::constant::MonthOfYear::from_number(month));
564 if target_day > month_enum.days_in_month(year) {
565 return Err(err!("The {}th {} does not exist in {}/{}", n, weekday, month, year; Invalid, Input));
566 }
567
568 Self::new(year, month, target_day, first_day.zone.clone())
569 }
570
571 pub fn last_weekday_of_month(year: i32, month: u8, weekday: crate::constant::DayOfWeek, zone: CalClockZone) -> Outcome<Self> {
572 let month_enum = res!(crate::constant::MonthOfYear::from_number(month));
573 let last_day = res!(Self::new(year, month, month_enum.days_in_month(year), zone));
574 let last_weekday = last_day.day_of_week();
575
576 // Calculate days to subtract to get to the last occurrence of the target weekday
577 let target_weekday_num = weekday as u8;
578 let last_weekday_num = last_weekday as u8;
579
580 let days_to_subtract = (last_weekday_num + 7 - target_weekday_num) % 7;
581 let target_day = month_enum.days_in_month(year) - days_to_subtract;
582
583 Self::new(year, month, target_day, last_day.zone.clone())
584 }
585
586 // ========================================================================
587 // Holiday and Special Date Calculations
588 // ========================================================================
589
590 pub fn easter_sunday(year: i32, zone: CalClockZone) -> Outcome<Self> {
591 // Using the anonymous Gregorian algorithm
592 let a = year % 19;
593 let b = year / 100;
594 let c = year % 100;
595 let d = b / 4;
596 let e = b % 4;
597 let f = (b + 8) / 25;
598 let g = (b - f + 1) / 3;
599 let h = (19 * a + b - d - g + 15) % 30;
600 let i = c / 4;
601 let k = c % 4;
602 let l = (32 + 2 * e + 2 * i - h - k) % 7;
603 let m = (a + 11 * h + 22 * l) / 451;
604 let month = (h + l - 7 * m + 114) / 31;
605 let day = ((h + l - 7 * m + 114) % 31) + 1;
606
607 Self::new(year, month as u8, day as u8, zone)
608 }
609
610 pub fn good_friday(year: i32, zone: CalClockZone) -> Outcome<Self> {
611 let easter = res!(Self::easter_sunday(year, zone));
612 easter.add_days(-2)
613 }
614
615 pub fn easter_monday(year: i32, zone: CalClockZone) -> Outcome<Self> {
616 let easter = res!(Self::easter_sunday(year, zone));
617 easter.add_days(1)
618 }
619
620 pub fn mothers_day_us(year: i32, zone: CalClockZone) -> Outcome<Self> {
621 Self::nth_weekday_of_month(year, 5, crate::constant::DayOfWeek::Sunday, 2, zone)
622 }
623
624 pub fn fathers_day_us(year: i32, zone: CalClockZone) -> Outcome<Self> {
625 Self::nth_weekday_of_month(year, 6, crate::constant::DayOfWeek::Sunday, 3, zone)
626 }
627
628 pub fn thanksgiving_us(year: i32, zone: CalClockZone) -> Outcome<Self> {
629 Self::nth_weekday_of_month(year, 11, crate::constant::DayOfWeek::Thursday, 4, zone)
630 }
631
632 pub fn memorial_day_us(year: i32, zone: CalClockZone) -> Outcome<Self> {
633 Self::last_weekday_of_month(year, 5, crate::constant::DayOfWeek::Monday, zone)
634 }
635
636 pub fn labor_day_us(year: i32, zone: CalClockZone) -> Outcome<Self> {
637 Self::nth_weekday_of_month(year, 9, crate::constant::DayOfWeek::Monday, 1, zone)
638 }
639
640 // ========================================================================
641 // Advanced Date Range Operations
642 // ========================================================================
643
644 /// Inclusive of both bounds.
645 pub fn is_between(&self, start: &Self, end: &Self) -> bool {
646 self >= start && self <= end
647 }
648
649 pub fn years_until(&self, other: &Self) -> i32 {
650 let mut years = other.year - self.year;
651
652 // Adjust if we haven't reached the anniversary yet
653 if other.month.of() < self.month.of() ||
654 (other.month.of() == self.month.of() && other.day < self.day) {
655 years -= 1;
656 }
657
658 years
659 }
660
661 pub fn months_until(&self, other: &Self) -> i32 {
662 let mut months = (other.year - self.year) * 12 + (other.month.of() as i32 - self.month.of() as i32);
663
664 // Adjust if we haven't reached the anniversary day yet
665 if other.day < self.day {
666 months -= 1;
667 }
668
669 months
670 }
671
672 pub fn age_years(&self, birth_date: &Self) -> Outcome<u32> {
673 if birth_date > self {
674 return Err(err!("Birth date cannot be after current date"; Invalid, Input));
675 }
676
677 let years = self.years_until(birth_date).abs() as u32;
678 Ok(years)
679 }
680
681 /// The Java calclock toJulianDayNumber4 algorithm, from
682 /// http://pmyers.pcug.org.au/General/JulianDates.htm
683 pub fn to_julian_day_number(&self) -> i64 {
684 let mut year = self.year as i64;
685 let mut month = self.month.of() as i64;
686 let day = self.day as i64;
687
688 if month > 2 {
689 month -= 3;
690 } else {
691 month += 9;
692 year -= 1;
693 }
694
695 let c = year / 100;
696 let ya = year - 100 * c;
697
698 (146097 * c) / 4
699 + (1461 * ya) / 4
700 + (153 * month + 2) / 5
701 + day
702 + 1721119
703 }
704
705 /// The inverse, from the same source.
706 pub fn from_julian_day_number(mut julian_day: i64, zone: CalClockZone) -> Outcome<Self> {
707 julian_day -= 1721119;
708 let y = (4 * julian_day - 1) / 146097;
709 julian_day = 4 * julian_day - 1 - 146097 * y;
710 let mut d = julian_day / 4;
711 julian_day = (4 * d + 3) / 1461;
712 d = 4 * d + 3 - 1461 * julian_day;
713 d = (d + 4) / 4;
714 let mut m = (5 * d - 3) / 153;
715 d = 5 * d - 3 - 153 * m;
716 d = (d + 5) / 5;
717 let mut year = 100 * y + julian_day;
718
719 if m < 10 {
720 m += 3;
721 } else {
722 m -= 9;
723 year += 1;
724 }
725
726 Self::new(year as i32, m as u8, d as u8, zone)
727 }
728
729 // ========================================================================
730 // String Formatting Implementation (ported from Java original)
731 // ========================================================================
732
733 /// The stencil characters, from the Java implementation:
734 /// - y yy yyy yyyy yyyyy Year 8 08 008 2008 02008
735 /// - M MM MMM MMMM Month 3 03 Mar March
736 /// - d dd ddd dddd Day 9 09 Sun Sunday
737 /// - z zz zzz zzzz Timezone +1 +01 +01:00 UTC
738 pub fn format_with_stencil(&self, stencil: &str) -> String {
739 let mut output = String::new();
740 let chars: Vec<char> = stencil.chars().collect();
741 let mut i = 0;
742
743 while i < chars.len() {
744 let curr = chars[i];
745
746 if self.is_recognised_format_char(curr) {
747 // Count consecutive occurrences of the same format character
748 let mut token_len = 1;
749 while i + token_len < chars.len() && chars[i + token_len] == curr {
750 token_len += 1;
751 }
752
753 // Process the token
754 self.switch_on_format_char(curr, token_len, &mut output);
755 i += token_len;
756 } else {
757 // Regular character, append as-is
758 output.push(curr);
759 i += 1;
760 }
761 }
762
763 output
764 }
765
766 fn switch_on_format_char(&self, c: char, n: usize, sb: &mut String) {
767 match c {
768 'y' => self.format_year(n, sb, self.year),
769 'M' => self.format_month(n, sb, self.month.of() as i32),
770 'd' => {
771 if n < 3 {
772 self.format_day(n, sb, self.day as i32);
773 } else {
774 self.format_day_of_week(n, sb, self.day_of_week() as i32);
775 }
776 },
777 'z' => self.format_timezone(n, sb),
778 _ => {}
779 }
780 }
781
782 fn format_year(&self, n: usize, sb: &mut String, val: i32) {
783 let val_str = val.to_string();
784 let val_len = val_str.len();
785
786 if val_len > n {
787 // Restrict to last n digits
788 let start = val_len - n;
789 sb.push_str(&val_str[start..]);
790 } else {
791 // Zero-pad to n digits
792 sb.push_str(&fmt!("{:0width$}", val, width = n));
793 }
794 }
795
796 fn format_month(&self, n: usize, sb: &mut String, val: i32) {
797 match n {
798 1 => sb.push_str(&val.to_string()),
799 2 => sb.push_str(&fmt!("{:02}", val)),
800 3 => sb.push_str(self.month.to_short_name()),
801 _ => sb.push_str(self.month.to_long_name()),
802 }
803 }
804
805 fn format_day(&self, n: usize, sb: &mut String, val: i32) {
806 match n {
807 1 => sb.push_str(&val.to_string()),
808 _ => sb.push_str(&fmt!("{:02}", val)),
809 }
810 }
811
812 fn format_day_of_week(&self, n: usize, sb: &mut String, _val: i32) {
813 let day_of_week = self.day_of_week();
814 match n {
815 3 => sb.push_str(day_of_week.to_short_name()),
816 _ => sb.push_str(day_of_week.to_long_name()),
817 }
818 }
819
820 fn format_timezone(&self, n: usize, sb: &mut String) {
821 match n {
822 1 => sb.push_str(self.zone.id()), // Simple ID
823 2 => sb.push_str(self.zone.id()), // Short format
824 3 => sb.push_str(self.zone.id()), // Medium format
825 _ => sb.push_str(self.zone.id()), // Long format
826 }
827 }
828}
829
830// Add to constant modules - these need implementations
831impl MonthOfYear {
832 pub fn to_short_name(&self) -> &'static str {
833 match self {
834 MonthOfYear::January => "Jan",
835 MonthOfYear::February => "Feb",
836 MonthOfYear::March => "Mar",
837 MonthOfYear::April => "Apr",
838 MonthOfYear::May => "May",
839 MonthOfYear::June => "Jun",
840 MonthOfYear::July => "Jul",
841 MonthOfYear::August => "Aug",
842 MonthOfYear::September => "Sep",
843 MonthOfYear::October => "Oct",
844 MonthOfYear::November => "Nov",
845 MonthOfYear::December => "Dec",
846 }
847 }
848
849 pub fn to_long_name(&self) -> &'static str {
850 match self {
851 MonthOfYear::January => "January",
852 MonthOfYear::February => "February",
853 MonthOfYear::March => "March",
854 MonthOfYear::April => "April",
855 MonthOfYear::May => "May",
856 MonthOfYear::June => "June",
857 MonthOfYear::July => "July",
858 MonthOfYear::August => "August",
859 MonthOfYear::September => "September",
860 MonthOfYear::October => "October",
861 MonthOfYear::November => "November",
862 MonthOfYear::December => "December",
863 }
864 }
865}
866
867impl DayOfWeek {
868 pub fn to_short_name(&self) -> &'static str {
869 match self {
870 DayOfWeek::Monday => "Mon",
871 DayOfWeek::Tuesday => "Tue",
872 DayOfWeek::Wednesday => "Wed",
873 DayOfWeek::Thursday => "Thu",
874 DayOfWeek::Friday => "Fri",
875 DayOfWeek::Saturday => "Sat",
876 DayOfWeek::Sunday => "Sun",
877 }
878 }
879
880 pub fn to_long_name(&self) -> &'static str {
881 match self {
882 DayOfWeek::Monday => "Monday",
883 DayOfWeek::Tuesday => "Tuesday",
884 DayOfWeek::Wednesday => "Wednesday",
885 DayOfWeek::Thursday => "Thursday",
886 DayOfWeek::Friday => "Friday",
887 DayOfWeek::Saturday => "Saturday",
888 DayOfWeek::Sunday => "Sunday",
889 }
890 }
891}
892
893impl Time for CalendarDate {
894 fn get_zone(&self) -> &CalClockZone {
895 &self.zone
896 }
897
898 fn to_zone(&self, new_zone: CalClockZone) -> Outcome<Self> {
899 Ok(Self {
900 zone: new_zone,
901 ..self.clone()
902 })
903 }
904
905 fn format(&self, stencil: &str) -> String {
906 self.format_with_stencil(stencil)
907 }
908
909 fn is_recognised_format_char(&self, c: char) -> bool {
910 matches!(c, 'y' | 'M' | 'd' | 'E' | 'z')
911 }
912
913 fn is_before(&self, other: &Self) -> bool {
914 self.cmp(other) == Ordering::Less
915 }
916
917 fn is_after(&self, other: &Self) -> bool {
918 self.cmp(other) == Ordering::Greater
919 }
920
921 fn or_earlier(&self, other: &Self) -> Self {
922 if self.is_before(other) { self.clone() } else { other.clone() }
923 }
924
925 fn or_later(&self, other: &Self) -> Self {
926 if self.is_after(other) { self.clone() } else { other.clone() }
927 }
928}
929
930impl Ord for CalendarDate {
931 fn cmp(&self, other: &Self) -> Ordering {
932 match self.year.cmp(&other.year) {
933 Ordering::Equal => match self.month.cmp(&other.month) {
934 Ordering::Equal => self.day.cmp(&other.day),
935 other => other,
936 },
937 other => other,
938 }
939 }
940}
941
942impl PartialOrd for CalendarDate {
943 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
944 Some(self.cmp(other))
945 }
946}
947
948impl Display for CalendarDate {
949 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
950 if self.calendar.is_gregorian() {
951 write!(f, "{:04}-{:02}-{:02}", self.year, self.month.of(), self.day)
952 } else {
953 write!(f, "{:04}-{:02}-{:02} ({})", self.year, self.month.of(), self.day, self.calendar)
954 }
955 }
956}