Merge branch '0.4.x' into merge_0.4.x

This commit is contained in:
Paul Dicker 2023-05-31 12:05:39 +02:00
commit 8164f9f635
15 changed files with 189 additions and 103 deletions

View File

@ -18,16 +18,20 @@ jobs:
- run: cargo fmt --check -- --color=always - run: cargo fmt --check -- --color=always
- run: cargo fmt --check --manifest-path fuzz/Cargo.toml - run: cargo fmt --check --manifest-path fuzz/Cargo.toml
- run: cargo clippy --color=always -- -D warnings - run: cargo clippy --color=always -- -D warnings
- run: cargo clippy --color=always --target x86_64-pc-windows-msvc -- -D warnings - run: |
- run: cargo clippy --manifest-path fuzz/Cargo.toml --color=always -- -D warnings cargo clippy --color=always --target x86_64-pc-windows-msvc \
-- -D warnings
- run: |
cargo clippy --manifest-path fuzz/Cargo.toml --color=always \
-- -D warnings
env: env:
RUSTFLAGS: "-Dwarnings" RUSTFLAGS: "-Dwarnings"
cargo-deny: cargo-deny:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: EmbarkStudios/cargo-deny-action@v1 - uses: EmbarkStudios/cargo-deny-action@v1
check-doc: check-doc:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -45,9 +45,18 @@ jobs:
with: with:
toolchain: 1.56.1 toolchain: 1.56.1
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
# run --lib and --doc to avoid the long running integration tests which are run elsewhere # run --lib and --doc to avoid the long running integration tests
- run: cargo test --lib --features unstable-locales,wasmbind,clock,serde,windows-sys --color=always -- --color=always # which are run elsewhere
- run: cargo test --doc --features unstable-locales,wasmbind,clock,serde,windows-sys --color=always -- --color=always - run: |
cargo test --lib \
--features \
unstable-locales,wasmbind,clock,serde,windows-sys \
--color=always -- --color=always
- run: |
cargo test --doc \
--features \
unstable-locales,wasmbind,clock,serde,windows-sys \
--color=always -- --color=always
rust_versions: rust_versions:
strategy: strategy:
@ -63,7 +72,8 @@ jobs:
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: cargo check --benches - run: cargo check --benches
- run: cargo check --manifest-path fuzz/Cargo.toml --all-targets - run: cargo check --manifest-path fuzz/Cargo.toml --all-targets
# run --lib and --doc to avoid the long running integration tests which are run elsewhere # run --lib and --doc to avoid the long running integration tests
# which are run elsewhere
- run: cargo test --lib --all-features --color=always -- --color=always - run: cargo test --lib --all-features --color=always -- --color=always
- run: cargo test --doc --all-features --color=always -- --color=always - run: cargo test --doc --all-features --color=always -- --color=always
@ -77,7 +87,13 @@ jobs:
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-hack - uses: taiki-e/install-action@cargo-hack
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-locales - run: |
cargo hack check --feature-powerset --optional-deps serde,rkyv \
--skip default --skip __internal_bench --skip __doctest \
--skip iana-time-zone --skip pure-rust-locales
# run using `bash` on all platforms for consistent
# line-continuation marks
shell: bash
- run: cargo test --lib --no-default-features - run: cargo test --lib --no-default-features
- run: cargo test --doc --no-default-features - run: cargo test --doc --no-default-features
@ -119,9 +135,12 @@ jobs:
with: with:
node-version: "12" node-version: "12"
- run: | - run: |
set -euxo pipefail
export RUST_BACKTRACE=1 export RUST_BACKTRACE=1
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf \
| bash --noprofile --norc
wasm-pack --version wasm-pack --version
shell: bash
- run: cargo build --target ${{ matrix.target }} --color=always - run: cargo build --target ${{ matrix.target }} --color=always
features_check_wasm: features_check_wasm:
@ -136,7 +155,10 @@ jobs:
targets: wasm32-unknown-unknown targets: wasm32-unknown-unknown
- uses: taiki-e/install-action@cargo-hack - uses: taiki-e/install-action@cargo-hack
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: cargo hack check --feature-powerset --optional-deps serde,rkyv --skip default --skip __internal_bench --skip __doctest --skip iana-time-zone --skip pure-rust-locales - run: |
cargo hack check --feature-powerset --optional-deps serde,rkyv \
--skip default --skip __internal_bench --skip __doctest \
--skip iana-time-zone --skip pure-rust-locales
cross-targets: cross-targets:
strategy: strategy:

