Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#654] Get week start and end days #666

Merged
merged 4 commits into from Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
target
Cargo.lock
.tool-versions
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Expand Up @@ -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")]
Expand Down
96 changes: 94 additions & 2 deletions src/naive/date.rs
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<T>`] 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<NaiveDate> {
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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
}
}
}
2 changes: 1 addition & 1 deletion src/naive/mod.rs
Expand Up @@ -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;
Expand Down