Skip to content

Commit

Permalink
add TimeDelta type which implements infallible conversion with `cor…
Browse files Browse the repository at this point in the history
…e::time::Duration`

readme updates

fix broken function name

remove wayward install-cross

fix broken function name - v2
  • Loading branch information
esheppa committed Aug 17, 2022
1 parent a383abf commit 05c808f
Show file tree
Hide file tree
Showing 19 changed files with 1,605 additions and 1,436 deletions.
4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,17 @@ appveyor = { repository = "chronotope/chrono" }
name = "chrono"

[features]
default = ["clock", "std", "oldtime", "wasmbind"]
default = ["clock", "std", "wasmbind"]
alloc = []
libc = []
std = []
clock = ["std", "winapi", "iana-time-zone"]
oldtime = ["time"]
wasmbind = ["wasm-bindgen", "js-sys"]
unstable-locales = ["pure-rust-locales", "alloc"]
__internal_bench = ["criterion"]
__doctest = []

[dependencies]
time = { version = "0.1.43", optional = true }
num-integer = { version = "0.1.36", default-features = false }
num-traits = { version = "0.2", default-features = false }
rustc-serialize = { version = "0.3.20", optional = true }
Expand Down
45 changes: 13 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@
[gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg
[gitter]: https://gitter.im/chrono-rs/chrono

It aims to be a feature-complete superset of
the [time](https://github.com/rust-lang-deprecated/time) library.
In particular,

* Chrono strictly adheres to ISO 8601.
* Chrono is timezone-aware by default, with separate timezone-naive types.
* Chrono is space-optimal and (while not being the primary goal) reasonably efficient.
Expand Down Expand Up @@ -74,32 +70,17 @@ See the [cargo docs][] for examples of specifying features.

## Overview

### Duration
### TimeDelta

Chrono currently uses its own [`Duration`] type to represent the magnitude
of a time span. Since this has the same name as the newer, standard type for
duration, the reference will refer this type as `OldDuration`.
Chrono currently uses its own [`TimeDelta`] type to represent the magnitude
of a time span. This is seperate from the [`Duration`] type in the `std`/`core` library as it
supports negative and positive durations. This type can be converted losslessly from [`Duration`]
but when converting into a [`Duration`] you must take the absolute value.

Note that this is an "accurate" duration represented as seconds and
Note that this is an "accurate" TimeDelta represented as seconds and
nanoseconds and does not represent "nominal" components such as days or
months.

When the `oldtime` feature is enabled, [`Duration`] is an alias for the
[`time::Duration`](https://docs.rs/time/0.1.40/time/struct.Duration.html)
type from v0.1 of the time crate. time v0.1 is deprecated, so new code
should disable the `oldtime` feature and use the `chrono::Duration` type
instead. The `oldtime` feature is enabled by default for backwards
compatibility, but future versions of Chrono are likely to remove the
feature entirely.

Chrono does not yet natively support
the standard [`Duration`](https://doc.rust-lang.org/std/time/struct.Duration.html) type,
but it will be supported in the future.
Meanwhile you can convert between two types with
[`Duration::from_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.from_std)
and
[`Duration::to_std`](https://docs.rs/time/0.1.40/time/struct.Duration.html#method.to_std)
methods.
months. These are supported via the [`NaiveDate::succ`] and [`NaiveDate::pred`] functions
as well as the [`Months`] data type.

### Date and Time

Expand Down Expand Up @@ -184,7 +165,7 @@ The following illustrates most supported operations to the date and time:

```rust
use chrono::prelude::*;
use chrono::Duration;
use chrono::TimeDelta;

// assume this returned `2014-11-28T21:45:59.324310806+09:00`:
let dt = FixedOffset::east(9*3600).ymd(2014, 11, 28).and_hms_nano(21, 45, 59, 324310806);
Expand All @@ -211,11 +192,11 @@ assert_eq!(dt.with_year(-300).unwrap().num_days_from_ce(), -109606); // November
// arithmetic operations
let dt1 = Utc.ymd(2014, 11, 14).and_hms(8, 9, 10);
let dt2 = Utc.ymd(2014, 11, 14).and_hms(10, 9, 8);
assert_eq!(dt1.signed_duration_since(dt2), Duration::seconds(-2 * 3600 + 2));
assert_eq!(dt2.signed_duration_since(dt1), Duration::seconds(2 * 3600 - 2));
assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + Duration::seconds(1_000_000_000),
assert_eq!(dt1.signed_duration_since(dt2), TimeDelta::seconds(-2 * 3600 + 2));
assert_eq!(dt2.signed_duration_since(dt1), TimeDelta::seconds(2 * 3600 - 2));
assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) + TimeDelta::seconds(1_000_000_000),
Utc.ymd(2001, 9, 9).and_hms(1, 46, 40));
assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - Duration::seconds(1_000_000_000),
assert_eq!(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0) - TimeDelta::seconds(1_000_000_000),
Utc.ymd(1938, 4, 24).and_hms(22, 13, 20));
```

Expand Down
8 changes: 4 additions & 4 deletions ci/github.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ source "${BASH_SOURCE[0]%/*}/_shlib.sh"
TEST_TZS=(ACST-9:30 EST4 UTC0 Asia/Katmandu)
FEATURES=(std serde clock "alloc serde" unstable-locales)
CHECK_FEATURES=(alloc "std unstable-locales" "serde clock" "clock unstable-locales")
RUST_132_FEATURES=(rustc-serialize serde)
RUST_136_FEATURES=(rustc-serialize serde)

main() {
if [[ "$*" =~ "-h" ]]; then
Expand Down Expand Up @@ -52,7 +52,7 @@ meaningful in the github actions feature matrix UI.
test_regular UTC0
fi
elif [[ ${RUST_VERSION:-} == 1.38.0 ]]; then
test_132
test_138
else
echo "ERROR: didn't run any tests"
exit 1
Expand Down Expand Up @@ -82,9 +82,9 @@ check_combinatoric() {
done
}

test_132() {
test_138() {
runv cargo build --color=always
for feature in "${RUST_132_FEATURES[@]}"; do
for feature in "${RUST_136_FEATURES[@]}"; do
runt cargo build --features "$feature" --color=always
done
}
Expand Down
116 changes: 77 additions & 39 deletions src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::time::Duration;
use core::{fmt, hash};

#[cfg(feature = "rkyv")]
Expand All @@ -18,8 +19,8 @@ use crate::format::Locale;
use crate::format::{DelayedFormat, Item, StrftimeItems};
use crate::naive::{IsoWeek, NaiveDate, NaiveTime};
use crate::offset::{TimeZone, Utc};
use crate::oldtime::Duration as OldDuration;
use crate::DateTime;
use crate::TimeDelta;
use crate::{Datelike, Weekday};

/// ISO 8601 calendar date with time zone.
Expand Down Expand Up @@ -52,7 +53,7 @@ use crate::{Datelike, Weekday};
///
/// - The date is timezone-agnostic up to one day (i.e. practically always),
/// so the local date and UTC date should be equal for most cases
/// even though the raw calculation between `NaiveDate` and `Duration` may not.
/// even though the raw calculation between `NaiveDate` and `TimeDelta` may not.
#[derive(Clone)]
#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
pub struct Date<Tz: TimeZone> {
Expand Down Expand Up @@ -236,31 +237,31 @@ impl<Tz: TimeZone> Date<Tz> {
tz.from_utc_date(&self.date)
}

/// Adds given `Duration` to the current date.
/// Adds given `TimeDelta` to the current date.
///
/// Returns `None` when it will result in overflow.
#[inline]
pub fn checked_add_signed(self, rhs: OldDuration) -> Option<Date<Tz>> {
pub fn checked_add_signed(self, rhs: TimeDelta) -> Option<Date<Tz>> {
let date = self.date.checked_add_signed(rhs)?;
Some(Date { date, offset: self.offset })
}

/// Subtracts given `Duration` from the current date.
/// Subtracts given `TimeDelta` from the current date.
///
/// Returns `None` when it will result in overflow.
#[inline]
pub fn checked_sub_signed(self, rhs: OldDuration) -> Option<Date<Tz>> {
pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option<Date<Tz>> {
let date = self.date.checked_sub_signed(rhs)?;
Some(Date { date, offset: self.offset })
}

/// Subtracts another `Date` from the current date.
/// Returns a `Duration` of integral numbers.
/// Returns a `TimeDelta` of integral numbers.
///
/// This does not overflow or underflow at all,
/// as all possible output fits in the range of `Duration`.
/// as all possible output fits in the range of `TimeDelta`.
#[inline]
pub fn signed_duration_since<Tz2: TimeZone>(self, rhs: Date<Tz2>) -> OldDuration {
pub fn signed_duration_since<Tz2: TimeZone>(self, rhs: Date<Tz2>) -> TimeDelta {
self.date.signed_duration_since(rhs.date)
}

Expand Down Expand Up @@ -479,43 +480,80 @@ impl<Tz: TimeZone> hash::Hash for Date<Tz> {
}
}

impl<Tz: TimeZone> Add<OldDuration> for Date<Tz> {
impl<Tz: TimeZone> Add<TimeDelta> for Date<Tz> {
type Output = Date<Tz>;

#[inline]
fn add(self, rhs: OldDuration) -> Date<Tz> {
self.checked_add_signed(rhs).expect("`Date + Duration` overflowed")
fn add(self, rhs: TimeDelta) -> Date<Tz> {
self.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed")
}
}
impl<Tz: TimeZone> Add<Duration> for Date<Tz> {
type Output = Date<Tz>;

#[inline]
fn add(self, rhs: Duration) -> Date<Tz> {
self.checked_add_signed(rhs.into()).expect("`Date + core::time::Duration` overflowed")
}
}

impl<Tz: TimeZone> AddAssign<OldDuration> for Date<Tz> {
impl<Tz: TimeZone> AddAssign<TimeDelta> for Date<Tz> {
#[inline]
fn add_assign(&mut self, rhs: OldDuration) {
self.date = self.date.checked_add_signed(rhs).expect("`Date + Duration` overflowed");
fn add_assign(&mut self, rhs: TimeDelta) {
self.date = self.date.checked_add_signed(rhs).expect("`Date + TimeDelta` overflowed");
}
}

impl<Tz: TimeZone> Sub<OldDuration> for Date<Tz> {
impl<Tz: TimeZone> AddAssign<Duration> for Date<Tz> {
#[inline]
fn add_assign(&mut self, rhs: Duration) {
self.date = self
.date
.checked_add_signed(rhs.into())
.expect("`Date + core::time::Duration` overflowed");
}
}

impl<Tz: TimeZone> Sub<TimeDelta> for Date<Tz> {
type Output = Date<Tz>;

#[inline]
fn sub(self, rhs: OldDuration) -> Date<Tz> {
self.checked_sub_signed(rhs).expect("`Date - Duration` overflowed")
fn sub(self, rhs: TimeDelta) -> Date<Tz> {
self.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed")
}
}

impl<Tz: TimeZone> Sub<Duration> for Date<Tz> {
type Output = Date<Tz>;

#[inline]
fn sub(self, rhs: Duration) -> Date<Tz> {
self.checked_sub_signed(rhs.into()).expect("`Date - core::time::Duration` overflowed")
}
}

impl<Tz: TimeZone> SubAssign<TimeDelta> for Date<Tz> {
#[inline]
fn sub_assign(&mut self, rhs: TimeDelta) {
self.date = self.date.checked_sub_signed(rhs).expect("`Date - TimeDelta` overflowed");
}
}

impl<Tz: TimeZone> SubAssign<OldDuration> for Date<Tz> {
impl<Tz: TimeZone> SubAssign<Duration> for Date<Tz> {
#[inline]
fn sub_assign(&mut self, rhs: OldDuration) {
self.date = self.date.checked_sub_signed(rhs).expect("`Date - Duration` overflowed");
fn sub_assign(&mut self, rhs: Duration) {
self.date = self
.date
.checked_sub_signed(rhs.into())
.expect("`Date - core::time::Duration` overflowed");
}
}

impl<Tz: TimeZone> Sub<Date<Tz>> for Date<Tz> {
type Output = OldDuration;
type Output = TimeDelta;

#[inline]
fn sub(self, rhs: Date<Tz>) -> OldDuration {
fn sub(self, rhs: Date<Tz>) -> TimeDelta {
self.signed_duration_since(rhs)
}
}
Expand All @@ -539,7 +577,7 @@ where
mod tests {
use super::Date;

use crate::oldtime::Duration;
use crate::TimeDelta;
use crate::{FixedOffset, NaiveDate, Utc};

#[cfg(feature = "clock")]
Expand All @@ -551,15 +589,15 @@ mod tests {
const WEEKS_PER_YEAR: f32 = 52.1775;

// This is always at least one year because 1 year = 52.1775 weeks.
let one_year_ago = Utc::today() - Duration::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64);
let one_year_ago = Utc::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 1.5).ceil() as i64);
// A bit more than 2 years.
let two_year_ago = Utc::today() - Duration::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64);
let two_year_ago = Utc::today() - TimeDelta::weeks((WEEKS_PER_YEAR * 2.5).ceil() as i64);

assert_eq!(Utc::today().years_since(one_year_ago), Some(1));
assert_eq!(Utc::today().years_since(two_year_ago), Some(2));

// If the given DateTime is later than now, the function will always return 0.
let future = Utc::today() + Duration::weeks(12);
let future = Utc::today() + TimeDelta::weeks(12);
assert_eq!(Utc::today().years_since(future), None);
}

Expand All @@ -569,20 +607,20 @@ mod tests {
let date = Date::<Utc>::from_utc(naivedate, Utc);
let mut date_add = date;

date_add += Duration::days(5);
assert_eq!(date_add, date + Duration::days(5));
date_add += TimeDelta::days(5);
assert_eq!(date_add, date + TimeDelta::days(5));

let timezone = FixedOffset::east(60 * 60);
let date = date.with_timezone(&timezone);
let date_add = date_add.with_timezone(&timezone);

assert_eq!(date_add, date + Duration::days(5));
assert_eq!(date_add, date + TimeDelta::days(5));

let timezone = FixedOffset::west(2 * 60 * 60);
let date = date.with_timezone(&timezone);
let date_add = date_add.with_timezone(&timezone);

assert_eq!(date_add, date + Duration::days(5));
assert_eq!(date_add, date + TimeDelta::days(5));
}

#[test]
Expand All @@ -593,8 +631,8 @@ mod tests {
let date = Local.from_utc_date(&naivedate);
let mut date_add = date;

date_add += Duration::days(5);
assert_eq!(date_add, date + Duration::days(5));
date_add += TimeDelta::days(5);
assert_eq!(date_add, date + TimeDelta::days(5));
}

#[test]
Expand All @@ -603,20 +641,20 @@ mod tests {
let date = Date::<Utc>::from_utc(naivedate, Utc);
let mut date_sub = date;

date_sub -= Duration::days(5);
assert_eq!(date_sub, date - Duration::days(5));
date_sub -= TimeDelta::days(5);
assert_eq!(date_sub, date - TimeDelta::days(5));

let timezone = FixedOffset::east(60 * 60);
let date = date.with_timezone(&timezone);
let date_sub = date_sub.with_timezone(&timezone);

assert_eq!(date_sub, date - Duration::days(5));
assert_eq!(date_sub, date - TimeDelta::days(5));

let timezone = FixedOffset::west(2 * 60 * 60);
let date = date.with_timezone(&timezone);
let date_sub = date_sub.with_timezone(&timezone);

assert_eq!(date_sub, date - Duration::days(5));
assert_eq!(date_sub, date - TimeDelta::days(5));
}

#[test]
Expand All @@ -627,7 +665,7 @@ mod tests {
let date = Local.from_utc_date(&naivedate);
let mut date_sub = date;

date_sub -= Duration::days(5);
assert_eq!(date_sub, date - Duration::days(5));
date_sub -= TimeDelta::days(5);
assert_eq!(date_sub, date - TimeDelta::days(5));
}
}
Loading

0 comments on commit 05c808f

Please sign in to comment.