Skip to content

Commit

Permalink
[#654] Get week start and end days
Browse files Browse the repository at this point in the history
Co-authored-by: David Mazarro <dmunuera@stackbuilders.com>
Co-authored-by: Jorge Guerra <jguerra@stackbuilders.com>
  • Loading branch information
3 people committed May 17, 2022
1 parent 7097f93 commit b274146
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 10 deletions.
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 @@ -25,6 +25,7 @@ Versions with only mechanical changes will be omitted from the following list.
* Add support for microseconds timestamps serde serialization for `NaiveDateTime`.
* Add support for optional timestamps serde serialization for `NaiveDateTime`.
* Fix build for wasm32-unknown-emscripten (@yu-re-ka #593)
* Add support for getting week bounds based on a specific date and a `Weekday` for `NaiveDate` and `Date` (#666)

## 0.4.19

Expand Down
52 changes: 51 additions & 1 deletion src/date.rs
Expand Up @@ -16,12 +16,29 @@ use rkyv::{Archive, Deserialize, Serialize};
use crate::format::Locale;
#[cfg(any(feature = "alloc", feature = "std", test))]
use crate::format::{DelayedFormat, Item, StrftimeItems};
use crate::naive::{self, IsoWeek, NaiveDate, NaiveTime};
use crate::naive::{self, IsoWeek, NaiveDate, NaiveTime, NaiveWeek};
use crate::offset::{TimeZone, Utc};
use crate::oldtime::Duration as OldDuration;
use crate::traits::Weeklike;
use crate::DateTime;
use crate::{Datelike, Weekday};

/// A week represented by a [`NaiveWeek`] + [Offset](crate::offset::Offset).
#[derive(Debug)]
pub struct Week<Tz: TimeZone> {
week: NaiveWeek,
offset: Tz::Offset,
}

impl<Tz: TimeZone> Weeklike for Week<Tz> {
type Day = Date<Tz>;

#[inline]
fn first_day(&self) -> Self::Day {
Date::from_utc(self.week.first_day(), self.offset.clone())
}
}

/// ISO 8601 calendar date with time zone.
///
/// You almost certainly want to be using a [`NaiveDate`] instead of this type.
Expand Down Expand Up @@ -275,6 +292,13 @@ impl<Tz: TimeZone> Date<Tz> {
pub fn naive_local(&self) -> NaiveDate {
self.date
}

/// Returns the [`Week<Tz>`] that the date belongs, starting with the
/// [`Weekday`] specified.
#[inline]
pub fn week(&self, weekday: Weekday) -> Week<Tz> {
Week { week: self.date.week(weekday), offset: self.offset.clone() }
}
}

/// Maps the local date to other date with given conversion function.
Expand Down Expand Up @@ -498,3 +522,29 @@ where
write!(f, "{}{}", self.naive_local(), self.offset)
}
}

#[cfg(test)]
mod tests {
use crate::{Local, Weekday, Weeklike};

#[test]
fn test_week() {
let weekdays = [
Weekday::Mon,
Weekday::Tue,
Weekday::Wed,
Weekday::Thu,
Weekday::Fri,
Weekday::Sat,
Weekday::Sun,
];
let date = Local::today();
for start in weekdays {
let week = date.week(start);
let days = week.days();
assert!(week.first_day() <= date);
assert!(week.last_day() >= date);
assert!(days.contains(&date));
}
}
}
6 changes: 3 additions & 3 deletions src/lib.rs
Expand Up @@ -472,7 +472,7 @@ macro_rules! try_opt {
}

mod date;
pub use date::{Date, MAX_DATE, MIN_DATE};
pub use date::{Date, Week, MAX_DATE, MIN_DATE};

mod datetime;
#[cfg(feature = "rustc-serialize")]
Expand All @@ -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 All @@ -506,7 +506,7 @@ mod month;
pub use month::{Month, ParseMonthError};

mod traits;
pub use traits::{Datelike, Timelike};
pub use traits::{Datelike, Timelike, Weeklike};

#[cfg(feature = "__internal_bench")]
#[doc(hidden)]
Expand Down
56 changes: 54 additions & 2 deletions src/naive/date.rs
Expand Up @@ -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, Weeklike};

use super::internals::{self, DateImpl, Mdf, Of, YearFlags};
use super::isoweek;
Expand Down Expand Up @@ -50,6 +50,26 @@ 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 Weeklike for NaiveWeek {
type Day = NaiveDate;

#[inline]
fn first_day(&self) -> Self::Day {
let start = self.start.num_days_from_monday();
let end = self.date.weekday().num_days_from_monday();
let days = if start > end { 7 - end } else { end - start };
self.date - Duration::days(days.into())
}
}

