New type: `chrono::naive::IsoWeek`.

This removes `Datelike::isoweekdate` in favor of `Datelike::iso_week`.
The original `isoweekdate` was not named in accordance with the style
guide and also used to return the day of the week which is already
provided by `Datelike::weekday`. The new design should be more
reasonable.

Note that we initially do not implement any public constructor for
`IsoWeek`. That is, the only legitimate way to get a new `IsoWeek` is
from `Datelike::iso_week`. This sidesteps the issue of boundary values
(for example the year number in the maximal date will overflow in
the week date) while giving the same power as the original API.

Partially accounts for #139. We may add additional week types
as necessary---this is the beginning.
This commit is contained in:
Kang Seonghoon 2017-06-22 00:21:24 +09:00
parent 42a7c8e589
commit 9768b289f0
No known key found for this signature in database
GPG Key ID: 82440FABA6709020
8 changed files with 219 additions and 59 deletions

View File

@ -10,7 +10,7 @@ use oldtime::Duration as OldDuration;
use {Weekday, Datelike};
use offset::{TimeZone, UTC};
use naive::{self, NaiveDate, NaiveTime};
use naive::{self, NaiveDate, NaiveTime, IsoWeek};
use DateTime;
use format::{Item, DelayedFormat, StrftimeItems};
@ -278,7 +278,7 @@ impl<Tz: TimeZone> Datelike for Date<Tz> {
#[inline] fn ordinal(&self) -> u32 { self.naive_local().ordinal() }
#[inline] fn ordinal0(&self) -> u32 { self.naive_local().ordinal0() }
#[inline] fn weekday(&self) -> Weekday { self.naive_local().weekday() }
#[inline] fn isoweekdate(&self) -> (i32, u32, Weekday) { self.naive_local().isoweekdate() }
#[inline] fn iso_week(&self) -> IsoWeek { self.naive_local().iso_week() }
#[inline]
fn with_year(&self, year: i32) -> Option<Date<Tz>> {

View File

@ -12,7 +12,7 @@ use oldtime::Duration as OldDuration;
use {Weekday, Timelike, Datelike};
use offset::{TimeZone, Offset, UTC, Local, FixedOffset};
use naive::{NaiveTime, NaiveDateTime};
use naive::{NaiveTime, NaiveDateTime, IsoWeek};
use Date;
use format::{Item, Numeric, Pad, Fixed};
use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems};
@ -258,7 +258,7 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
#[inline] fn ordinal(&self) -> u32 { self.naive_local().ordinal() }
#[inline] fn ordinal0(&self) -> u32 { self.naive_local().ordinal0() }
#[inline] fn weekday(&self) -> Weekday { self.naive_local().weekday() }
#[inline] fn isoweekdate(&self) -> (i32, u32, Weekday) { self.naive_local().isoweekdate() }
#[inline] fn iso_week(&self) -> IsoWeek { self.naive_local().iso_week() }
#[inline]
fn with_year(&self, year: i32) -> Option<DateTime<Tz>> {

View File

@ -353,14 +353,14 @@ pub fn format<'a, I>(w: &mut fmt::Formatter, date: Option<&NaiveDate>, time: Opt
Year => (4, date.map(|d| d.year() as i64)),
YearDiv100 => (2, date.map(|d| div_floor(d.year() as i64, 100))),
YearMod100 => (2, date.map(|d| mod_floor(d.year() as i64, 100))),
IsoYear => (4, date.map(|d| d.isoweekdate().0 as i64)),
IsoYearDiv100 => (2, date.map(|d| div_floor(d.isoweekdate().0 as i64, 100))),
IsoYearMod100 => (2, date.map(|d| mod_floor(d.isoweekdate().0 as i64, 100))),
IsoYear => (4, date.map(|d| d.iso_week().year() as i64)),
IsoYearDiv100 => (2, date.map(|d| div_floor(d.iso_week().year() as i64, 100))),
IsoYearMod100 => (2, date.map(|d| mod_floor(d.iso_week().year() as i64, 100))),
Month => (2, date.map(|d| d.month() as i64)),
Day => (2, date.map(|d| d.day() as i64)),
WeekFromSun => (2, date.map(|d| week_from_sun(d) as i64)),
WeekFromMon => (2, date.map(|d| week_from_mon(d) as i64)),
IsoWeek => (2, date.map(|d| d.isoweekdate().1 as i64)),
IsoWeek => (2, date.map(|d| d.iso_week().week() as i64)),
NumDaysFromSun => (1, date.map(|d| d.weekday().num_days_from_sunday() as i64)),
WeekdayFromMon => (1, date.map(|d| d.weekday().number_from_monday() as i64)),
Ordinal => (3, date.map(|d| d.ordinal() as i64)),

View File

@ -332,7 +332,10 @@ impl Parsed {
// verify the ISO week date.
let verify_isoweekdate = |date: NaiveDate| {
let (isoyear, isoweek, weekday) = date.isoweekdate();
let week = date.iso_week();
let isoyear = week.year();
let isoweek = week.week();
let weekday = date.weekday();
let (isoyear_div_100, isoyear_mod_100) = if isoyear >= 0 {
let (q, r) = div_rem(isoyear, 100);
(Some(q), Some(r))

View File

@ -362,10 +362,10 @@ extern crate serde as serdelib;
pub use oldtime::Duration;
#[doc(no_inline)] pub use offset::{TimeZone, Offset, LocalResult, UTC, FixedOffset, Local};
#[doc(no_inline)] pub use naive::{NaiveDate, NaiveTime, NaiveDateTime};
pub use date_::{Date, MIN_DATE, MAX_DATE};
pub use datetime_::DateTime;
#[cfg(feature = "rustc-serialize")] pub use datetime_::TsSeconds;
#[doc(no_inline)] pub use naive::{NaiveDate, IsoWeek, NaiveTime, NaiveDateTime};
pub use date::{Date, MIN_DATE, MAX_DATE};
pub use datetime::DateTime;
#[cfg(feature = "rustc-serialize")] pub use datetime::TsSeconds;
pub use format::{ParseError, ParseResult};
/// A convenience module appropriate for glob imports (`use chrono::prelude::*;`).
@ -393,24 +393,24 @@ pub mod naive {
//! but can be also used for the simpler date and time handling.
mod internals;
mod date;
mod isoweek;
mod time;
mod datetime;
// avoid using them directly even in the crate itself
#[path = "date.rs"] mod date_;
#[path = "time.rs"] mod time_;
#[path = "datetime.rs"] mod datetime_;
pub use self::date_::{NaiveDate, MIN_DATE, MAX_DATE};
pub use self::time_::NaiveTime;
pub use self::datetime_::{NaiveDateTime, TsSeconds};
pub use self::date::{NaiveDate, MIN_DATE, MAX_DATE};
pub use self::isoweek::IsoWeek;
pub use self::time::NaiveTime;
pub use self::datetime::{NaiveDateTime, TsSeconds};
/// Tools to help serializing/deserializing naive types.
#[cfg(feature = "serde")]
pub mod serde {
pub use super::datetime_::serde::*;
pub use super::datetime::serde::*;
}
}
#[path = "date.rs"] mod date_;
#[path = "datetime.rs"] mod datetime_;
mod date;
mod datetime;
pub mod format;
/// Ser/de helpers
@ -419,7 +419,7 @@ pub mod format;
/// annotation](https://serde.rs/attributes.html#field-attributes).
#[cfg(feature = "serde")]
pub mod serde {
pub use super::datetime_::serde::*;
pub use super::datetime::serde::*;
}
/// The day of week.
@ -761,9 +761,8 @@ pub trait Datelike: Sized {
/// Returns the day of week.
fn weekday(&self) -> Weekday;
/// Returns the ISO week date: an adjusted year, week number and day of week.
/// The adjusted year may differ from that of the calendar date.
fn isoweekdate(&self) -> (i32, u32, Weekday);
/// Returns the ISO week.
fn iso_week(&self) -> IsoWeek;
/// Makes a new value with the year number changed.
///

View File

@ -10,10 +10,11 @@ use oldtime::Duration as OldDuration;
use {Weekday, Datelike};
use div::div_mod_floor;
use naive::{NaiveTime, NaiveDateTime};
use naive::{NaiveTime, NaiveDateTime, IsoWeek};
use format::{Item, Numeric, Pad};
use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems};
use super::isoweek;
use super::internals::{self, DateImpl, Of, Mdf, YearFlags};
const MAX_YEAR: i32 = internals::MAX_YEAR;
@ -81,7 +82,8 @@ const MAX_BITS: usize = 44;
/// For example, January 3, 2016 (Sunday) was on the last (53rd) week of 2015.
///
/// Chrono's date types default to the ISO 8601 [calendar date](#calendar-date),
/// but the [`Datelike::isoweekdate`](../trait.Datelike.html#tymethod.isoweekdate) method
/// but [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) and
/// [`Datelike::weekday`](../trait.Datelike.html#tymethod.weekday) methods
/// can be used to get the corresponding week date.
///
/// # Ordinal Date
@ -151,7 +153,9 @@ impl NaiveDate {
/// assert_eq!(d.month(), 3);
/// assert_eq!(d.day(), 14);
/// assert_eq!(d.ordinal(), 73); // day of year
/// assert_eq!(d.isoweekdate(), (2015, 11, Weekday::Sat)); // ISO week and weekday
/// assert_eq!(d.iso_week().year(), 2015);
/// assert_eq!(d.iso_week().week(), 11);
/// assert_eq!(d.weekday(), Weekday::Sat);
/// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE
/// ~~~~
pub fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate {
@ -197,7 +201,9 @@ impl NaiveDate {
/// assert_eq!(d.year(), 2015);
/// assert_eq!(d.month(), 3);
/// assert_eq!(d.day(), 14);
/// assert_eq!(d.isoweekdate(), (2015, 11, Weekday::Sat)); // ISO week and weekday
/// assert_eq!(d.iso_week().year(), 2015);
/// assert_eq!(d.iso_week().week(), 11);
/// assert_eq!(d.weekday(), Weekday::Sat);
/// assert_eq!(d.num_days_from_ce(), 735671); // days since January 1, 1 CE
/// ~~~~
pub fn from_yo(year: i32, ordinal: u32) -> NaiveDate {
@ -241,7 +247,9 @@ impl NaiveDate {
/// use chrono::{NaiveDate, Datelike, Weekday};
///
/// let d = NaiveDate::from_isoywd(2015, 11, Weekday::Sat);
/// assert_eq!(d.isoweekdate(), (2015, 11, Weekday::Sat));
/// assert_eq!(d.iso_week().year(), 2015);
/// assert_eq!(d.iso_week().week(), 11);
/// assert_eq!(d.weekday(), Weekday::Sat);
/// assert_eq!(d.year(), 2015);
/// assert_eq!(d.month(), 3);
/// assert_eq!(d.day(), 14);
@ -338,7 +346,9 @@ impl NaiveDate {
/// assert_eq!(d.month(), 3);
/// assert_eq!(d.day(), 14);
/// assert_eq!(d.ordinal(), 73); // day of year
/// assert_eq!(d.isoweekdate(), (2015, 11, Weekday::Sat)); // ISO week and weekday
/// assert_eq!(d.iso_week().year(), 2015);
/// assert_eq!(d.iso_week().week(), 11);
/// assert_eq!(d.weekday(), Weekday::Sat);
/// ~~~~
///
/// While not directly supported by Chrono,
@ -1128,21 +1138,9 @@ impl Datelike for NaiveDate {
self.of().weekday()
}
fn isoweekdate(&self) -> (i32, u32, Weekday) {
let of = self.of();
let year = self.year();
let (rawweek, weekday) = of.isoweekdate_raw();
if rawweek < 1 { // previous year
let prevlastweek = YearFlags::from_year(year - 1).nisoweeks();
(year - 1, prevlastweek, weekday)
} else {
let lastweek = of.flags().nisoweeks();
if rawweek > lastweek { // next year
(year + 1, 1, weekday)
} else {
(year, rawweek, weekday)
}
}
#[inline]
fn iso_week(&self) -> IsoWeek {
isoweek::iso_week_from_yof(self.year(), self.of())
}
/// Makes a new `NaiveDate` with the year number changed.
@ -1720,7 +1718,7 @@ mod tests {
}
#[test]
fn test_date_from_isoywd_and_isoweekdate() {
fn test_date_from_isoywd_and_iso_week() {
for year in 2000..2401 {
for week in 1..54 {
for &weekday in [Weekday::Mon, Weekday::Tue, Weekday::Wed, Weekday::Thu,
@ -1729,10 +1727,9 @@ mod tests {
if d.is_some() {
let d = d.unwrap();
assert_eq!(d.weekday(), weekday);
let (year_, week_, weekday_) = d.isoweekdate();
assert_eq!(year_, year);
assert_eq!(week_, week);
assert_eq!(weekday_, weekday);
let w = d.iso_week();
assert_eq!(w.year(), year);
assert_eq!(w.week(), week);
}
}
}
@ -1744,8 +1741,8 @@ mod tests {
let d = NaiveDate::from_ymd_opt(year, month, day);
if d.is_some() {
let d = d.unwrap();
let (year_, week_, weekday_) = d.isoweekdate();
let d_ = NaiveDate::from_isoywd(year_, week_, weekday_);
let w = d.iso_week();
let d_ = NaiveDate::from_isoywd(w.year(), w.week(), d.weekday());
assert_eq!(d, d_);
}
}

View File

@ -10,7 +10,7 @@ use oldtime::Duration as OldDuration;
use {Weekday, Timelike, Datelike};
use div::div_mod_floor;
use naive::{NaiveTime, NaiveDate};
use naive::{NaiveTime, NaiveDate, IsoWeek};
use format::{Item, Numeric, Pad, Fixed};
use format::{parse, Parsed, ParseError, ParseResult, DelayedFormat, StrftimeItems};
@ -781,8 +781,8 @@ impl Datelike for NaiveDateTime {
}
#[inline]
fn isoweekdate(&self) -> (i32, u32, Weekday) {
self.date.isoweekdate()
fn iso_week(&self) -> IsoWeek {
self.date.iso_week()
}
/// Makes a new `NaiveDateTime` with the year number changed.

161
src/naive/isoweek.rs Normal file
View File

@ -0,0 +1,161 @@
// This is a part of Chrono.
// See README.md and LICENSE.txt for details.
//! ISO 8601 week.
use std::fmt;
use super::internals::{DateImpl, Of, YearFlags};
/// ISO 8601 week.
///
/// This type, combined with [`Weekday`](../enum.Weekday.html),
/// constitues the ISO 8601 [week date](./struct.NaiveDate.html#week-date).
/// One can retrieve this type from the existing [`Datelike`](../trait.Datelike.html) types
/// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub struct IsoWeek {
// note that this allows for larger year range than `NaiveDate`.
// this is crucial because we have an edge case for the first and last week supported,
// which year number might not match the calendar year number.
ywf: DateImpl, // (year << 10) | (week << 4) | flag
}
/// Returns the corresponding `IsoWeek` from the year and the `Of` internal value.
//
// internal use only. we don't expose the public constructor for `IsoWeek` for now,
// because the year range for the week date and the calendar date do not match and
// it is confusing to have a date that is out of range in one and not in another.
// currently we sidestep this issue by making `IsoWeek` fully dependent of `Datelike`.
pub fn iso_week_from_yof(year: i32, of: Of) -> IsoWeek {
let (rawweek, _) = of.isoweekdate_raw();
let (year, week) = if rawweek < 1 { // previous year
let prevlastweek = YearFlags::from_year(year - 1).nisoweeks();
(year - 1, prevlastweek)
} else {
let lastweek = of.flags().nisoweeks();
if rawweek > lastweek { // next year
(year + 1, 1)
} else {
(year, rawweek)
}
};
IsoWeek { ywf: (year << 10) | (week << 4) as DateImpl | of.flags().0 as DateImpl }
}
impl IsoWeek {
/// Returns the year number for this ISO week.
///
/// # Example
///
/// ~~~~
/// use chrono::{NaiveDate, Datelike, Weekday};
///
/// let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon);
/// assert_eq!(d.iso_week().year(), 2015);
/// ~~~~
///
/// This year number might not match the calendar year number.
/// Continuing the example...
///
/// ~~~~
/// # use chrono::{NaiveDate, Datelike, Weekday};
/// # let d = NaiveDate::from_isoywd(2015, 1, Weekday::Mon);
/// assert_eq!(d.year(), 2014);
/// assert_eq!(d, NaiveDate::from_ymd(2014, 12, 29));
/// ~~~~
#[inline]
pub fn year(&self) -> i32 {
self.ywf >> 10
}
/// Returns the ISO week number starting from 1.
///
/// The return value ranges from 1 to 53. (The last week of year differs by years.)
///
/// # Example
///
/// ~~~~
/// use chrono::{NaiveDate, Datelike, Weekday};
///
/// let d = NaiveDate::from_isoywd(2015, 15, Weekday::Mon);
/// assert_eq!(d.iso_week().week(), 15);
/// ~~~~
#[inline]
pub fn week(&self) -> u32 {
((self.ywf >> 4) & 0x3f) as u32
}
/// Returns the ISO week number starting from 0.
///
/// The return value ranges from 0 to 52. (The last week of year differs by years.)
///
/// # Example
///
/// ~~~~
/// use chrono::{NaiveDate, Datelike, Weekday};
///
/// let d = NaiveDate::from_isoywd(2015, 15, Weekday::Mon);
/// assert_eq!(d.iso_week().week0(), 14);
/// ~~~~
#[inline]
pub fn week0(&self) -> u32 {
((self.ywf >> 4) & 0x3f) as u32 - 1
}
}
/// The `Debug` output of the ISO week `w` is same to
/// [`d.format("%G-W%V")`](../format/strftime/index.html)
/// where `d` is any `NaiveDate` value in that week.
///
/// # Example
///
/// ~~~~
/// use chrono::{NaiveDate, Datelike};
///
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(2015, 9, 5).iso_week()), "2015-W36");
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 3).iso_week()), "0000-W01");
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(9999, 12, 31).iso_week()), "9999-W52");
/// ~~~~
///
/// ISO 8601 requires an explicit sign for years before 1 BCE or after 9999 CE.
///
/// ~~~~
/// # use chrono::{NaiveDate, Datelike};
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd( 0, 1, 2).iso_week()), "-0001-W52");
/// assert_eq!(format!("{:?}", NaiveDate::from_ymd(10000, 12, 31).iso_week()), "+10000-W52");
/// ~~~~
impl fmt::Debug for IsoWeek {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let year = self.year();
let week = self.week();
if 0 <= year && year <= 9999 {
write!(f, "{:04}-W{:02}", year, week)
} else {
// ISO 8601 requires the explicit sign for out-of-range years
write!(f, "{:+05}-W{:02}", year, week)
}
}
}
#[cfg(test)]
mod tests {
use naive::{internals, MIN_DATE, MAX_DATE};
use Datelike;
#[test]
fn test_iso_week_extremes() {
let minweek = MIN_DATE.iso_week();
let maxweek = MAX_DATE.iso_week();
assert_eq!(minweek.year(), internals::MIN_YEAR);
assert_eq!(minweek.week(), 1);
assert_eq!(minweek.week0(), 0);
assert_eq!(format!("{:?}", minweek), MIN_DATE.format("%G-W%V").to_string());
assert_eq!(maxweek.year(), internals::MAX_YEAR + 1);
assert_eq!(maxweek.week(), 1);
assert_eq!(maxweek.week0(), 0);
assert_eq!(format!("{:?}", maxweek), MAX_DATE.format("%G-W%V").to_string());
}
}