View File

@ -3,8 +3,8 @@ cff-version: 1.2.0
message: Please cite this crate using these information. message: Please cite this crate using these information.
# Version information. # Version information.
date-released: 2023-05-29 date-released: 2023-05-31
version: 0.4.25 version: 0.4.26
# Project information. # Project information.
abstract: Date and time library for Rust abstract: Date and time library for Rust

View File

@ -10,7 +10,7 @@ categories = ["date-and-time"]
readme = "README.md" readme = "README.md"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
exclude = ["/ci/*"] exclude = ["/ci/*"]
edition = "2018" edition = "2021"
rust-version = "1.56.0" rust-version = "1.56.0"
[lib] [lib]
@ -51,7 +51,6 @@ android-tzdata = "0.1.1"
serde_json = { version = "1" } serde_json = { version = "1" }
serde_derive = { version = "1", default-features = false } serde_derive = { version = "1", default-features = false }
bincode = { version = "1.3.0" } bincode = { version = "1.3.0" }
num-iter = { version = "0.1.35", default-features = false }
doc-comment = { version = "0.3" } doc-comment = { version = "0.3" }
[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies] [target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies]

View File

@ -1,4 +1,4 @@
[Chrono][docsrs]: Date and Time for Rust [Chrono][docsrs]: Timezone-aware date and time handling
======================================== ========================================
[![Chrono GitHub Actions][gh-image]][gh-checks] [![Chrono GitHub Actions][gh-image]][gh-checks]
@ -15,45 +15,65 @@
[gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg [gitter-image]: https://badges.gitter.im/chrono-rs/chrono.svg
[gitter]: https://gitter.im/chrono-rs/chrono [gitter]: https://gitter.im/chrono-rs/chrono
It aims to be a feature-complete superset of Chrono aims to provide all functionality needed to do correct operations on dates and times in the
the [time](https://github.com/rust-lang-deprecated/time) library. [proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar):
In particular,
* Chrono strictly adheres to ISO 8601. * The [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) type is timezone-aware
* Chrono is timezone-aware by default, with separate timezone-naive types. by default, with separate timezone-naive types.
* Chrono is space-optimal and (while not being the primary goal) reasonably efficient. * Operations that may produce an invalid or ambiguous date and time return `Option` or
[`LocalResult`](https://docs.rs/chrono/latest/chrono/offset/enum.LocalResult.html).
* Configurable parsing and formatting with an `strftime` inspired date and time formatting syntax.
* The [`Local`](https://docs.rs/chrono/latest/chrono/offset/struct.Local.html) timezone works with
the current timezone of the OS.
* Types and operations are implemented to be reasonably efficient.
There were several previous attempts to bring a good date and time library to Rust, Timezone data is not shipped with chrono by default to limit binary sizes. Use the companion crate
which Chrono builds upon and should acknowledge: [Chrono-TZ](https://crates.io/crates/chrono-tz) or [`tzfile`](https://crates.io/crates/tzfile) for
full timezone support.
* [Initial research on ## Documentation
the wiki](https://github.com/rust-lang/rust-wiki-backup/blob/master/Lib-datetime.md)
* Dietrich Epp's [datetime-rs](https://github.com/depp/datetime-rs) See [docs.rs](https://docs.rs/chrono/latest/chrono/) for the API reference.
* Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime)
## Limitations ## Limitations
Only proleptic Gregorian calendar (i.e. extended to support older dates) is supported. * Only the proleptic Gregorian calendar (i.e. extended to support older dates) is supported.
Be very careful if you really have to deal with pre-20C dates, they can be in Julian or others. * Date types are limited to about +/- 262,000 years from the common epoch.
* Time types are limited to nanosecond accuracy.
* Leap seconds can be represented, but Chrono does not fully support them.
See [Leap Second Handling](https://docs.rs/chrono/latest/chrono/naive/struct.NaiveTime.html#leap-second-handling).
Date types are limited in about +/- 262,000 years from the common epoch. ## Crate features
Time types are limited in the nanosecond accuracy.
[Leap seconds are supported in the representation but Default features:
Chrono doesn't try to make use of them](https://docs.rs/chrono/0.5/chrono/naive/struct.NaiveTime.html#leap-second-handling).
(The main reason is that leap seconds are not really predictable.)
Almost *every* operation over the possible leap seconds will ignore them.
Consider using `NaiveDateTime` with the implicit TAI (International Atomic Time) scale
if you want.
Chrono inherently does not support an inaccurate or partial date and time representation. * `alloc`: Enable features that depend on allocation (primarily string formatting)
Any operation that can be ambiguous will return `None` in such cases. * `std`: Enables functionality that depends on the standard library. This is a superset of `alloc`
For example, "a month later" of 2014-01-30 is not well-defined and adds interoperation with standard library types and traits.
and consequently `Utc.ymd_opt(2014, 1, 30).unwrap().with_month(2)` returns `None`. * `clock`: Enables reading the system time (`now`) and local timezone (`Local`).
* `wasmbind`: Interface with the JS Date API for the `wasm32` target.
Non ISO week handling is not yet supported. Optional features:
For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext)
crate ([sources](https://github.com/bcourtine/chrono-ext/)).
Advanced time zone handling is not yet supported. * `serde`: Enable serialization/deserialization via serde.
For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead. * `rkyv`: Enable serialization/deserialization via rkyv.
* `rustc-serialize`: Enable serialization/deserialization via rustc-serialize (deprecated).
* `arbitrary`: construct arbitrary instances of a type with the Arbitrary crate.
* `unstable-locales`: Enable localization. This adds various methods with a `_localized` suffix.
The implementation and API may change or even be removed in a patch release. Feedback welcome.
## Rust version requirements
The Minimum Supported Rust Version (MSRV) is currently **Rust 1.56.0**.
The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done
lightly.
## License
This project is licensed under either of
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
* [MIT License](https://opensource.org/licenses/MIT)
at your option.

View File

@ -866,6 +866,7 @@ fn test_parse() {
// fixed: dot plus nanoseconds // fixed: dot plus nanoseconds
check!("", [fix!(Nanosecond)]; ); // no field set, but not an error check!("", [fix!(Nanosecond)]; ); // no field set, but not an error
check!(".", [fix!(Nanosecond)]; TOO_SHORT);
check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4` check!("4", [fix!(Nanosecond)]; TOO_LONG); // never consumes `4`
check!("4", [fix!(Nanosecond), num!(Second)]; second: 4); check!("4", [fix!(Nanosecond), num!(Second)]; second: 4);
check!(".0", [fix!(Nanosecond)]; nanosecond: 0); check!(".0", [fix!(Nanosecond)]; nanosecond: 0);
@ -888,13 +889,13 @@ fn test_parse() {
check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0); check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0);
check!(".0000000009999999999999999999999999", [fix!(Nanosecond)]; nanosecond: 0); check!(".0000000009999999999999999999999999", [fix!(Nanosecond)]; nanosecond: 0);
check!(".4🤠", [fix!(Nanosecond), lit!("🤠")]; nanosecond: 400_000_000); check!(".4🤠", [fix!(Nanosecond), lit!("🤠")]; nanosecond: 400_000_000);
check!(".", [fix!(Nanosecond)]; TOO_SHORT);
check!(".4x", [fix!(Nanosecond)]; TOO_LONG); check!(".4x", [fix!(Nanosecond)]; TOO_LONG);
check!(". 4", [fix!(Nanosecond)]; INVALID); check!(". 4", [fix!(Nanosecond)]; INVALID);
check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming
// fixed: nanoseconds without the dot // fixed: nanoseconds without the dot
check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
check!(".", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("4", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT); check!("42", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
@ -910,6 +911,7 @@ fn test_parse() {
check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID); check!(".421", [internal_fix!(Nanosecond3NoDot)]; INVALID);
check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
check!(".", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
check!("1234", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("1234", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
check!("12345", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT); check!("12345", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
@ -923,6 +925,7 @@ fn test_parse() {
check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID); check!(".42100", [internal_fix!(Nanosecond6NoDot)]; INVALID);
check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
check!(".", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
check!("12345678", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT); check!("12345678", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803); check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803);

View File

@ -4,8 +4,6 @@
//! A collection of parsed date and time items. //! A collection of parsed date and time items.
//! They can be constructed incrementally while being checked for consistency. //! They can be constructed incrementally while being checked for consistency.
use core::convert::TryFrom;
use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE}; use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE};
use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime}; use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone}; use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone};

View File

@ -1,4 +1,4 @@
use core::{convert::TryFrom, fmt}; use core::fmt;
#[cfg(feature = "rkyv")] #[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize, Serialize}; use rkyv::{Archive, Deserialize, Serialize};
@ -12,7 +12,6 @@ use crate::OutOfRange;
/// ///
/// It is possible to convert from a date to a month independently /// It is possible to convert from a date to a month independently
/// ``` /// ```
/// # use std::convert::TryFrom;
/// use chrono::prelude::*; /// use chrono::prelude::*;
/// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap(); /// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
/// // `2019-10-28T09:10:11Z` /// // `2019-10-28T09:10:11Z`
@ -311,8 +310,6 @@ mod month_serde {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use core::convert::TryFrom;
use super::Month; use super::Month;
use crate::{Datelike, OutOfRange, TimeZone, Utc}; use crate::{Datelike, OutOfRange, TimeZone, Utc};

View File

@ -5,7 +5,6 @@
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
use core::borrow::Borrow; use core::borrow::Borrow;
use core::convert::TryFrom;
use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign}; use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign};
use core::{fmt, str}; use core::{fmt, str};
@ -2250,10 +2249,7 @@ mod tests {
}; };
use crate::time_delta::TimeDelta; use crate::time_delta::TimeDelta;
use crate::{Datelike, Weekday}; use crate::{Datelike, Weekday};
use std::{ use std::{i32, u32};
convert::{TryFrom, TryInto},
i32, u32,
};
#[test] #[test]
fn diff_months() { fn diff_months() {
@ -2342,9 +2338,7 @@ mod tests {
#[test] #[test]
fn test_readme_doomsday() { fn test_readme_doomsday() {
use num_iter::range_inclusive; for y in NaiveDate::MIN.year()..=NaiveDate::MAX.year() {
for y in range_inclusive(NaiveDate::MIN.year(), NaiveDate::MAX.year()) {
// even months // even months
let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap(); let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap();
let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap(); let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap();

View File

@ -5,7 +5,6 @@
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
use core::borrow::Borrow; use core::borrow::Borrow;
use core::convert::TryFrom;
use core::fmt::Write; use core::fmt::Write;
use core::ops::{Add, AddAssign, Sub, SubAssign}; use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::{fmt, str}; use core::{fmt, str};

View File

@ -504,8 +504,6 @@ const fn weekday_from_u32_mod7(n: u32) -> Weekday {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use num_iter::range_inclusive;
use std::convert::TryFrom;
use std::u32; use std::u32;
use super::weekday_from_u32_mod7; use super::weekday_from_u32_mod7;
@ -555,7 +553,7 @@ mod tests {
#[test] #[test]
fn test_of() { fn test_of() {
fn check(expected: bool, flags: YearFlags, ordinal1: u32, ordinal2: u32) { fn check(expected: bool, flags: YearFlags, ordinal1: u32, ordinal2: u32) {
for ordinal in range_inclusive(ordinal1, ordinal2) { for ordinal in ordinal1..=ordinal2 {
let of = match Of::new(ordinal, flags) { let of = match Of::new(ordinal, flags) {
Some(of) => of, Some(of) => of,
None if !expected => continue, None if !expected => continue,
@ -591,8 +589,8 @@ mod tests {
#[test] #[test]
fn test_mdf_valid() { fn test_mdf_valid() {
fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) { fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) {
for month in range_inclusive(month1, month2) { for month in month1..=month2 {
for day in range_inclusive(day1, day2) { for day in day1..=day2 {
let mdf = match Mdf::new(month, day, flags) { let mdf = match Mdf::new(month, day, flags) {
Some(mdf) => mdf, Some(mdf) => mdf,
None if !expected => continue, None if !expected => continue,
@ -682,7 +680,7 @@ mod tests {
#[test] #[test]
fn test_of_fields() { fn test_of_fields() {
for &flags in FLAGS.iter() { for &flags in FLAGS.iter() {
for ordinal in range_inclusive(1u32, 366) { for ordinal in 1u32..=366 {
if let Some(of) = Of::new(ordinal, flags) { if let Some(of) = Of::new(ordinal, flags) {
assert_eq!(of.ordinal(), ordinal); assert_eq!(of.ordinal(), ordinal);
} }
@ -695,7 +693,7 @@ mod tests {
fn check(flags: YearFlags, ordinal: u32) { fn check(flags: YearFlags, ordinal: u32) {
let of = Of::new(ordinal, flags).unwrap(); let of = Of::new(ordinal, flags).unwrap();
for ordinal in range_inclusive(0u32, 1024) { for ordinal in 0u32..=1024 {
let of = of.with_ordinal(ordinal); let of = of.with_ordinal(ordinal);
assert_eq!(of, Of::new(ordinal, flags)); assert_eq!(of, Of::new(ordinal, flags));
if let Some(of) = of { if let Some(of) = of {
@ -733,7 +731,7 @@ mod tests {
for &flags in FLAGS.iter() { for &flags in FLAGS.iter() {
let mut prev = Of::new(1, flags).unwrap().weekday(); let mut prev = Of::new(1, flags).unwrap().weekday();
for ordinal in range_inclusive(2u32, flags.ndays()) { for ordinal in 2u32..=flags.ndays() {
let of = Of::new(ordinal, flags).unwrap(); let of = Of::new(ordinal, flags).unwrap();
let expected = prev.succ(); let expected = prev.succ();
assert_eq!(of.weekday(), expected); assert_eq!(of.weekday(), expected);
@ -745,8 +743,8 @@ mod tests {
#[test] #[test]
fn test_mdf_fields() { fn test_mdf_fields() {
for &flags in FLAGS.iter() { for &flags in FLAGS.iter() {
for month in range_inclusive(1u32, 12) { for month in 1u32..=12 {
for day in range_inclusive(1u32, 31) { for day in 1u32..31 {
let mdf = match Mdf::new(month, day, flags) { let mdf = match Mdf::new(month, day, flags) {
Some(mdf) => mdf, Some(mdf) => mdf,
None => continue, None => continue,
@ -766,7 +764,7 @@ mod tests {
fn check(flags: YearFlags, month: u32, day: u32) { fn check(flags: YearFlags, month: u32, day: u32) {
let mdf = Mdf::new(month, day, flags).unwrap(); let mdf = Mdf::new(month, day, flags).unwrap();
for month in range_inclusive(0u32, 16) { for month in 0u32..=16 {
let mdf = match mdf.with_month(month) { let mdf = match mdf.with_month(month) {
Some(mdf) => mdf, Some(mdf) => mdf,
None if month > 12 => continue, None if month > 12 => continue,
@ -779,7 +777,7 @@ mod tests {
} }
} }
for day in range_inclusive(0u32, 1024) { for day in 0u32..=1024 {
let mdf = match mdf.with_day(day) { let mdf = match mdf.with_day(day) {
Some(mdf) => mdf, Some(mdf) => mdf,
None if day > 31 => continue, None if day > 31 => continue,
@ -822,7 +820,7 @@ mod tests {
#[test] #[test]
fn test_of_to_mdf() { fn test_of_to_mdf() {
for i in range_inclusive(0u32, 8192) { for i in 0u32..=8192 {
if let Some(of) = Of(i).validate() { if let Some(of) = Of(i).validate() {
assert!(of.to_mdf().valid()); assert!(of.to_mdf().valid());
} }
@ -831,7 +829,7 @@ mod tests {
#[test] #[test]
fn test_mdf_to_of() { fn test_mdf_to_of() {
for i in range_inclusive(0u32, 8192) { for i in 0u32..=8192 {
let mdf = Mdf(i); let mdf = Mdf(i);
assert_eq!(mdf.valid(), mdf.to_of().is_some()); assert_eq!(mdf.valid(), mdf.to_of().is_some());
} }
@ -839,7 +837,7 @@ mod tests {
#[test] #[test]
fn test_of_to_mdf_to_of() { fn test_of_to_mdf_to_of() {
for i in range_inclusive(0u32, 8192) { for i in 0u32..=8192 {
if let Some(of) = Of(i).validate() { if let Some(of) = Of(i).validate() {
assert_eq!(of, of.to_mdf().to_of().unwrap()); assert_eq!(of, of.to_mdf().to_of().unwrap());
} }
@ -848,7 +846,7 @@ mod tests {
#[test] #[test]
fn test_mdf_to_of_to_mdf() { fn test_mdf_to_of_to_mdf() {
for i in range_inclusive(0u32, 8192) { for i in 0u32..=8192 {
let mdf = Mdf(i); let mdf = Mdf(i);
if mdf.valid() { if mdf.valid() {
assert_eq!(mdf, mdf.to_of().unwrap().to_mdf()); assert_eq!(mdf, mdf.to_of().unwrap().to_mdf());

View File

@ -178,6 +178,9 @@ where
T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>, T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
{ {
if let Some(span) = duration.num_nanoseconds() { if let Some(span) = duration.num_nanoseconds() {
if span < 0 {
return Err(RoundingError::DurationExceedsLimit);
}
if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS {
return Err(RoundingError::TimestampExceedsLimit); return Err(RoundingError::TimestampExceedsLimit);
} }
@ -217,6 +220,9 @@ where
T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>, T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
{ {
if let Some(span) = duration.num_nanoseconds() { if let Some(span) = duration.num_nanoseconds() {
if span < 0 {
return Err(RoundingError::DurationExceedsLimit);
}
if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS { if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS {
return Err(RoundingError::TimestampExceedsLimit); return Err(RoundingError::TimestampExceedsLimit);
} }
@ -304,10 +310,10 @@ impl std::error::Error for RoundingError {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{DurationRound, SubsecRound, TimeDelta}; use super::{DurationRound, RoundingError, SubsecRound, TimeDelta};
use crate::offset::{FixedOffset, TimeZone, Utc}; use crate::offset::{FixedOffset, TimeZone, Utc};
use crate::NaiveDate;
use crate::Timelike; use crate::Timelike;
use crate::{NaiveDate, NaiveDateTime};
#[test] #[test]
fn test_round_subsecs() { fn test_round_subsecs() {
@ -760,4 +766,19 @@ mod tests {
"1969-12-12 12:10:00 UTC" "1969-12-12 12:10:00 UTC"
); );
} }
#[test]
fn issue1010() {
let dt = NaiveDateTime::from_timestamp_opt(-4227854320, 1678774288).unwrap();
let span = TimeDelta::microseconds(-7019067213869040);
assert_eq!(dt.duration_trunc(span), Err(RoundingError::DurationExceedsLimit));
let dt = NaiveDateTime::from_timestamp_opt(320041586, 1920103021).unwrap();
let span = TimeDelta::nanoseconds(-8923838508697114584);
assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit));
let dt = NaiveDateTime::from_timestamp_opt(-2621440, 0).unwrap();
let span = TimeDelta::nanoseconds(-9223372036854771421);
assert_eq!(dt.duration_round(span), Err(RoundingError::DurationExceedsLimit));
}
} }

View File

@ -219,9 +219,7 @@ mod tests {
date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400) date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400)
} }
use num_iter::range_inclusive; for year in NaiveDate::MIN.year()..=NaiveDate::MAX.year() {
for year in range_inclusive(NaiveDate::MIN.year(), NaiveDate::MAX.year()) {
let jan1_year = NaiveDate::from_ymd_opt(year, 1, 1).unwrap(); let jan1_year = NaiveDate::from_ymd_opt(year, 1, 1).unwrap();
assert_eq!( assert_eq!(
jan1_year.num_days_from_ce(), jan1_year.num_days_from_ce(),

View File

@ -1,4 +1,4 @@
use core::{convert::TryFrom, fmt}; use core::fmt;
#[cfg(feature = "rkyv")] #[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize, Serialize}; use rkyv::{Archive, Deserialize, Serialize};
@ -14,7 +14,6 @@ use crate::OutOfRange;
/// # Example /// # Example
/// ``` /// ```
/// use chrono::Weekday; /// use chrono::Weekday;
/// use std::convert::TryFrom;
/// ///
/// let monday = "Monday".parse::<Weekday>().unwrap(); /// let monday = "Monday".parse::<Weekday>().unwrap();
/// assert_eq!(monday, Weekday::Mon); /// assert_eq!(monday, Weekday::Mon);
@ -199,7 +198,6 @@ impl fmt::Debug for ParseWeekdayError {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Weekday; use super::Weekday;
use std::convert::TryFrom;
#[test] #[test]
fn test_num_days_from() { fn test_num_days_from() {

View File

@ -52,34 +52,68 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) {
} }
} }
/// path to Unix `date` command. Should work on most Linux and Unixes. Not the
/// path for MacOS (/bin/date) which uses a different version of `date` with
/// different arguments (so it won't run which is okay).
/// for testing only
#[allow(dead_code)]
#[cfg(not(target_os = "aix"))]
const DATE_PATH: &'static str = "/usr/bin/date";
#[allow(dead_code)]
#[cfg(target_os = "aix")]
const DATE_PATH: &'static str = "/opt/freeware/bin/date";
#[cfg(test)]
/// test helper to sanity check the date command behaves as expected
/// asserts the command succeeded
fn assert_run_date_version() {
// note environment variable `LANG`
match std::env::var_os("LANG") {
Some(lang) => eprintln!("LANG: {:?}", lang),
None => eprintln!("LANG not set"),
}
let out = process::Command::new(DATE_PATH).arg("--version").output().unwrap();
let stdout = String::from_utf8(out.stdout).unwrap();
let stderr = String::from_utf8(out.stderr).unwrap();
// note the `date` binary version
eprintln!("command: {:?} --version\nstdout: {:?}\nstderr: {:?}", DATE_PATH, stdout, stderr);
assert!(out.status.success(), "command failed: {:?} --version", DATE_PATH);
}
#[test] #[test]
#[cfg(unix)] #[cfg(unix)]
fn try_verify_against_date_command() { fn try_verify_against_date_command() {
#[cfg(not(target_os = "aix"))] if !path::Path::new(DATE_PATH).exists() {
let date_path = "/usr/bin/date"; eprintln!("date command {:?} not found, skipping", DATE_PATH);
#[cfg(target_os = "aix")]
let date_path = "/opt/freeware/bin/date";
if !path::Path::new(date_path).exists() {
// date command not found, skipping
// avoid running this on macOS, which has path /bin/date
// as the required CLI arguments are not present in the
// macOS build.
return; return;
} }
assert_run_date_version();
let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(); let mut date = NaiveDate::from_ymd_opt(1975, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
eprintln!(
"Run command {:?} for every hour from {} to 2077, skipping some years...",
DATE_PATH,
date.year()
);
let mut count: u64 = 0;
let mut year_at = date.year();
while date.year() < 2078 { while date.year() < 2078 {
if (1975..=1977).contains(&date.year()) if (1975..=1977).contains(&date.year())
|| (2020..=2022).contains(&date.year()) || (2020..=2022).contains(&date.year())
|| (2073..=2077).contains(&date.year()) || (2073..=2077).contains(&date.year())
{ {
verify_against_date_command_local(date_path, date); if date.year() != year_at {
eprintln!("at year {}...", date.year());
year_at = date.year();
}
verify_against_date_command_local(DATE_PATH, date);
count += 1;
} }
date += chrono::TimeDelta::hours(1); date += chrono::TimeDelta::hours(1);
} }
eprintln!("Command {:?} was run {} times", DATE_PATH, count);
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -98,6 +132,7 @@ fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTim
// Z%Z - too many ways to represent it, will most likely fail // Z%Z - too many ways to represent it, will most likely fail
let output = process::Command::new(path) let output = process::Command::new(path)
.env("LANG", "c")
.arg("-d") .arg("-d")
.arg(format!( .arg(format!(
"{}-{:02}-{:02} {:02}:{:02}:{:02}", "{}-{:02}-{:02} {:02}:{:02}:{:02}",
@ -124,15 +159,15 @@ fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTim
#[test] #[test]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn try_verify_against_date_command_format() { fn try_verify_against_date_command_format() {
let date_path = "/usr/bin/date"; if !path::Path::new(DATE_PATH).exists() {
eprintln!("date command {:?} not found, skipping", DATE_PATH);
if !path::Path::new(date_path).exists() {
// date command not found, skipping
return; return;
} }
assert_run_date_version();
let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap(); let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap();
while date.year() < 2008 { while date.year() < 2008 {
verify_against_date_command_format_local(date_path, date); verify_against_date_command_format_local(DATE_PATH, date);
date += chrono::TimeDelta::days(55); date += chrono::TimeDelta::days(55);
} }
} }