diff --git a/.gitignore b/.gitignore index a9d37c560c..9fac28caba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +.tool-versions diff --git a/CHANGELOG.md b/CHANGELOG.md index 7562212808..f6a1a3bd19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ Versions with only mechanical changes will be omitted from the following list. * Make `ParseErrorKind` public and available through `ParseError::kind()` (#588) * Implement `DoubleEndedIterator` for `NaiveDateDaysIterator` and `NaiveDateWeeksIterator` * Fix panicking when parsing a `DateTime` (@botahamec) +* Add support for getting week bounds based on a specific `NaiveDate` and a `Weekday` (#666) ## 0.4.19 diff --git a/src/lib.rs b/src/lib.rs index bf193b8a12..1bd4bb937a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -487,7 +487,7 @@ pub use format::{ParseError, ParseResult}; pub mod naive; #[doc(no_inline)] -pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime}; +pub use naive::{IsoWeek, NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek}; pub mod offset; #[cfg(feature = "clock")] diff --git a/src/naive/date.rs b/src/naive/date.rs index 08c6869323..3e566b75aa 100644 --- a/src/naive/date.rs +++ b/src/naive/date.rs @@ -5,7 +5,7 @@ #[cfg(any(feature = "alloc", feature = "std", test))] use core::borrow::Borrow; -use core::ops::{Add, AddAssign, Sub, SubAssign}; +use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::{fmt, str}; use num_integer::div_mod_floor; @@ -19,7 +19,7 @@ use crate::format::{parse, ParseError, ParseResult, Parsed, StrftimeItems}; use crate::format::{Item, Numeric, Pad}; use crate::naive::{IsoWeek, NaiveDateTime, NaiveTime}; use crate::oldtime::Duration as OldDuration; -use crate::{Datelike, Weekday}; +use crate::{Datelike, Duration, Weekday}; use super::internals::{self, DateImpl, Mdf, Of, YearFlags}; use super::isoweek; @@ -50,6 +50,70 @@ const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 + (MIN_YEAR + 400_0 #[cfg(test)] // only used for testing, but duplicated in naive::datetime const MAX_BITS: usize = 44; +/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first +/// day of the week. +#[derive(Debug)] +pub struct NaiveWeek { + date: NaiveDate, + start: Weekday, +} + +impl NaiveWeek { + /// Returns a date representing the first day of the week. + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd(2022, 4, 18); + /// let week = date.week(Weekday::Mon); + /// assert!(week.first_day() <= date); + /// ``` + #[inline] + pub fn first_day(&self) -> NaiveDate { + let start = self.start.num_days_from_monday(); + let end = self.date.weekday().num_days_from_monday(); + let days = if start > end { 7 - start + end } else { end - start }; + self.date - Duration::days(days.into()) + } + + /// Returns a date representing the last day of the week. + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd(2022, 4, 18); + /// let week = date.week(Weekday::Mon); + /// assert!(week.last_day() >= date); + /// ``` + #[inline] + pub fn last_day(&self) -> NaiveDate { + self.first_day() + Duration::days(6) + } + + /// Returns a [`RangeInclusive`] representing the whole week bounded by + /// [first_day](./struct.NaiveWeek.html#method.first_day) and + /// [last_day](./struct.NaiveWeek.html#method.last_day) functions. + /// + /// # Examples + /// + /// ``` + /// use chrono::{NaiveDate, Weekday}; + /// + /// let date = NaiveDate::from_ymd(2022, 4, 18); + /// let week = date.week(Weekday::Mon); + /// let days = week.days(); + /// assert!(days.contains(&date)); + /// ``` + #[inline] + pub fn days(&self) -> RangeInclusive { + self.first_day()..=self.last_day() + } +} + /// ISO 8601 calendar date without timezone. /// Allows for every [proleptic Gregorian date](#calendar-date) /// from Jan 1, 262145 BCE to Dec 31, 262143 CE. @@ -1105,6 +1169,13 @@ impl NaiveDate { pub fn iter_weeks(&self) -> NaiveDateWeeksIterator { NaiveDateWeeksIterator { value: *self } } + + /// Returns the [`NaiveWeek`] that the date belongs to, starting with the [`Weekday`] + /// specified. + #[inline] + pub fn week(&self, start: Weekday) -> NaiveWeek { + NaiveWeek { date: *self, start } + } } impl Datelike for NaiveDate { @@ -2445,4 +2516,25 @@ mod tests { assert_eq!(NaiveDate::from_ymd(262143, 12, 12).iter_weeks().take(4).count(), 2); assert_eq!(NaiveDate::from_ymd(-262144, 1, 15).iter_weeks().rev().take(4).count(), 2); } + + #[test] + fn test_naiveweek() { + let date = NaiveDate::from_ymd(2022, 5, 18); + let asserts = vec![ + (Weekday::Mon, "2022-05-16", "2022-05-22"), + (Weekday::Tue, "2022-05-17", "2022-05-23"), + (Weekday::Wed, "2022-05-18", "2022-05-24"), + (Weekday::Thu, "2022-05-12", "2022-05-18"), + (Weekday::Fri, "2022-05-13", "2022-05-19"), + (Weekday::Sat, "2022-05-14", "2022-05-20"), + (Weekday::Sun, "2022-05-15", "2022-05-21"), + ]; + for (start, first_day, last_day) in asserts { + let week = date.week(start); + let days = week.days(); + assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%Y-%m-%d")); + assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%Y-%m-%d")); + assert!(days.contains(&date)); + } + } } diff --git a/src/naive/mod.rs b/src/naive/mod.rs index f23a731a76..171cac36bb 100644 --- a/src/naive/mod.rs +++ b/src/naive/mod.rs @@ -10,7 +10,7 @@ mod internals; mod isoweek; mod time; -pub use self::date::{NaiveDate, MAX_DATE, MIN_DATE}; +pub use self::date::{NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE}; #[cfg(feature = "rustc-serialize")] #[allow(deprecated)] pub use self::datetime::rustc_serialize::TsSeconds;