/// 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 @@ -1095,6 +1115,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 @@ -1894,7 +1921,7 @@ mod tests {
use super::{MAX_DATE, MAX_DAYS_FROM_YEAR_0, MAX_YEAR};
use super::{MIN_DATE, MIN_DAYS_FROM_YEAR_0, MIN_YEAR};
use crate::oldtime::Duration;
use crate::{Datelike, Weekday};
use crate::{Datelike, Weekday, Weeklike};
use std::{i32, u32};

#[test]
Expand Down Expand Up @@ -2411,4 +2438,29 @@ mod tests {
fn test_week_iterator_limit() {
assert_eq!(NaiveDate::from_ymd(262143, 12, 12).iter_weeks().take(4).count(), 2);
}

#[test]
fn test_naiveweek_less_than_weekday() {
let date = NaiveDate::from_ymd(2022, 5, 18);
let week = date.week(Weekday::Mon);
assert_eq!(week.first_day(), NaiveDate::from_ymd(2022, 5, 16));
assert_eq!(week.last_day(), NaiveDate::from_ymd(2022, 5, 22));
}

#[test]
fn test_naiveweek_equal_to_weekday() {
let date = NaiveDate::from_ymd(2022, 5, 18);
let week = date.week(Weekday::Wed);
assert_eq!(week.first_day(), date);
assert_eq!(week.last_day(), NaiveDate::from_ymd(2022, 5, 24));
}

/// TODO: Fix this test
#[test]
fn test_naiveweek_greater_than_weekday() {
let date = NaiveDate::from_ymd(2022, 5, 18);
let week = date.week(Weekday::Fri);
assert_eq!(week.first_day(), NaiveDate::from_ymd(2022, 5, 13));
assert_eq!(week.last_day(), NaiveDate::from_ymd(2022, 5, 19));
}
}
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
96 changes: 93 additions & 3 deletions src/traits.rs
@@ -1,4 +1,63 @@
use crate::{IsoWeek, Weekday};
use core::ops::{Add, RangeInclusive};

use crate::{Duration, IsoWeek, Weekday};

/// The common set of methods for a week component.
pub trait Weeklike: Sized {
/// An associated type, representing a day within the context of a week.
type Day: Add<Duration, Output = Self::Day>;

/// Returns a date representing the first day of the week.
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Weekday, Weeklike};
///
/// let date = NaiveDate::from_ymd(2022, 4, 18);
/// let week = date.week(Weekday::Mon);
/// assert!(week.first_day() <= date);
/// ```
fn first_day(&self) -> Self::Day;

/// Returns a date representing the last day of the week.
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Weekday, Weeklike};
///
/// let date = NaiveDate::from_ymd(2022, 4, 18);
/// let week = date.week(Weekday::Mon);
/// assert!(week.last_day() >= date);
/// ```
#[inline]
fn last_day(&self) -> Self::Day {
self.first_day() + Duration::days(6)
}

/// Returns a [`RangeInclusive<T>`] representing the whole week bounded by
/// [first_day()][first_day] and [last_day()][last_day] functions.
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Weekday, Weeklike};
///
/// let date = NaiveDate::from_ymd(2022, 4, 18);
/// let week = date.week(Weekday::Mon);
/// let days = week.days();
/// assert_eq!(days.start(), &week.first_day());
/// assert_eq!(days.end(), &week.last_day());
/// ```
///
/// [first_day]: ./trait.Weeklike.html#tymethod.first_day
/// [last_day]: ./trait.Weeklike.html#method.last_day
#[inline]
fn days(&self) -> RangeInclusive<Self::Day> {
self.first_day()..=self.last_day()
}
}

/// The common set of methods for date component.
pub trait Datelike: Sized {
Expand Down Expand Up @@ -179,9 +238,21 @@ pub trait Timelike: Sized {

#[cfg(test)]
mod tests {
use super::Datelike;
use super::{Datelike, Weeklike};
use crate::naive::{MAX_DATE, MIN_DATE};
use crate::{Duration, NaiveDate};
use crate::{Duration, NaiveDate, Weekday};

struct FakeWeek {
date: NaiveDate,
}

impl Weeklike for FakeWeek {
type Day = NaiveDate;

fn first_day(&self) -> Self::Day {
self.date
}
}

/// Tests `Datelike::num_days_from_ce` against an alternative implementation.
///
Expand Down Expand Up @@ -239,4 +310,23 @@ mod tests {
);
}
}

#[test]
fn test_weeklike_last_day() {
let date = NaiveDate::from_ymd(2022, 4, 18);
let week = FakeWeek { date };
let last_day = week.last_day();
assert_eq!(date.weekday(), Weekday::Mon);
assert_eq!(last_day.weekday(), Weekday::Sun);
assert_eq!(last_day, NaiveDate::from_ymd(2022, 4, 24));
}

#[test]
fn test_weeklike_days() {
let date = NaiveDate::from_ymd(2022, 4, 18);
let week = FakeWeek { date };
let days = week.days();
assert_eq!(days.start(), &week.first_day());
assert_eq!(days.end(), &week.last_day());
}
}

0 comments on commit b274146

Please sign in to comment.