1use super::traits::{RenderContext, View, WidgetProps};
7use crate::render::{Cell, Modifier};
8use crate::style::Color;
9use crate::utils::border::render_border;
10use crate::{impl_props_builders, impl_styled_view};
11
12pub fn days_in_month(year: i32, month: u32) -> u32 {
14 match month {
15 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
16 4 | 6 | 9 | 11 => 30,
17 2 => {
18 if is_leap_year(year) {
19 29
20 } else {
21 28
22 }
23 }
24 _ => 0,
25 }
26}
27
28fn is_leap_year(year: i32) -> bool {
30 (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
31}
32
33fn first_day_of_month(year: i32, month: u32) -> u32 {
36 let m = if month < 3 {
37 month as i32 + 12
38 } else {
39 month as i32
40 };
41 let y = if month < 3 { year - 1 } else { year };
42 let q = 1i32; let k = y % 100;
44 let j = y / 100;
45
46 let h = (q + (13 * (m + 1)) / 5 + k + k / 4 + j / 4 - 2 * j) % 7;
47 let h = ((h + 6) % 7 + 7) % 7;
50 h as u32
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
55pub struct Date {
56 pub year: i32,
58 pub month: u32,
60 pub day: u32,
62}
63
64impl Date {
65 pub fn new(year: i32, month: u32, day: u32) -> Self {
67 Self { year, month, day }
68 }
69
70 pub fn today() -> Self {
72 Self::new(2025, 1, 1)
74 }
75
76 pub fn is_valid(&self) -> bool {
78 self.month >= 1
79 && self.month <= 12
80 && self.day >= 1
81 && self.day <= days_in_month(self.year, self.month)
82 }
83
84 pub fn weekday(&self) -> u32 {
86 let first = first_day_of_month(self.year, self.month);
87 (first + self.day - 1) % 7
88 }
89
90 pub fn prev_day(&self) -> Self {
92 if self.day > 1 {
93 Self::new(self.year, self.month, self.day - 1)
94 } else if self.month > 1 {
95 let prev_month = self.month - 1;
96 let days = days_in_month(self.year, prev_month);
97 Self::new(self.year, prev_month, days)
98 } else {
99 Self::new(self.year - 1, 12, 31)
100 }
101 }
102
103 pub fn next_day(&self) -> Self {
105 let days = days_in_month(self.year, self.month);
106 if self.day < days {
107 Self::new(self.year, self.month, self.day + 1)
108 } else if self.month < 12 {
109 Self::new(self.year, self.month + 1, 1)
110 } else {
111 Self::new(self.year + 1, 1, 1)
112 }
113 }
114
115 pub fn subtract_days(&self, n: u32) -> Self {
117 let mut result = *self;
118 for _ in 0..n {
119 result = result.prev_day();
120 }
121 result
122 }
123
124 pub fn add_days(&self, n: u32) -> Self {
126 let mut result = *self;
127 for _ in 0..n {
128 result = result.next_day();
129 }
130 result
131 }
132}
133
134impl Default for Date {
135 fn default() -> Self {
136 Self::new(2025, 1, 1)
137 }
138}
139
140#[derive(Clone, Debug)]
142pub struct DateMarker {
143 pub date: Date,
145 pub color: Color,
147 pub symbol: Option<char>,
149}
150
151impl DateMarker {
152 pub fn new(date: Date, color: Color) -> Self {
154 Self {
155 date,
156 color,
157 symbol: None,
158 }
159 }
160
161 pub fn symbol(mut self, symbol: char) -> Self {
163 self.symbol = Some(symbol);
164 self
165 }
166}
167
168#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
170pub enum CalendarMode {
171 #[default]
173 Month,
174 Year,
176 Week,
178}
179
180#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
182pub enum FirstDayOfWeek {
183 #[default]
185 Sunday,
186 Monday,
188}
189
190pub struct Calendar {
192 year: i32,
194 month: u32,
196 selected: Option<Date>,
198 range_end: Option<Date>,
200 mode: CalendarMode,
202 first_day: FirstDayOfWeek,
204 show_week_numbers: bool,
206 markers: Vec<DateMarker>,
208 today: Option<Date>,
210 header_fg: Color,
212 header_bg: Option<Color>,
213 day_fg: Color,
214 weekend_fg: Color,
215 selected_fg: Color,
216 selected_bg: Color,
217 today_fg: Color,
218 outside_fg: Color,
219 border_color: Option<Color>,
220 focused: bool,
222 props: WidgetProps,
224}
225
226impl Calendar {
227 pub fn new(year: i32, month: u32) -> Self {
229 Self {
230 year,
231 month: month.clamp(1, 12),
232 selected: None,
233 range_end: None,
234 mode: CalendarMode::Month,
235 first_day: FirstDayOfWeek::Sunday,
236 show_week_numbers: false,
237 markers: Vec::new(),
238 today: None,
239 header_fg: Color::CYAN,
240 header_bg: None,
241 day_fg: Color::WHITE,
242 weekend_fg: Color::rgb(150, 150, 150),
243 selected_fg: Color::BLACK,
244 selected_bg: Color::CYAN,
245 today_fg: Color::YELLOW,
246 outside_fg: Color::rgb(80, 80, 80),
247 border_color: None,
248 focused: false,
249 props: WidgetProps::new(),
250 }
251 }
252
253 pub fn selected(mut self, date: Date) -> Self {
255 self.selected = Some(date);
256 self.year = date.year;
257 self.month = date.month;
258 self
259 }
260
261 pub fn range(mut self, start: Date, end: Date) -> Self {
263 self.selected = Some(start);
264 self.range_end = Some(end);
265 self
266 }
267
268 pub fn mode(mut self, mode: CalendarMode) -> Self {
270 self.mode = mode;
271 self
272 }
273
274 pub fn first_day(mut self, first: FirstDayOfWeek) -> Self {
276 self.first_day = first;
277 self
278 }
279
280 pub fn week_numbers(mut self, show: bool) -> Self {
282 self.show_week_numbers = show;
283 self
284 }
285
286 pub fn marker(mut self, marker: DateMarker) -> Self {
288 self.markers.push(marker);
289 self
290 }
291
292 pub fn markers(mut self, markers: Vec<DateMarker>) -> Self {
294 self.markers.extend(markers);
295 self
296 }
297
298 pub fn today(mut self, date: Date) -> Self {
300 self.today = Some(date);
301 self
302 }
303
304 pub fn header_color(mut self, fg: Color) -> Self {
306 self.header_fg = fg;
307 self
308 }
309
310 pub fn header_bg(mut self, bg: Color) -> Self {
312 self.header_bg = Some(bg);
313 self
314 }
315
316 pub fn day_color(mut self, fg: Color) -> Self {
318 self.day_fg = fg;
319 self
320 }
321
322 pub fn weekend_color(mut self, fg: Color) -> Self {
324 self.weekend_fg = fg;
325 self
326 }
327
328 pub fn selected_color(mut self, fg: Color, bg: Color) -> Self {
330 self.selected_fg = fg;
331 self.selected_bg = bg;
332 self
333 }
334
335 pub fn today_color(mut self, fg: Color) -> Self {
337 self.today_fg = fg;
338 self
339 }
340
341 pub fn border(mut self, color: Color) -> Self {
343 self.border_color = Some(color);
344 self
345 }
346
347 pub fn focused(mut self, focused: bool) -> Self {
349 self.focused = focused;
350 self
351 }
352
353 pub fn prev_month(&mut self) {
355 if self.month == 1 {
356 self.month = 12;
357 self.year -= 1;
358 } else {
359 self.month -= 1;
360 }
361 }
362
363 pub fn next_month(&mut self) {
365 if self.month == 12 {
366 self.month = 1;
367 self.year += 1;
368 } else {
369 self.month += 1;
370 }
371 }
372
373 pub fn prev_year(&mut self) {
375 self.year -= 1;
376 }
377
378 pub fn next_year(&mut self) {
380 self.year += 1;
381 }
382
383 pub fn select(&mut self, date: Date) {
385 self.selected = Some(date);
386 }
387
388 pub fn clear_selection(&mut self) {
390 self.selected = None;
391 self.range_end = None;
392 }
393
394 pub fn get_selected(&self) -> Option<Date> {
396 self.selected
397 }
398
399 pub fn select_next_day(&mut self) {
401 if let Some(date) = self.selected {
402 let days = days_in_month(date.year, date.month);
403 if date.day < days {
404 self.selected = Some(Date::new(date.year, date.month, date.day + 1));
405 } else if date.month < 12 {
406 self.selected = Some(Date::new(date.year, date.month + 1, 1));
407 self.month = date.month + 1;
408 } else {
409 self.selected = Some(Date::new(date.year + 1, 1, 1));
410 self.year = date.year + 1;
411 self.month = 1;
412 }
413 } else {
414 self.selected = Some(Date::new(self.year, self.month, 1));
415 }
416 }
417
418 pub fn select_prev_day(&mut self) {
420 if let Some(date) = self.selected {
421 if date.day > 1 {
422 self.selected = Some(Date::new(date.year, date.month, date.day - 1));
423 } else if date.month > 1 {
424 let prev_month = date.month - 1;
425 let days = days_in_month(date.year, prev_month);
426 self.selected = Some(Date::new(date.year, prev_month, days));
427 self.month = prev_month;
428 } else {
429 let days = days_in_month(date.year - 1, 12);
430 self.selected = Some(Date::new(date.year - 1, 12, days));
431 self.year = date.year - 1;
432 self.month = 12;
433 }
434 } else {
435 self.selected = Some(Date::new(self.year, self.month, 1));
436 }
437 }
438
439 pub fn select_next_week(&mut self) {
441 for _ in 0..7 {
442 self.select_next_day();
443 }
444 }
445
446 pub fn select_prev_week(&mut self) {
448 for _ in 0..7 {
449 self.select_prev_day();
450 }
451 }
452
453 pub fn handle_key(&mut self, key: &crate::event::Key) -> bool {
455 use crate::event::Key;
456
457 if !self.focused {
458 return false;
459 }
460
461 match key {
462 Key::Left | Key::Char('h') => {
463 self.select_prev_day();
464 true
465 }
466 Key::Right | Key::Char('l') => {
467 self.select_next_day();
468 true
469 }
470 Key::Up | Key::Char('k') => {
471 self.select_prev_week();
472 true
473 }
474 Key::Down | Key::Char('j') => {
475 self.select_next_week();
476 true
477 }
478 Key::Char('[') => {
479 self.prev_month();
480 true
481 }
482 Key::Char(']') => {
483 self.next_month();
484 true
485 }
486 Key::Char('{') => {
487 self.prev_year();
488 true
489 }
490 Key::Char('}') => {
491 self.next_year();
492 true
493 }
494 _ => false,
495 }
496 }
497
498 fn is_in_range(&self, date: &Date) -> bool {
500 match (self.selected, self.range_end) {
501 (Some(start), Some(end)) => {
502 let (start, end) = if start <= end {
503 (start, end)
504 } else {
505 (end, start)
506 };
507 date >= &start && date <= &end
508 }
509 _ => false,
510 }
511 }
512
513 fn get_marker(&self, date: &Date) -> Option<&DateMarker> {
515 self.markers.iter().find(|m| &m.date == date)
516 }
517
518 fn day_names(&self) -> [&'static str; 7] {
520 match self.first_day {
521 FirstDayOfWeek::Sunday => ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
522 FirstDayOfWeek::Monday => ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
523 }
524 }
525
526 fn is_weekend(&self, day_index: u32) -> bool {
528 match self.first_day {
529 FirstDayOfWeek::Sunday => day_index == 0 || day_index == 6,
530 FirstDayOfWeek::Monday => day_index == 5 || day_index == 6,
531 }
532 }
533
534 fn render_month(&self, ctx: &mut RenderContext) {
536 let area = ctx.area;
537 if area.width < 20 || area.height < 8 {
538 return;
539 }
540
541 let has_border = self.border_color.is_some();
542 let start_x = area.x + if has_border { 1 } else { 0 };
543 let start_y = area.y + if has_border { 1 } else { 0 };
544 let week_num_offset: u16 = if self.show_week_numbers { 4 } else { 0 };
545
546 if let Some(border_color) = self.border_color {
548 render_border(ctx, area, border_color);
549 }
550
551 let month_names = [
553 "January",
554 "February",
555 "March",
556 "April",
557 "May",
558 "June",
559 "July",
560 "August",
561 "September",
562 "October",
563 "November",
564 "December",
565 ];
566 let header = format!("{} {}", month_names[(self.month - 1) as usize], self.year);
567 let header_x = start_x + week_num_offset + (20 - header.len() as u16) / 2;
568
569 for (i, ch) in header.chars().enumerate() {
570 let mut cell = Cell::new(ch);
571 cell.fg = Some(self.header_fg);
572 cell.bg = self.header_bg;
573 cell.modifier |= Modifier::BOLD;
574 ctx.buffer.set(header_x + i as u16, start_y, cell);
575 }
576
577 if self.focused {
579 let mut left = Cell::new('◀');
580 left.fg = Some(self.header_fg);
581 ctx.buffer.set(start_x + week_num_offset, start_y, left);
582
583 let mut right = Cell::new('▶');
584 right.fg = Some(self.header_fg);
585 ctx.buffer
586 .set(start_x + week_num_offset + 21, start_y, right);
587 }
588
589 let y = start_y + 2;
591 let day_names = self.day_names();
592
593 if self.show_week_numbers {
594 let mut wk = Cell::new('W');
595 wk.fg = Some(self.header_fg);
596 ctx.buffer.set(start_x, y, wk);
597 }
598
599 for (i, name) in day_names.iter().enumerate() {
600 let x = start_x + week_num_offset + (i as u16) * 3;
601 let is_weekend = self.is_weekend(i as u32);
602
603 for (j, ch) in name.chars().enumerate() {
604 let mut cell = Cell::new(ch);
605 cell.fg = Some(if is_weekend {
606 self.weekend_fg
607 } else {
608 self.header_fg
609 });
610 ctx.buffer.set(x + j as u16, y, cell);
611 }
612 }
613
614 let first_day = first_day_of_month(self.year, self.month);
616 let first_day_adjusted = match self.first_day {
617 FirstDayOfWeek::Sunday => first_day,
618 FirstDayOfWeek::Monday => (first_day + 6) % 7,
619 };
620 let days = days_in_month(self.year, self.month);
621
622 let mut day = 1u32;
623 let mut row = 0u32;
624
625 while day <= days {
626 let y = start_y + 3 + row as u16;
627
628 if self.show_week_numbers {
630 let week_num = self.get_week_number(self.year, self.month, day);
631 let week_str = format!("{:2}", week_num);
632 for (i, ch) in week_str.chars().enumerate() {
633 let mut cell = Cell::new(ch);
634 cell.fg = Some(self.outside_fg);
635 ctx.buffer.set(start_x + i as u16, y, cell);
636 }
637 }
638
639 for col in 0..7u32 {
640 let cell_day = if row == 0 {
641 if col < first_day_adjusted {
642 continue;
643 }
644 col - first_day_adjusted + 1
645 } else {
646 row * 7 + col - first_day_adjusted + 1
647 };
648
649 if cell_day < 1 || cell_day > days {
650 continue;
651 }
652
653 let x = start_x + week_num_offset + col as u16 * 3;
654 let date = Date::new(self.year, self.month, cell_day);
655
656 let is_selected = self.selected == Some(date);
658 let is_in_range = self.is_in_range(&date);
659 let is_today = self.today == Some(date);
660 let is_weekend = self.is_weekend(col);
661 let marker = self.get_marker(&date);
662
663 let (fg, bg, modifier) = if is_selected {
664 (self.selected_fg, Some(self.selected_bg), Modifier::BOLD)
665 } else if is_in_range {
666 (
667 self.selected_fg,
668 Some(Color::rgb(60, 90, 120)),
669 Modifier::empty(),
670 )
671 } else if is_today {
672 (self.today_fg, None, Modifier::BOLD)
673 } else if let Some(m) = marker {
674 (m.color, None, Modifier::empty())
675 } else if is_weekend {
676 (self.weekend_fg, None, Modifier::empty())
677 } else {
678 (self.day_fg, None, Modifier::empty())
679 };
680
681 let day_str = format!("{:2}", cell_day);
683 for (i, ch) in day_str.chars().enumerate() {
684 let mut cell = Cell::new(ch);
685 cell.fg = Some(fg);
686 cell.bg = bg;
687 cell.modifier = modifier;
688 ctx.buffer.set(x + i as u16, y, cell);
689 }
690
691 if let Some(m) = marker {
693 if let Some(sym) = m.symbol {
694 let mut cell = Cell::new(sym);
695 cell.fg = Some(m.color);
696 ctx.buffer.set(x + 2, y, cell);
697 }
698 }
699 }
700
701 if row == 0 {
702 day = 8 - first_day_adjusted;
703 } else {
704 day += 7;
705 }
706 row += 1;
707 }
708 }
709
710 fn get_week_number(&self, year: i32, month: u32, day: u32) -> u32 {
717 let day_of_year = (1..month).map(|m| days_in_month(year, m)).sum::<u32>() + day;
719
720 let weekday = {
722 let m = if month < 3 {
723 month as i32 + 12
724 } else {
725 month as i32
726 };
727 let y = if month < 3 { year - 1 } else { year };
728 let k = y % 100;
729 let j = y / 100;
730 let h = (day as i32 + (13 * (m + 1)) / 5 + k + k / 4 + j / 4 - 2 * j) % 7;
731 ((h + 5) % 7) as u32
733 };
734
735 let thursday_day_of_year = day_of_year as i32 + 3 - weekday as i32;
738
739 if thursday_day_of_year < 1 {
740 return self.get_week_number(year - 1, 12, 31);
742 }
743
744 let days_in_year = if is_leap_year(year) { 366 } else { 365 };
745 if thursday_day_of_year > days_in_year as i32 {
746 return 1;
748 }
749
750 ((thursday_day_of_year as u32 - 1) / 7) + 1
752 }
753}
754
755impl Default for Calendar {
756 fn default() -> Self {
757 Self::new(2025, 1)
758 }
759}
760
761impl View for Calendar {
762 crate::impl_view_meta!("Calendar");
763
764 fn render(&self, ctx: &mut RenderContext) {
765 match self.mode {
766 CalendarMode::Month => self.render_month(ctx),
767 CalendarMode::Year | CalendarMode::Week => {
768 self.render_month(ctx);
770 }
771 }
772 }
773}
774
775impl_styled_view!(Calendar);
776impl_props_builders!(Calendar);
777
778pub fn calendar(year: i32, month: u32) -> Calendar {
780 Calendar::new(year, month)
781}
782
783#[cfg(test)]
784mod tests {
785 use super::*;
786 use crate::layout::Rect;
787 use crate::render::Buffer;
788
789 #[test]
790 fn test_calendar_new() {
791 let cal = Calendar::new(2025, 1);
792 assert_eq!(cal.year, 2025);
793 assert_eq!(cal.month, 1);
794 }
795
796 #[test]
797 fn test_calendar_month_clamp() {
798 let cal = Calendar::new(2025, 13);
799 assert_eq!(cal.month, 12);
800
801 let cal = Calendar::new(2025, 0);
802 assert_eq!(cal.month, 1);
803 }
804
805 #[test]
806 fn test_date_new() {
807 let date = Date::new(2025, 6, 15);
808 assert_eq!(date.year, 2025);
809 assert_eq!(date.month, 6);
810 assert_eq!(date.day, 15);
811 }
812
813 #[test]
814 fn test_date_valid() {
815 assert!(Date::new(2025, 1, 1).is_valid());
816 assert!(Date::new(2025, 2, 28).is_valid());
817 assert!(Date::new(2024, 2, 29).is_valid()); assert!(!Date::new(2025, 2, 29).is_valid()); assert!(!Date::new(2025, 13, 1).is_valid());
820 assert!(!Date::new(2025, 1, 32).is_valid());
821 }
822
823 #[test]
824 fn test_days_in_month() {
825 assert_eq!(days_in_month(2025, 1), 31);
826 assert_eq!(days_in_month(2025, 2), 28);
827 assert_eq!(days_in_month(2024, 2), 29);
828 assert_eq!(days_in_month(2025, 4), 30);
829 }
830
831 #[test]
832 fn test_leap_year() {
833 assert!(is_leap_year(2024));
834 assert!(!is_leap_year(2025));
835 assert!(is_leap_year(2000));
836 assert!(!is_leap_year(1900));
837 }
838
839 #[test]
840 fn test_calendar_navigation() {
841 let mut cal = Calendar::new(2025, 1);
842
843 cal.next_month();
844 assert_eq!(cal.month, 2);
845
846 cal.prev_month();
847 assert_eq!(cal.month, 1);
848
849 cal.prev_month();
850 assert_eq!(cal.month, 12);
851 assert_eq!(cal.year, 2024);
852
853 cal.next_month();
854 assert_eq!(cal.month, 1);
855 assert_eq!(cal.year, 2025);
856 }
857
858 #[test]
859 fn test_calendar_year_navigation() {
860 let mut cal = Calendar::new(2025, 6);
861
862 cal.next_year();
863 assert_eq!(cal.year, 2026);
864
865 cal.prev_year();
866 assert_eq!(cal.year, 2025);
867 }
868
869 #[test]
870 fn test_calendar_selection() {
871 let mut cal = Calendar::new(2025, 1);
872
873 cal.select(Date::new(2025, 1, 15));
874 assert_eq!(cal.get_selected(), Some(Date::new(2025, 1, 15)));
875
876 cal.clear_selection();
877 assert_eq!(cal.get_selected(), None);
878 }
879
880 #[test]
881 fn test_calendar_select_next_day() {
882 let mut cal = Calendar::new(2025, 1).selected(Date::new(2025, 1, 31));
883
884 cal.select_next_day();
885 assert_eq!(cal.get_selected(), Some(Date::new(2025, 2, 1)));
886 assert_eq!(cal.month, 2);
887 }
888
889 #[test]
890 fn test_calendar_select_prev_day() {
891 let mut cal = Calendar::new(2025, 2).selected(Date::new(2025, 2, 1));
892
893 cal.select_prev_day();
894 assert_eq!(cal.get_selected(), Some(Date::new(2025, 1, 31)));
895 assert_eq!(cal.month, 1);
896 }
897
898 #[test]
899 fn test_date_marker() {
900 let marker = DateMarker::new(Date::new(2025, 1, 1), Color::RED).symbol('★');
901
902 assert_eq!(marker.date, Date::new(2025, 1, 1));
903 assert_eq!(marker.color, Color::RED);
904 assert_eq!(marker.symbol, Some('★'));
905 }
906
907 #[test]
908 fn test_calendar_range() {
909 let cal = Calendar::new(2025, 1).range(Date::new(2025, 1, 10), Date::new(2025, 1, 20));
910
911 assert!(cal.is_in_range(&Date::new(2025, 1, 15)));
912 assert!(!cal.is_in_range(&Date::new(2025, 1, 5)));
913 }
914
915 #[test]
916 fn test_calendar_render() {
917 let mut buffer = Buffer::new(30, 12);
918 let area = Rect::new(0, 0, 30, 12);
919 let mut ctx = RenderContext::new(&mut buffer, area);
920
921 let cal = Calendar::new(2025, 1)
922 .selected(Date::new(2025, 1, 15))
923 .today(Date::new(2025, 1, 10));
924
925 cal.render(&mut ctx);
926 }
928
929 #[test]
930 fn test_calendar_with_border() {
931 let mut buffer = Buffer::new(30, 12);
932 let area = Rect::new(0, 0, 30, 12);
933 let mut ctx = RenderContext::new(&mut buffer, area);
934
935 let cal = Calendar::new(2025, 1).border(Color::WHITE);
936 cal.render(&mut ctx);
937
938 assert_eq!(buffer.get(0, 0).unwrap().symbol, '┌');
939 }
940
941 #[test]
942 fn test_calendar_first_day() {
943 let cal_sun = Calendar::new(2025, 1).first_day(FirstDayOfWeek::Sunday);
944 let cal_mon = Calendar::new(2025, 1).first_day(FirstDayOfWeek::Monday);
945
946 assert_eq!(cal_sun.day_names()[0], "Su");
947 assert_eq!(cal_mon.day_names()[0], "Mo");
948 }
949
950 #[test]
951 fn test_first_day_of_month() {
952 assert_eq!(first_day_of_month(2025, 1), 3);
954 }
955
956 #[test]
957 fn test_calendar_helper() {
958 let cal = calendar(2025, 6);
959 assert_eq!(cal.year, 2025);
960 assert_eq!(cal.month, 6);
961 }
962
963 #[test]
964 fn test_iso_week_number() {
965 let cal = Calendar::new(2025, 1);
966
967 assert_eq!(cal.get_week_number(2025, 1, 1), 1);
969
970 assert_eq!(cal.get_week_number(2025, 1, 6), 2);
972
973 assert_eq!(cal.get_week_number(2024, 12, 30), 1);
975
976 assert_eq!(cal.get_week_number(2024, 12, 28), 52);
978 }
979
980 #[test]
981 fn test_iso_week_number_edge_cases() {
982 let cal = Calendar::new(2020, 1);
983
984 assert_eq!(cal.get_week_number(2020, 1, 1), 1);
986
987 assert_eq!(cal.get_week_number(2019, 12, 30), 1);
989
990 assert_eq!(cal.get_week_number(2020, 12, 31), 53);
992 }
993}