1 // This is a part of Chrono. 2 // See README.md and LICENSE.txt for details. 3 4 //! ISO 8601 week. 5 6 use core::fmt; 7 8 use super::internals::YearFlags; 9 10 #[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))] 11 use rkyv::{Archive, Deserialize, Serialize}; 12 13 /// ISO 8601 week. 14 /// 15 /// This type, combined with [`Weekday`](../enum.Weekday.html), 16 /// constitutes the ISO 8601 [week date](./struct.NaiveDate.html#week-date). 17 /// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types 18 /// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method. 19 #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)] 20 #[cfg_attr( 21 any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"), 22 derive(Archive, Deserialize, Serialize), 23 archive(compare(PartialEq, PartialOrd)), 24 archive_attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)) 25 )] 26 #[cfg_attr(feature = "rkyv-validation", archive(check_bytes))] 27 pub struct IsoWeek { 28 // Note that this allows for larger year range than `NaiveDate`. 29 // This is crucial because we have an edge case for the first and last week supported, 30 // which year number might not match the calendar year number. 31 ywf: i32, // (year << 10) | (week << 4) | flag 32 } 33 34 impl IsoWeek { 35 /// Returns the corresponding `IsoWeek` from the year and the `Of` internal value. 36 // 37 // Internal use only. We don't expose the public constructor for `IsoWeek` for now 38 // because the year range for the week date and the calendar date do not match, and 39 // it is confusing to have a date that is out of range in one and not in another. 40 // Currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`. from_yof(year: i32, ordinal: u32, year_flags: YearFlags) -> Self41 pub(super) fn from_yof(year: i32, ordinal: u32, year_flags: YearFlags) -> Self { 42 let rawweek = (ordinal + year_flags.isoweek_delta()) / 7; 43 let (year, week) = if rawweek < 1 { 44 // previous year 45 let prevlastweek = YearFlags::from_year(year - 1).nisoweeks(); 46 (year - 1, prevlastweek) 47 } else { 48 let lastweek = year_flags.nisoweeks(); 49 if rawweek > lastweek { 50 // next year 51 (year + 1, 1) 52 } else { 53 (year, rawweek) 54 } 55 }; 56 let flags = YearFlags::from_year(year); 57 IsoWeek { ywf: (year << 10) | (week << 4) as i32 | i32::from(flags.0) } 58 } 59 60 /// Returns the year number for this ISO week. 61 /// 62 /// # Example 63 /// 64 /// ``` 65 /// use chrono::{Datelike, NaiveDate, Weekday}; 66 /// 67 /// let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); 68 /// assert_eq!(d.iso_week().year(), 2015); 69 /// ``` 70 /// 71 /// This year number might not match the calendar year number. 72 /// Continuing the example... 73 /// 74 /// ``` 75 /// # use chrono::{NaiveDate, Datelike, Weekday}; 76 /// # let d = NaiveDate::from_isoywd_opt(2015, 1, Weekday::Mon).unwrap(); 77 /// assert_eq!(d.year(), 2014); 78 /// assert_eq!(d, NaiveDate::from_ymd_opt(2014, 12, 29).unwrap()); 79 /// ``` 80 #[inline] year(&self) -> i3281 pub const fn year(&self) -> i32 { 82 self.ywf >> 10 83 } 84 85 /// Returns the ISO week number starting from 1. 86 /// 87 /// The return value ranges from 1 to 53. (The last week of year differs by years.) 88 /// 89 /// # Example 90 /// 91 /// ``` 92 /// use chrono::{Datelike, NaiveDate, Weekday}; 93 /// 94 /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); 95 /// assert_eq!(d.iso_week().week(), 15); 96 /// ``` 97 #[inline] week(&self) -> u3298 pub const fn week(&self) -> u32 { 99 ((self.ywf >> 4) & 0x3f) as u32 100 } 101 102 /// Returns the ISO week number starting from 0. 103 /// 104 /// The return value ranges from 0 to 52. (The last week of year differs by years.) 105 /// 106 /// # Example 107 /// 108 /// ``` 109 /// use chrono::{Datelike, NaiveDate, Weekday}; 110 /// 111 /// let d = NaiveDate::from_isoywd_opt(2015, 15, Weekday::Mon).unwrap(); 112 /// assert_eq!(d.iso_week().week0(), 14); 113 /// ``` 114 #[inline] week0(&self) -> u32115 pub const fn week0(&self) -> u32 { 116 ((self.ywf >> 4) & 0x3f) as u32 - 1 117 } 118 } 119 120 /// The `Debug` output of the ISO week `w` is the same as 121 /// [`d.format("%G-W%V")`](../format/strftime/index.html) 122 /// where `d` is any `NaiveDate` value in that week. 123 /// 124 /// # Example 125 /// 126 /// ``` 127 /// use chrono::{Datelike, NaiveDate}; 128 /// 129 /// assert_eq!( 130 /// format!("{:?}", NaiveDate::from_ymd_opt(2015, 9, 5).unwrap().iso_week()), 131 /// "2015-W36" 132 /// ); 133 /// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 3).unwrap().iso_week()), "0000-W01"); 134 /// assert_eq!( 135 /// format!("{:?}", NaiveDate::from_ymd_opt(9999, 12, 31).unwrap().iso_week()), 136 /// "9999-W52" 137 /// ); 138 /// ``` 139 /// 140 /// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE. 141 /// 142 /// ``` 143 /// # use chrono::{NaiveDate, Datelike}; 144 /// assert_eq!(format!("{:?}", NaiveDate::from_ymd_opt(0, 1, 2).unwrap().iso_week()), "-0001-W52"); 145 /// assert_eq!( 146 /// format!("{:?}", NaiveDate::from_ymd_opt(10000, 12, 31).unwrap().iso_week()), 147 /// "+10000-W52" 148 /// ); 149 /// ``` 150 impl fmt::Debug for IsoWeek { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result151 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 152 let year = self.year(); 153 let week = self.week(); 154 if (0..=9999).contains(&year) { 155 write!(f, "{:04}-W{:02}", year, week) 156 } else { 157 // ISO 8601 requires the explicit sign for out-of-range years 158 write!(f, "{:+05}-W{:02}", year, week) 159 } 160 } 161 } 162 163 #[cfg(test)] 164 mod tests { 165 #[cfg(feature = "rkyv-validation")] 166 use super::IsoWeek; 167 use crate::naive::date::{self, NaiveDate}; 168 use crate::Datelike; 169 170 #[test] test_iso_week_extremes()171 fn test_iso_week_extremes() { 172 let minweek = NaiveDate::MIN.iso_week(); 173 let maxweek = NaiveDate::MAX.iso_week(); 174 175 assert_eq!(minweek.year(), date::MIN_YEAR); 176 assert_eq!(minweek.week(), 1); 177 assert_eq!(minweek.week0(), 0); 178 #[cfg(feature = "alloc")] 179 assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string()); 180 181 assert_eq!(maxweek.year(), date::MAX_YEAR + 1); 182 assert_eq!(maxweek.week(), 1); 183 assert_eq!(maxweek.week0(), 0); 184 #[cfg(feature = "alloc")] 185 assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string()); 186 } 187 188 #[test] test_iso_week_equivalence_for_first_week()189 fn test_iso_week_equivalence_for_first_week() { 190 let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap(); 191 let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); 192 193 assert_eq!(monday.iso_week(), friday.iso_week()); 194 } 195 196 #[test] test_iso_week_equivalence_for_last_week()197 fn test_iso_week_equivalence_for_last_week() { 198 let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap(); 199 let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); 200 201 assert_eq!(monday.iso_week(), friday.iso_week()); 202 } 203 204 #[test] test_iso_week_ordering_for_first_week()205 fn test_iso_week_ordering_for_first_week() { 206 let monday = NaiveDate::from_ymd_opt(2024, 12, 30).unwrap(); 207 let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap(); 208 209 assert!(monday.iso_week() >= friday.iso_week()); 210 assert!(monday.iso_week() <= friday.iso_week()); 211 } 212 213 #[test] test_iso_week_ordering_for_last_week()214 fn test_iso_week_ordering_for_last_week() { 215 let monday = NaiveDate::from_ymd_opt(2026, 12, 28).unwrap(); 216 let friday = NaiveDate::from_ymd_opt(2027, 1, 1).unwrap(); 217 218 assert!(monday.iso_week() >= friday.iso_week()); 219 assert!(monday.iso_week() <= friday.iso_week()); 220 } 221 222 #[test] 223 #[cfg(feature = "rkyv-validation")] test_rkyv_validation()224 fn test_rkyv_validation() { 225 let minweek = NaiveDate::MIN.iso_week(); 226 let bytes = rkyv::to_bytes::<_, 4>(&minweek).unwrap(); 227 assert_eq!(rkyv::from_bytes::<IsoWeek>(&bytes).unwrap(), minweek); 228 229 let maxweek = NaiveDate::MAX.iso_week(); 230 let bytes = rkyv::to_bytes::<_, 4>(&maxweek).unwrap(); 231 assert_eq!(rkyv::from_bytes::<IsoWeek>(&bytes).unwrap(), maxweek); 232 } 233 } 234