icu_calendar/
week.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5//! Functions for region-specific weekday information.
6
7use crate::{error::RangeError, provider::*, types::Weekday};
8use icu_locale_core::preferences::{define_preferences, extensions::unicode::keywords::FirstDay};
9use icu_provider::prelude::*;
10
11/// Minimum number of days in a month unit required for using this module
12const MIN_UNIT_DAYS: u16 = 14;
13
14define_preferences!(
15    /// The preferences for the week information.
16    [Copy]
17    WeekPreferences,
18    {
19        /// The first day of the week
20        first_weekday: FirstDay
21    }
22);
23
24/// Information about the first day of the week and the weekend.
25#[derive(Clone, Copy, Debug)]
26#[non_exhaustive]
27pub struct WeekInformation {
28    /// The first day of a week.
29    pub first_weekday: Weekday,
30    /// The set of weekend days
31    pub weekend: WeekdaySet,
32}
33
34impl WeekInformation {
35    icu_provider::gen_buffer_data_constructors!(
36        (prefs: WeekPreferences) -> error: DataError,
37        /// Creates a new [`WeekCalculator`] from compiled data.
38    );
39
40    #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::try_new)]
41    pub fn try_new_unstable<P>(provider: &P, prefs: WeekPreferences) -> Result<Self, DataError>
42    where
43        P: DataProvider<crate::provider::CalendarWeekV1> + ?Sized,
44    {
45        let locale = CalendarWeekV1::make_locale(prefs.locale_preferences);
46        provider
47            .load(DataRequest {
48                id: DataIdentifierBorrowed::for_locale(&locale),
49                ..Default::default()
50            })
51            .map(|response| WeekInformation {
52                first_weekday: match prefs.first_weekday {
53                    Some(FirstDay::Mon) => Weekday::Monday,
54                    Some(FirstDay::Tue) => Weekday::Tuesday,
55                    Some(FirstDay::Wed) => Weekday::Wednesday,
56                    Some(FirstDay::Thu) => Weekday::Thursday,
57                    Some(FirstDay::Fri) => Weekday::Friday,
58                    Some(FirstDay::Sat) => Weekday::Saturday,
59                    Some(FirstDay::Sun) => Weekday::Sunday,
60                    _ => response.payload.get().first_weekday,
61                },
62                weekend: response.payload.get().weekend,
63            })
64    }
65
66    /// Weekdays that are part of the 'weekend', for calendar purposes.
67    /// Days may not be contiguous, and order is based off the first weekday.
68    pub fn weekend(self) -> WeekdaySetIterator {
69        WeekdaySetIterator::new(self.first_weekday, self.weekend)
70    }
71}
72
73#[derive(Clone, Copy, Debug)]
74pub(crate) struct WeekCalculator {
75    first_weekday: Weekday,
76    min_week_days: u8,
77}
78
79impl WeekCalculator {
80    pub(crate) const ISO: Self = Self {
81        first_weekday: Weekday::Monday,
82        min_week_days: 4,
83    };
84
85    /// Returns the zero based index of `weekday` vs this calendar's start of week.
86    fn weekday_index(self, weekday: Weekday) -> i8 {
87        (7 + (weekday as i8) - (self.first_weekday as i8)) % 7
88    }
89
90    /// Computes & returns the week of given month/year according to `calendar`.
91    ///
92    /// # Arguments
93    ///  - calendar: Calendar information used to compute the week number.
94    ///  - num_days_in_previous_unit: The number of days in the preceding month/year.
95    ///  - num_days_in_unit: The number of days in the month/year.
96    ///  - day: 1-based day of month/year.
97    ///  - week_day: The weekday of `day`..
98    ///
99    /// # Error
100    /// If num_days_in_unit/num_days_in_previous_unit < MIN_UNIT_DAYS
101    pub(crate) fn week_of(
102        self,
103        num_days_in_previous_unit: u16,
104        num_days_in_unit: u16,
105        day: u16,
106        week_day: Weekday,
107    ) -> Result<WeekOf, RangeError> {
108        let current = UnitInfo::new(
109            // The first day of this month/year is (day - 1) days from `day`.
110            add_to_weekday(week_day, 1 - i32::from(day)),
111            num_days_in_unit,
112        )?;
113
114        match current.relative_week(self, day) {
115            RelativeWeek::LastWeekOfPreviousUnit => {
116                let previous = UnitInfo::new(
117                    add_to_weekday(current.first_day, -i32::from(num_days_in_previous_unit)),
118                    num_days_in_previous_unit,
119                )?;
120
121                Ok(WeekOf {
122                    week: previous.num_weeks(self),
123                    unit: RelativeUnit::Previous,
124                })
125            }
126            RelativeWeek::WeekOfCurrentUnit(w) => Ok(WeekOf {
127                week: w,
128                unit: RelativeUnit::Current,
129            }),
130            RelativeWeek::FirstWeekOfNextUnit => Ok(WeekOf {
131                week: 1,
132                unit: RelativeUnit::Next,
133            }),
134        }
135    }
136}
137
138/// Returns the weekday that's `num_days` after `weekday`.
139fn add_to_weekday(weekday: Weekday, num_days: i32) -> Weekday {
140    let new_weekday = (7 + (weekday as i32) + (num_days % 7)) % 7;
141    Weekday::from_days_since_sunday(new_weekday as isize)
142}
143
144/// Which year or month that a calendar assigns a week to relative to the year/month
145/// the week is in.
146#[derive(Clone, Copy, Debug, PartialEq)]
147#[allow(clippy::enum_variant_names)]
148enum RelativeWeek {
149    /// A week that is assigned to the last week of the previous year/month. e.g. 2021-01-01 is week 54 of 2020 per the ISO calendar.
150    LastWeekOfPreviousUnit,
151    /// A week that's assigned to the current year/month. The offset is 1-based. e.g. 2021-01-11 is week 2 of 2021 per the ISO calendar so would be WeekOfCurrentUnit(2).
152    WeekOfCurrentUnit(u8),
153    /// A week that is assigned to the first week of the next year/month. e.g. 2019-12-31 is week 1 of 2020 per the ISO calendar.
154    FirstWeekOfNextUnit,
155}
156
157/// Information about a year or month.
158#[derive(Clone, Copy)]
159struct UnitInfo {
160    /// The weekday of this year/month's first day.
161    first_day: Weekday,
162    /// The number of days in this year/month.
163    duration_days: u16,
164}
165
166impl UnitInfo {
167    /// Creates a UnitInfo for a given year or month.
168    fn new(first_day: Weekday, duration_days: u16) -> Result<UnitInfo, RangeError> {
169        if duration_days < MIN_UNIT_DAYS {
170            return Err(RangeError {
171                field: "num_days_in_unit",
172                value: duration_days as i32,
173                min: MIN_UNIT_DAYS as i32,
174                max: i32::MAX,
175            });
176        }
177        Ok(UnitInfo {
178            first_day,
179            duration_days,
180        })
181    }
182
183    /// Returns the start of this unit's first week.
184    ///
185    /// The returned value can be negative if this unit's first week started during the previous
186    /// unit.
187    fn first_week_offset(self, calendar: WeekCalculator) -> i8 {
188        let first_day_index = calendar.weekday_index(self.first_day);
189        if 7 - first_day_index >= calendar.min_week_days as i8 {
190            -first_day_index
191        } else {
192            7 - first_day_index
193        }
194    }
195
196    /// Returns the number of weeks in this unit according to `calendar`.
197    fn num_weeks(self, calendar: WeekCalculator) -> u8 {
198        let first_week_offset = self.first_week_offset(calendar);
199        let num_days_including_first_week =
200            (self.duration_days as i32) - (first_week_offset as i32);
201        debug_assert!(
202            num_days_including_first_week >= 0,
203            "Unit is shorter than a week."
204        );
205        ((num_days_including_first_week + 7 - (calendar.min_week_days as i32)) / 7) as u8
206    }
207
208    /// Returns the week number for the given day in this unit.
209    fn relative_week(self, calendar: WeekCalculator, day: u16) -> RelativeWeek {
210        let days_since_first_week =
211            i32::from(day) - i32::from(self.first_week_offset(calendar)) - 1;
212        if days_since_first_week < 0 {
213            return RelativeWeek::LastWeekOfPreviousUnit;
214        }
215
216        let week_number = (1 + days_since_first_week / 7) as u8;
217        if week_number > self.num_weeks(calendar) {
218            return RelativeWeek::FirstWeekOfNextUnit;
219        }
220        RelativeWeek::WeekOfCurrentUnit(week_number)
221    }
222}
223
224/// The year or month that a calendar assigns a week to relative to the year/month that it is in.
225#[derive(Debug, PartialEq)]
226#[allow(clippy::exhaustive_enums)] // this type is stable
227pub(crate) enum RelativeUnit {
228    /// A week that is assigned to previous year/month. e.g. 2021-01-01 is week 54 of 2020 per the ISO calendar.
229    Previous,
230    /// A week that's assigned to the current year/month. e.g. 2021-01-11 is week 2 of 2021 per the ISO calendar.
231    Current,
232    /// A week that is assigned to the next year/month. e.g. 2019-12-31 is week 1 of 2020 per the ISO calendar.
233    Next,
234}
235
236/// The week number assigned to a given week according to a calendar.
237#[derive(Debug, PartialEq)]
238#[allow(clippy::exhaustive_structs)] // this type is stable
239pub(crate) struct WeekOf {
240    /// Week of month/year. 1 based.
241    pub week: u8,
242    /// The month/year that this week is in, relative to the month/year of the input date.
243    pub unit: RelativeUnit,
244}
245
246/// [Iterator] that yields weekdays that are part of the weekend.
247#[derive(Clone, Copy, Debug, PartialEq)]
248pub struct WeekdaySetIterator {
249    /// Determines the order in which we should start reading values from `weekend`.
250    first_weekday: Weekday,
251    /// Day being evaluated.
252    current_day: Weekday,
253    /// Bitset to read weekdays from.
254    weekend: WeekdaySet,
255}
256
257impl WeekdaySetIterator {
258    /// Creates the Iterator. Sets `current_day` to the day after `first_weekday`.
259    pub(crate) fn new(first_weekday: Weekday, weekend: WeekdaySet) -> Self {
260        WeekdaySetIterator {
261            first_weekday,
262            current_day: first_weekday,
263            weekend,
264        }
265    }
266}
267
268impl Iterator for WeekdaySetIterator {
269    type Item = Weekday;
270
271    fn next(&mut self) -> Option<Self::Item> {
272        // Check each bit until we find one that is ON or until we are back to the start of the week.
273        while self.current_day.next_day() != self.first_weekday {
274            if self.weekend.contains(self.current_day) {
275                let result = self.current_day;
276                self.current_day = self.current_day.next_day();
277                return Some(result);
278            } else {
279                self.current_day = self.current_day.next_day();
280            }
281        }
282
283        if self.weekend.contains(self.current_day) {
284            // Clear weekend, we've seen all bits.
285            // Breaks the loop next time `next()` is called
286            self.weekend = WeekdaySet::new(&[]);
287            return Some(self.current_day);
288        }
289
290        Option::None
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297    use crate::{types::Weekday, Date, DateDuration, RangeError};
298
299    static ISO_CALENDAR: WeekCalculator = WeekCalculator {
300        first_weekday: Weekday::Monday,
301        min_week_days: 4,
302    };
303
304    static AE_CALENDAR: WeekCalculator = WeekCalculator {
305        first_weekday: Weekday::Saturday,
306        min_week_days: 4,
307    };
308
309    static US_CALENDAR: WeekCalculator = WeekCalculator {
310        first_weekday: Weekday::Sunday,
311        min_week_days: 1,
312    };
313
314    #[test]
315    fn test_weekday_index() {
316        assert_eq!(ISO_CALENDAR.weekday_index(Weekday::Monday), 0);
317        assert_eq!(ISO_CALENDAR.weekday_index(Weekday::Sunday), 6);
318
319        assert_eq!(AE_CALENDAR.weekday_index(Weekday::Saturday), 0);
320        assert_eq!(AE_CALENDAR.weekday_index(Weekday::Friday), 6);
321    }
322
323    #[test]
324    fn test_first_week_offset() {
325        let first_week_offset =
326            |calendar, day| UnitInfo::new(day, 30).unwrap().first_week_offset(calendar);
327        assert_eq!(first_week_offset(ISO_CALENDAR, Weekday::Monday), 0);
328        assert_eq!(first_week_offset(ISO_CALENDAR, Weekday::Tuesday), -1);
329        assert_eq!(first_week_offset(ISO_CALENDAR, Weekday::Wednesday), -2);
330        assert_eq!(first_week_offset(ISO_CALENDAR, Weekday::Thursday), -3);
331        assert_eq!(first_week_offset(ISO_CALENDAR, Weekday::Friday), 3);
332        assert_eq!(first_week_offset(ISO_CALENDAR, Weekday::Saturday), 2);
333        assert_eq!(first_week_offset(ISO_CALENDAR, Weekday::Sunday), 1);
334
335        assert_eq!(first_week_offset(AE_CALENDAR, Weekday::Saturday), 0);
336        assert_eq!(first_week_offset(AE_CALENDAR, Weekday::Sunday), -1);
337        assert_eq!(first_week_offset(AE_CALENDAR, Weekday::Monday), -2);
338        assert_eq!(first_week_offset(AE_CALENDAR, Weekday::Tuesday), -3);
339        assert_eq!(first_week_offset(AE_CALENDAR, Weekday::Wednesday), 3);
340        assert_eq!(first_week_offset(AE_CALENDAR, Weekday::Thursday), 2);
341        assert_eq!(first_week_offset(AE_CALENDAR, Weekday::Friday), 1);
342
343        assert_eq!(first_week_offset(US_CALENDAR, Weekday::Sunday), 0);
344        assert_eq!(first_week_offset(US_CALENDAR, Weekday::Monday), -1);
345        assert_eq!(first_week_offset(US_CALENDAR, Weekday::Tuesday), -2);
346        assert_eq!(first_week_offset(US_CALENDAR, Weekday::Wednesday), -3);
347        assert_eq!(first_week_offset(US_CALENDAR, Weekday::Thursday), -4);
348        assert_eq!(first_week_offset(US_CALENDAR, Weekday::Friday), -5);
349        assert_eq!(first_week_offset(US_CALENDAR, Weekday::Saturday), -6);
350    }
351
352    #[test]
353    fn test_num_weeks() {
354        // 4 days in first & last week.
355        assert_eq!(
356            UnitInfo::new(Weekday::Thursday, 4 + 2 * 7 + 4)
357                .unwrap()
358                .num_weeks(ISO_CALENDAR),
359            4
360        );
361        // 3 days in first week, 4 in last week.
362        assert_eq!(
363            UnitInfo::new(Weekday::Friday, 3 + 2 * 7 + 4)
364                .unwrap()
365                .num_weeks(ISO_CALENDAR),
366            3
367        );
368        // 3 days in first & last week.
369        assert_eq!(
370            UnitInfo::new(Weekday::Friday, 3 + 2 * 7 + 3)
371                .unwrap()
372                .num_weeks(ISO_CALENDAR),
373            2
374        );
375
376        // 1 day in first & last week.
377        assert_eq!(
378            UnitInfo::new(Weekday::Saturday, 1 + 2 * 7 + 1)
379                .unwrap()
380                .num_weeks(US_CALENDAR),
381            4
382        );
383    }
384
385    /// Uses enumeration & bucketing to assign each day of a month or year `unit` to a week.
386    ///
387    /// This alternative implementation serves as an exhaustive safety check
388    /// of relative_week() (in addition to the manual test points used
389    /// for testing week_of()).
390    fn classify_days_of_unit(calendar: WeekCalculator, unit: &UnitInfo) -> Vec<RelativeWeek> {
391        let mut weeks: Vec<Vec<Weekday>> = Vec::new();
392        for day_index in 0..unit.duration_days {
393            let day = super::add_to_weekday(unit.first_day, i32::from(day_index));
394            if day == calendar.first_weekday || weeks.is_empty() {
395                weeks.push(Vec::new());
396            }
397            weeks.last_mut().unwrap().push(day);
398        }
399
400        let mut day_week_of_units = Vec::new();
401        let mut weeks_in_unit = 0;
402        for (index, week) in weeks.iter().enumerate() {
403            let week_of_unit = if week.len() < usize::from(calendar.min_week_days) {
404                match index {
405                    0 => RelativeWeek::LastWeekOfPreviousUnit,
406                    x if x == weeks.len() - 1 => RelativeWeek::FirstWeekOfNextUnit,
407                    _ => panic!(),
408                }
409            } else {
410                weeks_in_unit += 1;
411                RelativeWeek::WeekOfCurrentUnit(weeks_in_unit)
412            };
413
414            day_week_of_units.append(&mut [week_of_unit].repeat(week.len()));
415        }
416        day_week_of_units
417    }
418
419    #[test]
420    fn test_relative_week_of_month() {
421        for min_week_days in 1..7 {
422            for start_of_week in 1..7 {
423                let calendar = WeekCalculator {
424                    first_weekday: Weekday::from_days_since_sunday(start_of_week),
425                    min_week_days,
426                };
427                for unit_duration in super::MIN_UNIT_DAYS..400 {
428                    for start_of_unit in 1..7 {
429                        let unit = UnitInfo::new(
430                            Weekday::from_days_since_sunday(start_of_unit),
431                            unit_duration,
432                        )
433                        .unwrap();
434                        let expected = classify_days_of_unit(calendar, &unit);
435                        for (index, expected_week_of) in expected.iter().enumerate() {
436                            let day = index + 1;
437                            assert_eq!(
438                                unit.relative_week(calendar, day as u16),
439                                *expected_week_of,
440                                "For the {day}/{unit_duration} starting on Weekday \
441                        {start_of_unit} using start_of_week {start_of_week} \
442                        & min_week_days {min_week_days}"
443                            );
444                        }
445                    }
446                }
447            }
448        }
449    }
450
451    fn week_of_month_from_iso_date(
452        calendar: WeekCalculator,
453        yyyymmdd: u32,
454    ) -> Result<WeekOf, RangeError> {
455        let year = (yyyymmdd / 10000) as i32;
456        let month = ((yyyymmdd / 100) % 100) as u8;
457        let day = (yyyymmdd % 100) as u8;
458
459        let date = Date::try_new_iso(year, month, day)?;
460        let previous_month = date.added(DateDuration::new(0, -1, 0, 0));
461
462        calendar.week_of(
463            u16::from(previous_month.days_in_month()),
464            u16::from(date.days_in_month()),
465            u16::from(day),
466            date.day_of_week(),
467        )
468    }
469
470    #[test]
471    fn test_week_of_month_using_dates() {
472        assert_eq!(
473            week_of_month_from_iso_date(ISO_CALENDAR, 20210418).unwrap(),
474            WeekOf {
475                week: 3,
476                unit: RelativeUnit::Current,
477            }
478        );
479        assert_eq!(
480            week_of_month_from_iso_date(ISO_CALENDAR, 20210419).unwrap(),
481            WeekOf {
482                week: 4,
483                unit: RelativeUnit::Current,
484            }
485        );
486
487        // First day of year is a Thursday.
488        assert_eq!(
489            week_of_month_from_iso_date(ISO_CALENDAR, 20180101).unwrap(),
490            WeekOf {
491                week: 1,
492                unit: RelativeUnit::Current,
493            }
494        );
495        // First day of year is a Friday.
496        assert_eq!(
497            week_of_month_from_iso_date(ISO_CALENDAR, 20210101).unwrap(),
498            WeekOf {
499                week: 5,
500                unit: RelativeUnit::Previous,
501            }
502        );
503
504        // The month ends on a Wednesday.
505        assert_eq!(
506            week_of_month_from_iso_date(ISO_CALENDAR, 20200930).unwrap(),
507            WeekOf {
508                week: 1,
509                unit: RelativeUnit::Next,
510            }
511        );
512        // The month ends on a Thursday.
513        assert_eq!(
514            week_of_month_from_iso_date(ISO_CALENDAR, 20201231).unwrap(),
515            WeekOf {
516                week: 5,
517                unit: RelativeUnit::Current,
518            }
519        );
520
521        // US calendar always assigns the week to the current month. 2020-12-31 is a Thursday.
522        assert_eq!(
523            week_of_month_from_iso_date(US_CALENDAR, 20201231).unwrap(),
524            WeekOf {
525                week: 5,
526                unit: RelativeUnit::Current,
527            }
528        );
529        assert_eq!(
530            week_of_month_from_iso_date(US_CALENDAR, 20210101).unwrap(),
531            WeekOf {
532                week: 1,
533                unit: RelativeUnit::Current,
534            }
535        );
536    }
537}
538
539#[test]
540fn test_first_day() {
541    use icu_locale_core::locale;
542
543    assert_eq!(
544        WeekInformation::try_new(locale!("und-US").into())
545            .unwrap()
546            .first_weekday,
547        Weekday::Sunday,
548    );
549
550    assert_eq!(
551        WeekInformation::try_new(locale!("und-FR").into())
552            .unwrap()
553            .first_weekday,
554        Weekday::Monday,
555    );
556
557    assert_eq!(
558        WeekInformation::try_new(locale!("und-FR-u-fw-tue").into())
559            .unwrap()
560            .first_weekday,
561        Weekday::Tuesday,
562    );
563}
564
565#[test]
566fn test_weekend() {
567    use icu_locale_core::locale;
568
569    assert_eq!(
570        WeekInformation::try_new(locale!("und").into())
571            .unwrap()
572            .weekend()
573            .collect::<Vec<_>>(),
574        vec![Weekday::Saturday, Weekday::Sunday],
575    );
576
577    assert_eq!(
578        WeekInformation::try_new(locale!("und-FR").into())
579            .unwrap()
580            .weekend()
581            .collect::<Vec<_>>(),
582        vec![Weekday::Saturday, Weekday::Sunday],
583    );
584
585    assert_eq!(
586        WeekInformation::try_new(locale!("und-IQ").into())
587            .unwrap()
588            .weekend()
589            .collect::<Vec<_>>(),
590        vec![Weekday::Saturday, Weekday::Friday],
591    );
592
593    assert_eq!(
594        WeekInformation::try_new(locale!("und-IR").into())
595            .unwrap()
596            .weekend()
597            .collect::<Vec<_>>(),
598        vec![Weekday::Friday],
599    );
600}
601
602#[test]
603fn test_weekdays_iter() {
604    use Weekday::*;
605
606    // Weekend ends one day before week starts
607    let default_weekend = WeekdaySetIterator::new(Monday, WeekdaySet::new(&[Saturday, Sunday]));
608    assert_eq!(vec![Saturday, Sunday], default_weekend.collect::<Vec<_>>());
609
610    // Non-contiguous weekend
611    let fri_sun_weekend = WeekdaySetIterator::new(Monday, WeekdaySet::new(&[Friday, Sunday]));
612    assert_eq!(vec![Friday, Sunday], fri_sun_weekend.collect::<Vec<_>>());
613
614    let multiple_contiguous_days = WeekdaySetIterator::new(
615        Monday,
616        WeekdaySet::new(&[
617            Weekday::Tuesday,
618            Weekday::Wednesday,
619            Weekday::Thursday,
620            Weekday::Friday,
621        ]),
622    );
623    assert_eq!(
624        vec![Tuesday, Wednesday, Thursday, Friday],
625        multiple_contiguous_days.collect::<Vec<_>>()
626    );
627
628    // Non-contiguous days and iterator yielding elements based off first_weekday
629    let multiple_non_contiguous_days = WeekdaySetIterator::new(
630        Wednesday,
631        WeekdaySet::new(&[
632            Weekday::Tuesday,
633            Weekday::Thursday,
634            Weekday::Friday,
635            Weekday::Sunday,
636        ]),
637    );
638    assert_eq!(
639        vec![Thursday, Friday, Sunday, Tuesday],
640        multiple_non_contiguous_days.collect::<Vec<_>>()
641    );
642}
643
644#[test]
645fn test_iso_weeks() {
646    use crate::types::IsoWeekOfYear;
647    use crate::Date;
648
649    #[allow(clippy::zero_prefixed_literal)]
650    for ((y, m, d), (iso_year, week_number)) in [
651        // 2010 starts on a Thursday, so 2009 has 53 ISO weeks
652        ((2009, 12, 30), (2009, 53)),
653        ((2009, 12, 31), (2009, 53)),
654        ((2010, 01, 01), (2009, 53)),
655        ((2010, 01, 02), (2009, 53)),
656        ((2010, 01, 03), (2009, 53)),
657        ((2010, 01, 04), (2010, 1)),
658        ((2010, 01, 05), (2010, 1)),
659        // 2030 starts on a Monday
660        ((2029, 12, 29), (2029, 52)),
661        ((2029, 12, 30), (2029, 52)),
662        ((2029, 12, 31), (2030, 1)),
663        ((2030, 01, 01), (2030, 1)),
664        ((2030, 01, 02), (2030, 1)),
665        ((2030, 01, 03), (2030, 1)),
666        ((2030, 01, 04), (2030, 1)),
667    ] {
668        assert_eq!(
669            Date::try_new_iso(y, m, d).unwrap().week_of_year(),
670            IsoWeekOfYear {
671                iso_year,
672                week_number
673            }
674        );
675    }
676}