Skip to main content

jiff/civil/
iso_week_date.rs

1use jcore::civil::ISOWeekDate as JISOWeekDate;
2
3use crate::{
4    civil::{Date, DateTime, Weekday},
5    error::Error,
6    fmt::temporal::{DEFAULT_DATETIME_PARSER, DEFAULT_DATETIME_PRINTER},
7    Zoned,
8};
9
10/// A type representing an [ISO 8601 week date].
11///
12/// The ISO 8601 week date scheme devises a calendar where days are identified
13/// by their year, week number and weekday. All years have either precisely
14/// 52 or 53 weeks.
15///
16/// The first week of an ISO 8601 year corresponds to the week containing the
17/// first Thursday of the year. For this reason, an ISO 8601 week year can be
18/// mismatched with the day's corresponding Gregorian year. For example, the
19/// ISO 8601 week date for `1995-01-01` is `1994-W52-7` (with `7` corresponding
20/// to Sunday).
21///
22/// ISO 8601 also considers Monday to be the start of the week, and uses
23/// a 1-based numbering system. That is, Monday corresponds to `1` while
24/// Sunday corresponds to `7` and is the last day of the week. Weekdays are
25/// encapsulated by the [`Weekday`] type, which provides routines for easily
26/// converting between different schemes (such as weeks where Sunday is the
27/// beginning).
28///
29/// [ISO 8601 week date]: https://en.wikipedia.org/wiki/ISO_week_date
30///
31/// # Use case
32///
33/// Some domains use this method of timekeeping. Otherwise, unless you
34/// specifically want a week oriented calendar, it's likely that you'll never
35/// need to care about this type.
36///
37/// # Parsing and printing
38///
39/// The `ISOWeekDate` type provides convenient trait implementations of
40/// [`std::str::FromStr`] and [`std::fmt::Display`]. These use the format
41/// specified by ISO 8601 for week dates:
42///
43/// ```
44/// use jiff::civil::ISOWeekDate;
45///
46/// let week_date: ISOWeekDate = "2024-W24-7".parse()?;
47/// assert_eq!(week_date.to_string(), "2024-W24-7");
48/// assert_eq!(week_date.date().to_string(), "2024-06-16");
49///
50/// # Ok::<(), Box<dyn std::error::Error>>(())
51/// ```
52///
53/// ISO 8601 allows the `-` separator to be absent:
54///
55/// ```
56/// use jiff::civil::ISOWeekDate;
57///
58/// let week_date: ISOWeekDate = "2024W241".parse()?;
59/// assert_eq!(week_date.to_string(), "2024-W24-1");
60/// assert_eq!(week_date.date().to_string(), "2024-06-10");
61///
62/// // But you cannot mix and match. Either `-` separates
63/// // both the year and week, or neither.
64/// assert!("2024W24-1".parse::<ISOWeekDate>().is_err());
65/// assert!("2024-W241".parse::<ISOWeekDate>().is_err());
66///
67/// # Ok::<(), Box<dyn std::error::Error>>(())
68/// ```
69///
70/// And the `W` may also be lowercase:
71///
72/// ```
73/// use jiff::civil::ISOWeekDate;
74///
75/// let week_date: ISOWeekDate = "2024-w24-2".parse()?;
76/// assert_eq!(week_date.to_string(), "2024-W24-2");
77/// assert_eq!(week_date.date().to_string(), "2024-06-11");
78///
79/// # Ok::<(), Box<dyn std::error::Error>>(())
80/// ```
81///
82/// # Default value
83///
84/// For convenience, this type implements the `Default` trait. Its default
85/// value is the first day of the zeroth year. i.e., `0000-W1-1`.
86///
87/// # Example: sample dates
88///
89/// This example shows a couple ISO 8601 week dates and their corresponding
90/// Gregorian equivalents:
91///
92/// ```
93/// use jiff::civil::{ISOWeekDate, Weekday, date};
94///
95/// let d = date(2019, 12, 30);
96/// let weekdate = ISOWeekDate::new(2020, 1, Weekday::Monday).unwrap();
97/// assert_eq!(d.iso_week_date(), weekdate);
98///
99/// let d = date(2024, 3, 9);
100/// let weekdate = ISOWeekDate::new(2024, 10, Weekday::Saturday).unwrap();
101/// assert_eq!(d.iso_week_date(), weekdate);
102/// ```
103///
104/// # Example: overlapping leap and long years
105///
106/// A "long" ISO 8601 week year is a year with 53 weeks. That is, it is a year
107/// that includes a leap week. This example shows all years in the 20th
108/// century that are both Gregorian leap years and long years.
109///
110/// ```
111/// use jiff::civil::date;
112///
113/// let mut overlapping = vec![];
114/// for year in 1900..=1999 {
115///     let date = date(year, 1, 1);
116///     if date.in_leap_year() && date.iso_week_date().in_long_year() {
117///         overlapping.push(year);
118///     }
119/// }
120/// assert_eq!(overlapping, vec![
121///     1904, 1908, 1920, 1932, 1936, 1948, 1960, 1964, 1976, 1988, 1992,
122/// ]);
123/// ```
124///
125/// # Example: printing all weeks in a year
126///
127/// The ISO 8601 week calendar can be useful when you want to categorize
128/// things into buckets of weeks where all weeks are exactly 7 days, _and_
129/// you don't care as much about the precise Gregorian year. Here's an example
130/// that prints all of the ISO 8601 weeks in one ISO 8601 week year:
131///
132/// ```
133/// use jiff::{civil::{ISOWeekDate, Weekday}, ToSpan};
134///
135/// let target_year = 2024;
136/// let iso_week_date = ISOWeekDate::new(target_year, 1, Weekday::Monday)?;
137/// // Create a series of dates via the Gregorian calendar. But since a
138/// // Gregorian week and an ISO 8601 week calendar week are both 7 days,
139/// // this works fine.
140/// let weeks = iso_week_date
141///     .date()
142///     .series(1.week())
143///     .map(|d| d.iso_week_date())
144///     .take_while(|wd| wd.year() == target_year);
145/// for start_of_week in weeks {
146///     let end_of_week = start_of_week.last_of_week()?;
147///     println!(
148///         "ISO week {}: {} - {}",
149///         start_of_week.week(),
150///         start_of_week.date(),
151///         end_of_week.date()
152///     );
153/// }
154/// # Ok::<(), Box<dyn std::error::Error>>(())
155/// ```
156#[derive(Clone, Copy, Eq, Hash, PartialEq)]
157#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
158pub struct ISOWeekDate {
159    pub(crate) inner: JISOWeekDate,
160}
161
162impl ISOWeekDate {
163    /// The maximum representable ISO week date.
164    ///
165    /// The maximum corresponds to the ISO week date of the maximum [`Date`]
166    /// value. That is, `-9999-01-01`.
167    pub const MIN: ISOWeekDate = ISOWeekDate { inner: JISOWeekDate::MIN };
168
169    /// The minimum representable ISO week date.
170    ///
171    /// The minimum corresponds to the ISO week date of the minimum [`Date`]
172    /// value. That is, `9999-12-31`.
173    pub const MAX: ISOWeekDate = ISOWeekDate { inner: JISOWeekDate::MAX };
174
175    /// The first day of the zeroth year.
176    ///
177    /// This is guaranteed to be equivalent to `ISOWeekDate::default()`. Note
178    /// that this is not equivalent to `Date::default()`.
179    ///
180    /// # Example
181    ///
182    /// ```
183    /// use jiff::civil::{ISOWeekDate, date};
184    ///
185    /// assert_eq!(ISOWeekDate::ZERO, ISOWeekDate::default());
186    /// // The first day of the 0th year in the ISO week calendar is actually
187    /// // the third day of the 0th year in the proleptic Gregorian calendar!
188    /// assert_eq!(ISOWeekDate::default().date(), date(0, 1, 3));
189    /// ```
190    pub const ZERO: ISOWeekDate = ISOWeekDate { inner: JISOWeekDate::ZERO };
191
192    /// Create a new ISO week date from it constituent parts.
193    ///
194    /// If the given values are out of range (based on what is representable
195    /// as a [`Date`]), then this returns an error. This will also return an
196    /// error if a leap week is given (week number `53`) for a year that does
197    /// not contain a leap week.
198    ///
199    /// # Example
200    ///
201    /// This example shows some the boundary conditions involving minimum
202    /// and maximum dates:
203    ///
204    /// ```
205    /// use jiff::civil::{ISOWeekDate, Weekday, date};
206    ///
207    /// // The year 1949 does not contain a leap week.
208    /// assert!(ISOWeekDate::new(1949, 53, Weekday::Monday).is_err());
209    ///
210    /// // Examples of dates at or exceeding the maximum.
211    /// let max = ISOWeekDate::new(9999, 52, Weekday::Friday).unwrap();
212    /// assert_eq!(max, ISOWeekDate::MAX);
213    /// assert_eq!(max.date(), date(9999, 12, 31));
214    /// assert!(ISOWeekDate::new(9999, 52, Weekday::Saturday).is_err());
215    /// assert!(ISOWeekDate::new(9999, 53, Weekday::Monday).is_err());
216    ///
217    /// // Examples of dates at or exceeding the minimum.
218    /// let min = ISOWeekDate::new(-9999, 1, Weekday::Monday).unwrap();
219    /// assert_eq!(min, ISOWeekDate::MIN);
220    /// assert_eq!(min.date(), date(-9999, 1, 1));
221    /// assert!(ISOWeekDate::new(-10000, 52, Weekday::Sunday).is_err());
222    /// ```
223    #[inline]
224    pub fn new(
225        year: i16,
226        week: i8,
227        weekday: Weekday,
228    ) -> Result<ISOWeekDate, Error> {
229        JISOWeekDate::new(year, week, weekday.to_jcore())
230            .map(ISOWeekDate::from_jcore)
231            .map_err(Error::jcore_range)
232    }
233
234    /// Converts a Gregorian date to an ISO week date.
235    ///
236    /// The minimum and maximum allowed values of an ISO week date are
237    /// set based on the minimum and maximum values of a `Date`. Therefore,
238    /// converting to and from `Date` values is non-lossy and infallible.
239    ///
240    /// This routine is equivalent to [`Date::iso_week_date`]. This routine
241    /// is also available via a `From<Date>` trait implementation for
242    /// `ISOWeekDate`.
243    ///
244    /// # Example
245    ///
246    /// ```
247    /// use jiff::civil::{ISOWeekDate, Weekday, date};
248    ///
249    /// let weekdate = ISOWeekDate::from_date(date(1948, 2, 10));
250    /// assert_eq!(
251    ///     weekdate,
252    ///     ISOWeekDate::new(1948, 7, Weekday::Tuesday).unwrap(),
253    /// );
254    /// ```
255    #[inline]
256    pub fn from_date(date: Date) -> ISOWeekDate {
257        date.iso_week_date()
258    }
259
260    // N.B. I tried defining a `ISOWeekDate::constant` for defining ISO week
261    // dates as constants, but it was too annoying to do. We could do it if
262    // there was a compelling reason for it though.
263
264    /// Returns the year component of this ISO 8601 week date.
265    ///
266    /// The value returned is guaranteed to be in the range `-9999..=9999`.
267    ///
268    /// # Example
269    ///
270    /// ```
271    /// use jiff::civil::date;
272    ///
273    /// let weekdate = date(2019, 12, 30).iso_week_date();
274    /// assert_eq!(weekdate.year(), 2020);
275    /// ```
276    #[inline]
277    pub fn year(self) -> i16 {
278        self.inner.year()
279    }
280
281    /// Returns the week component of this ISO 8601 week date.
282    ///
283    /// The value returned is guaranteed to be in the range `1..=53`. A
284    /// value of `53` can only occur for "long" years. That is, years
285    /// with a leap week. This occurs precisely in cases for which
286    /// [`ISOWeekDate::in_long_year`] returns `true`.
287    ///
288    /// # Example
289    ///
290    /// ```
291    /// use jiff::civil::date;
292    ///
293    /// let weekdate = date(2019, 12, 30).iso_week_date();
294    /// assert_eq!(weekdate.year(), 2020);
295    /// assert_eq!(weekdate.week(), 1);
296    ///
297    /// let weekdate = date(1948, 12, 31).iso_week_date();
298    /// assert_eq!(weekdate.year(), 1948);
299    /// assert_eq!(weekdate.week(), 53);
300    /// ```
301    #[inline]
302    pub fn week(self) -> i8 {
303        self.inner.week()
304    }
305
306    /// Returns the day component of this ISO 8601 week date.
307    ///
308    /// One can use methods on `Weekday` such as
309    /// [`Weekday::to_monday_one_offset`]
310    /// and
311    /// [`Weekday::to_sunday_zero_offset`]
312    /// to convert the weekday to a number.
313    ///
314    /// # Example
315    ///
316    /// ```
317    /// use jiff::civil::{date, Weekday};
318    ///
319    /// let weekdate = date(1948, 12, 31).iso_week_date();
320    /// assert_eq!(weekdate.year(), 1948);
321    /// assert_eq!(weekdate.week(), 53);
322    /// assert_eq!(weekdate.weekday(), Weekday::Friday);
323    /// assert_eq!(weekdate.weekday().to_monday_zero_offset(), 4);
324    /// assert_eq!(weekdate.weekday().to_monday_one_offset(), 5);
325    /// assert_eq!(weekdate.weekday().to_sunday_zero_offset(), 5);
326    /// assert_eq!(weekdate.weekday().to_sunday_one_offset(), 6);
327    /// ```
328    #[inline]
329    pub fn weekday(self) -> Weekday {
330        Weekday::from_jcore(self.inner.weekday())
331    }
332
333    /// Returns the ISO 8601 week date corresponding to the first day in the
334    /// week of this week date. The date returned is guaranteed to have a
335    /// weekday of [`Weekday::Monday`].
336    ///
337    /// # Errors
338    ///
339    /// Since `-9999-01-01` falls on a Monday, it follows that the minimum
340    /// supported Gregorian date is exactly equivalent to the minimum supported
341    /// ISO 8601 week date. This means that this routine can never actually
342    /// fail, but only insomuch as the minimums line up. For that reason, and
343    /// for consistency with [`ISOWeekDate::last_of_week`], the API is
344    /// fallible.
345    ///
346    /// # Example
347    ///
348    /// ```
349    /// use jiff::civil::{ISOWeekDate, Weekday, date};
350    ///
351    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
352    /// assert_eq!(wd.date(), date(2025, 1, 29));
353    /// assert_eq!(
354    ///     wd.first_of_week()?,
355    ///     ISOWeekDate::new(2025, 5, Weekday::Monday).unwrap(),
356    /// );
357    ///
358    /// // Works even for the minimum date.
359    /// assert_eq!(
360    ///     ISOWeekDate::MIN.first_of_week()?,
361    ///     ISOWeekDate::new(-9999, 1, Weekday::Monday).unwrap(),
362    /// );
363    ///
364    /// # Ok::<(), Box<dyn std::error::Error>>(())
365    /// ```
366    #[inline]
367    pub fn first_of_week(self) -> Result<ISOWeekDate, Error> {
368        self.inner
369            .first_of_week()
370            .map(ISOWeekDate::from_jcore)
371            .map_err(Error::jcore_range)
372    }
373
374    /// Returns the ISO 8601 week date corresponding to the last day in the
375    /// week of this week date. The date returned is guaranteed to have a
376    /// weekday of [`Weekday::Sunday`].
377    ///
378    /// # Errors
379    ///
380    /// This can return an error if the last day of the week exceeds Jiff's
381    /// maximum Gregorian date of `9999-12-31`. It turns out this can happen
382    /// since `9999-12-31` falls on a Friday.
383    ///
384    /// # Example
385    ///
386    /// ```
387    /// use jiff::civil::{ISOWeekDate, Weekday, date};
388    ///
389    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
390    /// assert_eq!(wd.date(), date(2025, 1, 29));
391    /// assert_eq!(
392    ///     wd.last_of_week()?,
393    ///     ISOWeekDate::new(2025, 5, Weekday::Sunday).unwrap(),
394    /// );
395    ///
396    /// // Unlike `first_of_week`, this routine can actually fail on real
397    /// // values, although, only when close to the maximum supported date.
398    /// assert_eq!(
399    ///     ISOWeekDate::MAX.last_of_week().unwrap_err().to_string(),
400    ///     "parameter 'weekday (Monday 1-indexed)' \
401    ///      is not in the required range of 1..=7",
402    /// );
403    ///
404    /// # Ok::<(), Box<dyn std::error::Error>>(())
405    /// ```
406    #[inline]
407    pub fn last_of_week(self) -> Result<ISOWeekDate, Error> {
408        self.inner
409            .last_of_week()
410            .map(ISOWeekDate::from_jcore)
411            .map_err(Error::jcore_range)
412    }
413
414    /// Returns the ISO 8601 week date corresponding to the first day in the
415    /// year of this week date. The date returned is guaranteed to have a
416    /// weekday of [`Weekday::Monday`].
417    ///
418    /// # Errors
419    ///
420    /// Since `-9999-01-01` falls on a Monday, it follows that the minimum
421    /// support Gregorian date is exactly equivalent to the minimum supported
422    /// ISO 8601 week date. This means that this routine can never actually
423    /// fail, but only insomuch as the minimums line up. For that reason, and
424    /// for consistency with [`ISOWeekDate::last_of_year`], the API is
425    /// fallible.
426    ///
427    /// # Example
428    ///
429    /// ```
430    /// use jiff::civil::{ISOWeekDate, Weekday, date};
431    ///
432    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
433    /// assert_eq!(wd.date(), date(2025, 1, 29));
434    /// assert_eq!(
435    ///     wd.first_of_year()?,
436    ///     ISOWeekDate::new(2025, 1, Weekday::Monday).unwrap(),
437    /// );
438    ///
439    /// // Works even for the minimum date.
440    /// assert_eq!(
441    ///     ISOWeekDate::MIN.first_of_year()?,
442    ///     ISOWeekDate::new(-9999, 1, Weekday::Monday).unwrap(),
443    /// );
444    ///
445    /// # Ok::<(), Box<dyn std::error::Error>>(())
446    /// ```
447    #[inline]
448    pub fn first_of_year(self) -> Result<ISOWeekDate, Error> {
449        self.inner
450            .first_of_year()
451            .map(ISOWeekDate::from_jcore)
452            .map_err(Error::jcore_range)
453    }
454
455    /// Returns the ISO 8601 week date corresponding to the last day in the
456    /// year of this week date. The date returned is guaranteed to have a
457    /// weekday of [`Weekday::Sunday`].
458    ///
459    /// # Errors
460    ///
461    /// This can return an error if the last day of the year exceeds Jiff's
462    /// maximum Gregorian date of `9999-12-31`. It turns out this can happen
463    /// since `9999-12-31` falls on a Friday.
464    ///
465    /// # Example
466    ///
467    /// ```
468    /// use jiff::civil::{ISOWeekDate, Weekday, date};
469    ///
470    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
471    /// assert_eq!(wd.date(), date(2025, 1, 29));
472    /// assert_eq!(
473    ///     wd.last_of_year()?,
474    ///     ISOWeekDate::new(2025, 52, Weekday::Sunday).unwrap(),
475    /// );
476    ///
477    /// // Works correctly for "long" years.
478    /// let wd = ISOWeekDate::new(2026, 5, Weekday::Wednesday).unwrap();
479    /// assert_eq!(wd.date(), date(2026, 1, 28));
480    /// assert_eq!(
481    ///     wd.last_of_year()?,
482    ///     ISOWeekDate::new(2026, 53, Weekday::Sunday).unwrap(),
483    /// );
484    ///
485    /// // Unlike `first_of_year`, this routine can actually fail on real
486    /// // values, although, only when close to the maximum supported date.
487    /// assert_eq!(
488    ///     ISOWeekDate::MAX.last_of_year().unwrap_err().to_string(),
489    ///     "parameter 'weekday (Monday 1-indexed)' \
490    ///      is not in the required range of 1..=7",
491    /// );
492    ///
493    /// # Ok::<(), Box<dyn std::error::Error>>(())
494    /// ```
495    #[inline]
496    pub fn last_of_year(self) -> Result<ISOWeekDate, Error> {
497        self.inner
498            .last_of_year()
499            .map(ISOWeekDate::from_jcore)
500            .map_err(Error::jcore_range)
501    }
502
503    /// Returns the total number of days in the year of this ISO 8601 week
504    /// date.
505    ///
506    /// It is guaranteed that the value returned is either 364 or 371. The
507    /// latter case occurs precisely when [`ISOWeekDate::in_long_year`]
508    /// returns `true`.
509    ///
510    /// # Example
511    ///
512    /// ```
513    /// use jiff::civil::{ISOWeekDate, Weekday};
514    ///
515    /// let weekdate = ISOWeekDate::new(2025, 7, Weekday::Monday).unwrap();
516    /// assert_eq!(weekdate.days_in_year(), 364);
517    /// let weekdate = ISOWeekDate::new(2026, 7, Weekday::Monday).unwrap();
518    /// assert_eq!(weekdate.days_in_year(), 371);
519    /// ```
520    #[inline]
521    pub fn days_in_year(self) -> i16 {
522        self.inner.days_in_year()
523    }
524
525    /// Returns the total number of weeks in the year of this ISO 8601 week
526    /// date.
527    ///
528    /// It is guaranteed that the value returned is either 52 or 53. The
529    /// latter case occurs precisely when [`ISOWeekDate::in_long_year`]
530    /// returns `true`.
531    ///
532    /// # Example
533    ///
534    /// ```
535    /// use jiff::civil::{ISOWeekDate, Weekday};
536    ///
537    /// let weekdate = ISOWeekDate::new(2025, 7, Weekday::Monday).unwrap();
538    /// assert_eq!(weekdate.weeks_in_year(), 52);
539    /// let weekdate = ISOWeekDate::new(2026, 7, Weekday::Monday).unwrap();
540    /// assert_eq!(weekdate.weeks_in_year(), 53);
541    /// ```
542    #[inline]
543    pub fn weeks_in_year(self) -> i8 {
544        self.inner.weeks_in_year()
545    }
546
547    /// Returns true if and only if the year of this week date is a "long"
548    /// year.
549    ///
550    /// A long year is one that contains precisely 53 weeks. All other years
551    /// contain precisely 52 weeks.
552    ///
553    /// # Example
554    ///
555    /// ```
556    /// use jiff::civil::{ISOWeekDate, Weekday};
557    ///
558    /// let weekdate = ISOWeekDate::new(1948, 7, Weekday::Monday).unwrap();
559    /// assert!(weekdate.in_long_year());
560    /// let weekdate = ISOWeekDate::new(1949, 7, Weekday::Monday).unwrap();
561    /// assert!(!weekdate.in_long_year());
562    /// ```
563    #[inline]
564    pub fn in_long_year(self) -> bool {
565        self.inner.in_long_year()
566    }
567
568    /// Returns the ISO 8601 date immediately following this one.
569    ///
570    /// # Errors
571    ///
572    /// This returns an error when this date is the maximum value.
573    ///
574    /// # Example
575    ///
576    /// ```
577    /// use jiff::civil::{ISOWeekDate, Weekday};
578    ///
579    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
580    /// assert_eq!(
581    ///     wd.tomorrow()?,
582    ///     ISOWeekDate::new(2025, 5, Weekday::Thursday).unwrap(),
583    /// );
584    ///
585    /// // The max doesn't have a tomorrow.
586    /// assert!(ISOWeekDate::MAX.tomorrow().is_err());
587    ///
588    /// # Ok::<(), Box<dyn std::error::Error>>(())
589    /// ```
590    #[inline]
591    pub fn tomorrow(self) -> Result<ISOWeekDate, Error> {
592        self.inner
593            .tomorrow()
594            .map(ISOWeekDate::from_jcore)
595            .map_err(Error::jcore_range)
596    }
597
598    /// Returns the ISO 8601 week date immediately preceding this one.
599    ///
600    /// # Errors
601    ///
602    /// This returns an error when this date is the minimum value.
603    ///
604    /// # Example
605    ///
606    /// ```
607    /// use jiff::civil::{ISOWeekDate, Weekday};
608    ///
609    /// let wd = ISOWeekDate::new(2025, 5, Weekday::Wednesday).unwrap();
610    /// assert_eq!(
611    ///     wd.yesterday()?,
612    ///     ISOWeekDate::new(2025, 5, Weekday::Tuesday).unwrap(),
613    /// );
614    ///
615    /// // The min doesn't have a yesterday.
616    /// assert!(ISOWeekDate::MIN.yesterday().is_err());
617    ///
618    /// # Ok::<(), Box<dyn std::error::Error>>(())
619    /// ```
620    #[inline]
621    pub fn yesterday(self) -> Result<ISOWeekDate, Error> {
622        self.inner
623            .yesterday()
624            .map(ISOWeekDate::from_jcore)
625            .map_err(Error::jcore_range)
626    }
627
628    /// Converts this ISO week date to a Gregorian [`Date`].
629    ///
630    /// The minimum and maximum allowed values of an ISO week date are
631    /// set based on the minimum and maximum values of a `Date`. Therefore,
632    /// converting to and from `Date` values is non-lossy and infallible.
633    ///
634    /// This routine is equivalent to [`Date::from_iso_week_date`].
635    ///
636    /// # Example
637    ///
638    /// ```
639    /// use jiff::civil::{ISOWeekDate, Weekday, date};
640    ///
641    /// let weekdate = ISOWeekDate::new(1948, 7, Weekday::Tuesday).unwrap();
642    /// assert_eq!(weekdate.date(), date(1948, 2, 10));
643    /// ```
644    #[inline]
645    pub fn date(self) -> Date {
646        Date::from_iso_week_date(self)
647    }
648
649    #[inline]
650    pub(crate) const fn from_jcore(week_date: JISOWeekDate) -> ISOWeekDate {
651        ISOWeekDate { inner: week_date }
652    }
653
654    #[inline]
655    pub(crate) const fn to_jcore(self) -> JISOWeekDate {
656        self.inner
657    }
658}
659
660impl Default for ISOWeekDate {
661    fn default() -> ISOWeekDate {
662        ISOWeekDate::ZERO
663    }
664}
665
666impl core::fmt::Debug for ISOWeekDate {
667    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
668        core::fmt::Display::fmt(self, f)
669    }
670}
671
672impl core::fmt::Display for ISOWeekDate {
673    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
674        use crate::fmt::StdFmtWrite;
675
676        DEFAULT_DATETIME_PRINTER
677            .print_iso_week_date(self, StdFmtWrite(f))
678            .map_err(|_| core::fmt::Error)
679    }
680}
681
682impl core::str::FromStr for ISOWeekDate {
683    type Err = Error;
684
685    fn from_str(string: &str) -> Result<ISOWeekDate, Error> {
686        DEFAULT_DATETIME_PARSER.parse_iso_week_date(string)
687    }
688}
689
690impl Ord for ISOWeekDate {
691    #[inline]
692    fn cmp(&self, other: &ISOWeekDate) -> core::cmp::Ordering {
693        (self.year(), self.week(), self.weekday().to_monday_one_offset()).cmp(
694            &(
695                other.year(),
696                other.week(),
697                other.weekday().to_monday_one_offset(),
698            ),
699        )
700    }
701}
702
703impl PartialOrd for ISOWeekDate {
704    #[inline]
705    fn partial_cmp(&self, other: &ISOWeekDate) -> Option<core::cmp::Ordering> {
706        Some(self.cmp(other))
707    }
708}
709
710impl From<Date> for ISOWeekDate {
711    #[inline]
712    fn from(date: Date) -> ISOWeekDate {
713        ISOWeekDate::from_date(date)
714    }
715}
716
717impl From<DateTime> for ISOWeekDate {
718    #[inline]
719    fn from(dt: DateTime) -> ISOWeekDate {
720        ISOWeekDate::from(dt.date())
721    }
722}
723
724impl From<Zoned> for ISOWeekDate {
725    #[inline]
726    fn from(zdt: Zoned) -> ISOWeekDate {
727        ISOWeekDate::from(zdt.date())
728    }
729}
730
731impl<'a> From<&'a Zoned> for ISOWeekDate {
732    #[inline]
733    fn from(zdt: &'a Zoned) -> ISOWeekDate {
734        ISOWeekDate::from(zdt.date())
735    }
736}
737
738#[cfg(feature = "defmt")]
739impl defmt::Format for ISOWeekDate {
740    fn format(&self, f: defmt::Formatter) {
741        use crate::fmt::DefmtWrite;
742
743        defmt::unwrap!(
744            DEFAULT_DATETIME_PRINTER.print_iso_week_date(self, DefmtWrite(f))
745        );
746    }
747}
748
749#[cfg(feature = "serde")]
750impl serde_core::Serialize for ISOWeekDate {
751    #[inline]
752    fn serialize<S: serde_core::Serializer>(
753        &self,
754        serializer: S,
755    ) -> Result<S::Ok, S::Error> {
756        serializer.collect_str(self)
757    }
758}
759
760#[cfg(feature = "serde")]
761impl<'de> serde_core::Deserialize<'de> for ISOWeekDate {
762    #[inline]
763    fn deserialize<D: serde_core::Deserializer<'de>>(
764        deserializer: D,
765    ) -> Result<ISOWeekDate, D::Error> {
766        use serde_core::de;
767
768        struct ISOWeekDateVisitor;
769
770        impl<'de> de::Visitor<'de> for ISOWeekDateVisitor {
771            type Value = ISOWeekDate;
772
773            fn expecting(
774                &self,
775                f: &mut core::fmt::Formatter,
776            ) -> core::fmt::Result {
777                f.write_str("an ISO 8601 week date string")
778            }
779
780            #[inline]
781            fn visit_bytes<E: de::Error>(
782                self,
783                value: &[u8],
784            ) -> Result<ISOWeekDate, E> {
785                DEFAULT_DATETIME_PARSER
786                    .parse_iso_week_date(value)
787                    .map_err(de::Error::custom)
788            }
789
790            #[inline]
791            fn visit_str<E: de::Error>(
792                self,
793                value: &str,
794            ) -> Result<ISOWeekDate, E> {
795                self.visit_bytes(value.as_bytes())
796            }
797        }
798
799        deserializer.deserialize_str(ISOWeekDateVisitor)
800    }
801}