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: Jorge Guerra <jguerra@stackbuilders.com>
  • Loading branch information
sestrella and Jagl257 committed Apr 22, 2022
1 parent 279f590 commit 4c7e6df
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 14 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
50 changes: 48 additions & 2 deletions src/date.rs
Expand Up @@ -16,11 +16,27 @@ 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::DateTime;
use crate::{Datelike, Weekday};
use crate::{Datelike, Weekday, Weeklike};

/// 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 start_day(&self) -> Self::Day {
Date::from_utc(self.week.start_day(), self.offset.clone())
}
}

/// ISO 8601 calendar date with time zone.
///
Expand Down Expand Up @@ -275,6 +291,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 +521,26 @@ where
write!(f, "{}{}", self.naive_local(), self.offset)
}
}

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

#[test]
fn test_week_start_day() {
let weekdays = [
Weekday::Mon,
Weekday::Tue,
Weekday::Wed,
Weekday::Thu,
Weekday::Fri,
Weekday::Sat,
Weekday::Sun,
];
let date = Utc.ymd(2022, 4, 18);
assert_eq!(date.weekday(), Weekday::Mon);
for weekday in weekdays {
assert!(date.week(weekday).start_day() <= date);
}
}
}
12 changes: 6 additions & 6 deletions src/lib.rs
Expand Up @@ -440,7 +440,7 @@ doctest!("../README.md");
/// A convenience module appropriate for glob imports (`use chrono::prelude::*;`).
pub mod prelude {
#[doc(no_inline)]
pub use crate::Date;
pub use crate::{Date, Week};
#[cfg(feature = "clock")]
#[doc(no_inline)]
pub use crate::Local;
Expand All @@ -452,11 +452,11 @@ pub mod prelude {
#[doc(no_inline)]
pub use crate::{DateTime, SecondsFormat};
#[doc(no_inline)]
pub use crate::{Datelike, Month, Timelike, Weekday};
pub use crate::{Datelike, Month, Timelike, Weekday, Weeklike};
#[doc(no_inline)]
pub use crate::{FixedOffset, Utc};
#[doc(no_inline)]
pub use crate::{NaiveDate, NaiveDateTime, NaiveTime};
pub use crate::{NaiveDate, NaiveDateTime, NaiveTime, NaiveWeek};
#[doc(no_inline)]
pub use crate::{Offset, TimeZone};
}
Expand All @@ -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
49 changes: 47 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,
weekday: Weekday,
}

impl Weeklike for NaiveWeek {
type Day = NaiveDate;

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

/// 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, starting with the
/// [`Weekday`] specified.
#[inline]
pub fn week(&self, weekday: Weekday) -> NaiveWeek {
NaiveWeek { date: *self, weekday }
}
}

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,22 @@ 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_start_day() {
let weekdays = [
Weekday::Mon,
Weekday::Tue,
Weekday::Wed,
Weekday::Thu,
Weekday::Fri,
Weekday::Sat,
Weekday::Sun,
];
let date = NaiveDate::from_ymd(2022, 4, 18);
assert_eq!(date.weekday(), Weekday::Mon);
for weekday in weekdays {
assert!(date.week(weekday).start_day() <= 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
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 start 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.start_day() <= date);
/// ```
fn start_day(&self) -> Self::Day;

/// Returns a date representing the end 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.end_day() >= date);
/// ```
#[inline]
fn end_day(&self) -> Self::Day {
self.start_day() + Duration::days(6)
}

/// Returns a [`RangeInclusive<T>`] representing the whole week bounded by
/// [start_day()][start_day] and [end_day()][end_day] functions.
///
/// # Examples
///
/// ```
/// use chrono::{NaiveDate, Weekday, Weeklike};
///
/// let date = NaiveDate::from_ymd(2022, 4, 18);
/// let week = date.week(Weekday::Mon);
/// let range = week.range();
/// assert_eq!(range.start(), &week.start_day());
/// assert_eq!(range.end(), &week.end_day());
/// ```
///
/// [start_day]: ./trait.Weeklike.html#tymethod.start_day
/// [end_day]: ./trait.Weeklike.html#method.end_day
#[inline]
fn range(&self) -> RangeInclusive<Self::Day> {
self.start_day()..=self.end_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 start_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_end_day() {
let start_day = NaiveDate::from_ymd(2022, 4, 18);
let week = FakeWeek { date: start_day };
let end_day = week.end_day();
assert_eq!(start_day.weekday(), Weekday::Mon);
assert_eq!(end_day.weekday(), Weekday::Sun);
assert_eq!(end_day, NaiveDate::from_ymd(2022, 4, 24));
}

#[test]
fn test_weeklike_range() {
let start_day = NaiveDate::from_ymd(2022, 4, 18);
let week = FakeWeek { date: start_day };
let range = week.range();
assert_eq!(range.start(), &start_day);
assert_eq!(range.end(), &week.end_day());
}
}

0 comments on commit 4c7e6df

Please sign in to comment.