mirror of
https://github.com/chronotope/chrono.git
synced 2025-10-01 23:11:56 +00:00
Merge branch '0.4.x' into merge_0.4.x
This commit is contained in:
commit
8164f9f635
12
.github/workflows/lint.yml
vendored
12
.github/workflows/lint.yml
vendored
@ -18,16 +18,20 @@ jobs:
|
||||
- run: cargo fmt --check -- --color=always
|
||||
- run: cargo fmt --check --manifest-path fuzz/Cargo.toml
|
||||
- run: cargo clippy --color=always -- -D warnings
|
||||
- run: cargo clippy --color=always --target x86_64-pc-windows-msvc -- -D warnings
|
||||
- run: cargo clippy --manifest-path fuzz/Cargo.toml --color=always -- -D warnings
|
||||
- run: |
|
||||
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:
|
||||
RUSTFLAGS: "-Dwarnings"
|
||||
|
||||
cargo-deny:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
|
||||
check-doc:
|
||||
runs-on: ubuntu-latest
|
||||
|
36
.github/workflows/test.yml
vendored
36
.github/workflows/test.yml
vendored
@ -45,9 +45,18 @@ jobs:
|
||||
with:
|
||||
toolchain: 1.56.1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
# run --lib and --doc to avoid the long running integration tests which are run elsewhere
|
||||
- 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
|
||||
# run --lib and --doc to avoid the long running integration tests
|
||||
# which are run elsewhere
|
||||
- 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:
|
||||
strategy:
|
||||
@ -63,7 +72,8 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- run: cargo check --benches
|
||||
- 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 --doc --all-features --color=always -- --color=always
|
||||
|
||||
@ -77,7 +87,13 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- 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 --doc --no-default-features
|
||||
|
||||
@ -119,9 +135,12 @@ jobs:
|
||||
with:
|
||||
node-version: "12"
|
||||
- run: |
|
||||
set -euxo pipefail
|
||||
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
|
||||
shell: bash
|
||||
- run: cargo build --target ${{ matrix.target }} --color=always
|
||||
|
||||
features_check_wasm:
|
||||
@ -136,7 +155,10 @@ jobs:
|
||||
targets: wasm32-unknown-unknown
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- 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:
|
||||
strategy:
|
||||
|
@ -3,8 +3,8 @@ cff-version: 1.2.0
|
||||
message: Please cite this crate using these information.
|
||||
|
||||
# Version information.
|
||||
date-released: 2023-05-29
|
||||
version: 0.4.25
|
||||
date-released: 2023-05-31
|
||||
version: 0.4.26
|
||||
|
||||
# Project information.
|
||||
abstract: Date and time library for Rust
|
||||
|
@ -10,7 +10,7 @@ categories = ["date-and-time"]
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
exclude = ["/ci/*"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
rust-version = "1.56.0"
|
||||
|
||||
[lib]
|
||||
@ -51,7 +51,6 @@ android-tzdata = "0.1.1"
|
||||
serde_json = { version = "1" }
|
||||
serde_derive = { version = "1", default-features = false }
|
||||
bincode = { version = "1.3.0" }
|
||||
num-iter = { version = "0.1.35", default-features = false }
|
||||
doc-comment = { version = "0.3" }
|
||||
|
||||
[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies]
|
||||
|
84
README.md
84
README.md
@ -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]
|
||||
@ -15,45 +15,65 @@
|
||||
[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 aims to provide all functionality needed to do correct operations on dates and times in the
|
||||
[proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar):
|
||||
|
||||
* 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.
|
||||
* The [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) type is timezone-aware
|
||||
by default, with separate timezone-naive types.
|
||||
* 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,
|
||||
which Chrono builds upon and should acknowledge:
|
||||
Timezone data is not shipped with chrono by default to limit binary sizes. Use the companion crate
|
||||
[Chrono-TZ](https://crates.io/crates/chrono-tz) or [`tzfile`](https://crates.io/crates/tzfile) for
|
||||
full timezone support.
|
||||
|
||||
* [Initial research on
|
||||
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)
|
||||
* Luis de Bethencourt's [rust-datetime](https://github.com/luisbg/rust-datetime)
|
||||
## Documentation
|
||||
|
||||
See [docs.rs](https://docs.rs/chrono/latest/chrono/) for the API reference.
|
||||
|
||||
## Limitations
|
||||
|
||||
Only 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.
|
||||
* Only the proleptic Gregorian calendar (i.e. extended to support older dates) is supported.
|
||||
* 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.
|
||||
Time types are limited in the nanosecond accuracy.
|
||||
## Crate features
|
||||
|
||||
[Leap seconds are supported in the representation but
|
||||
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.
|
||||
Default features:
|
||||
|
||||
Chrono inherently does not support an inaccurate or partial date and time representation.
|
||||
Any operation that can be ambiguous will return `None` in such cases.
|
||||
For example, "a month later" of 2014-01-30 is not well-defined
|
||||
and consequently `Utc.ymd_opt(2014, 1, 30).unwrap().with_month(2)` returns `None`.
|
||||
* `alloc`: Enable features that depend on allocation (primarily string formatting)
|
||||
* `std`: Enables functionality that depends on the standard library. This is a superset of `alloc`
|
||||
and adds interoperation with standard library types and traits.
|
||||
* `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.
|
||||
For now you can use the [chrono_ext](https://crates.io/crates/chrono_ext)
|
||||
crate ([sources](https://github.com/bcourtine/chrono-ext/)).
|
||||
Optional features:
|
||||
|
||||
Advanced time zone handling is not yet supported.
|
||||
For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead.
|
||||
* `serde`: Enable serialization/deserialization via serde.
|
||||
* `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.
|
||||
|
@ -866,6 +866,7 @@ fn test_parse() {
|
||||
|
||||
// fixed: dot plus nanoseconds
|
||||
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), num!(Second)]; second: 4);
|
||||
check!(".0", [fix!(Nanosecond)]; nanosecond: 0);
|
||||
@ -888,13 +889,13 @@ fn test_parse() {
|
||||
check!(".000000000547", [fix!(Nanosecond)]; nanosecond: 0);
|
||||
check!(".0000000009999999999999999999999999", [fix!(Nanosecond)]; nanosecond: 0);
|
||||
check!(".4🤠", [fix!(Nanosecond), lit!("🤠")]; nanosecond: 400_000_000);
|
||||
check!(".", [fix!(Nanosecond)]; TOO_SHORT);
|
||||
check!(".4x", [fix!(Nanosecond)]; TOO_LONG);
|
||||
check!(". 4", [fix!(Nanosecond)]; INVALID);
|
||||
check!(" .4", [fix!(Nanosecond)]; TOO_LONG); // no automatic trimming
|
||||
|
||||
// fixed: nanoseconds without the dot
|
||||
check!("", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
|
||||
check!(".", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
|
||||
check!("0", [internal_fix!(Nanosecond3NoDot)]; TOO_SHORT);
|
||||
check!("4", [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!("", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
|
||||
check!(".", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
|
||||
check!("0", [internal_fix!(Nanosecond6NoDot)]; TOO_SHORT);
|
||||
check!("1234", [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!("", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
|
||||
check!(".", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
|
||||
check!("42195", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
|
||||
check!("12345678", [internal_fix!(Nanosecond9NoDot)]; TOO_SHORT);
|
||||
check!("421950803", [internal_fix!(Nanosecond9NoDot)]; nanosecond: 421_950_803);
|
||||
|
@ -4,8 +4,6 @@
|
||||
//! A collection of parsed date and time items.
|
||||
//! They can be constructed incrementally while being checked for consistency.
|
||||
|
||||
use core::convert::TryFrom;
|
||||
|
||||
use super::{ParseResult, IMPOSSIBLE, NOT_ENOUGH, OUT_OF_RANGE};
|
||||
use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone};
|
||||
|
@ -1,4 +1,4 @@
|
||||
use core::{convert::TryFrom, fmt};
|
||||
use core::fmt;
|
||||
|
||||
#[cfg(feature = "rkyv")]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
@ -12,7 +12,6 @@ use crate::OutOfRange;
|
||||
///
|
||||
/// It is possible to convert from a date to a month independently
|
||||
/// ```
|
||||
/// # use std::convert::TryFrom;
|
||||
/// use chrono::prelude::*;
|
||||
/// let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
|
||||
/// // `2019-10-28T09:10:11Z`
|
||||
@ -311,8 +310,6 @@ mod month_serde {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::convert::TryFrom;
|
||||
|
||||
use super::Month;
|
||||
use crate::{Datelike, OutOfRange, TimeZone, Utc};
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
use core::borrow::Borrow;
|
||||
use core::convert::TryFrom;
|
||||
use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign};
|
||||
use core::{fmt, str};
|
||||
|
||||
@ -2250,10 +2249,7 @@ mod tests {
|
||||
};
|
||||
use crate::time_delta::TimeDelta;
|
||||
use crate::{Datelike, Weekday};
|
||||
use std::{
|
||||
convert::{TryFrom, TryInto},
|
||||
i32, u32,
|
||||
};
|
||||
use std::{i32, u32};
|
||||
|
||||
#[test]
|
||||
fn diff_months() {
|
||||
@ -2342,9 +2338,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_readme_doomsday() {
|
||||
use num_iter::range_inclusive;
|
||||
|
||||
for y in range_inclusive(NaiveDate::MIN.year(), NaiveDate::MAX.year()) {
|
||||
for y in NaiveDate::MIN.year()..=NaiveDate::MAX.year() {
|
||||
// even months
|
||||
let d4 = NaiveDate::from_ymd_opt(y, 4, 4).unwrap();
|
||||
let d6 = NaiveDate::from_ymd_opt(y, 6, 6).unwrap();
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
#[cfg(any(feature = "alloc", feature = "std", test))]
|
||||
use core::borrow::Borrow;
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt::Write;
|
||||
use core::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
use core::{fmt, str};
|
||||
|
@ -504,8 +504,6 @@ const fn weekday_from_u32_mod7(n: u32) -> Weekday {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use num_iter::range_inclusive;
|
||||
use std::convert::TryFrom;
|
||||
use std::u32;
|
||||
|
||||
use super::weekday_from_u32_mod7;
|
||||
@ -555,7 +553,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_of() {
|
||||
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) {
|
||||
Some(of) => of,
|
||||
None if !expected => continue,
|
||||
@ -591,8 +589,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_mdf_valid() {
|
||||
fn check(expected: bool, flags: YearFlags, month1: u32, day1: u32, month2: u32, day2: u32) {
|
||||
for month in range_inclusive(month1, month2) {
|
||||
for day in range_inclusive(day1, day2) {
|
||||
for month in month1..=month2 {
|
||||
for day in day1..=day2 {
|
||||
let mdf = match Mdf::new(month, day, flags) {
|
||||
Some(mdf) => mdf,
|
||||
None if !expected => continue,
|
||||
@ -682,7 +680,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_of_fields() {
|
||||
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) {
|
||||
assert_eq!(of.ordinal(), ordinal);
|
||||
}
|
||||
@ -695,7 +693,7 @@ mod tests {
|
||||
fn check(flags: YearFlags, ordinal: u32) {
|
||||
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);
|
||||
assert_eq!(of, Of::new(ordinal, flags));
|
||||
if let Some(of) = of {
|
||||
@ -733,7 +731,7 @@ mod tests {
|
||||
|
||||
for &flags in FLAGS.iter() {
|
||||
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 expected = prev.succ();
|
||||
assert_eq!(of.weekday(), expected);
|
||||
@ -745,8 +743,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_mdf_fields() {
|
||||
for &flags in FLAGS.iter() {
|
||||
for month in range_inclusive(1u32, 12) {
|
||||
for day in range_inclusive(1u32, 31) {
|
||||
for month in 1u32..=12 {
|
||||
for day in 1u32..31 {
|
||||
let mdf = match Mdf::new(month, day, flags) {
|
||||
Some(mdf) => mdf,
|
||||
None => continue,
|
||||
@ -766,7 +764,7 @@ mod tests {
|
||||
fn check(flags: YearFlags, month: u32, day: u32) {
|
||||
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) {
|
||||
Some(mdf) => mdf,
|
||||
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) {
|
||||
Some(mdf) => mdf,
|
||||
None if day > 31 => continue,
|
||||
@ -822,7 +820,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_of_to_mdf() {
|
||||
for i in range_inclusive(0u32, 8192) {
|
||||
for i in 0u32..=8192 {
|
||||
if let Some(of) = Of(i).validate() {
|
||||
assert!(of.to_mdf().valid());
|
||||
}
|
||||
@ -831,7 +829,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_mdf_to_of() {
|
||||
for i in range_inclusive(0u32, 8192) {
|
||||
for i in 0u32..=8192 {
|
||||
let mdf = Mdf(i);
|
||||
assert_eq!(mdf.valid(), mdf.to_of().is_some());
|
||||
}
|
||||
@ -839,7 +837,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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() {
|
||||
assert_eq!(of, of.to_mdf().to_of().unwrap());
|
||||
}
|
||||
@ -848,7 +846,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_mdf_to_of_to_mdf() {
|
||||
for i in range_inclusive(0u32, 8192) {
|
||||
for i in 0u32..=8192 {
|
||||
let mdf = Mdf(i);
|
||||
if mdf.valid() {
|
||||
assert_eq!(mdf, mdf.to_of().unwrap().to_mdf());
|
||||
|
25
src/round.rs
25
src/round.rs
@ -178,6 +178,9 @@ where
|
||||
T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
|
||||
{
|
||||
if let Some(span) = duration.num_nanoseconds() {
|
||||
if span < 0 {
|
||||
return Err(RoundingError::DurationExceedsLimit);
|
||||
}
|
||||
if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS {
|
||||
return Err(RoundingError::TimestampExceedsLimit);
|
||||
}
|
||||
@ -217,6 +220,9 @@ where
|
||||
T: Timelike + Add<TimeDelta, Output = T> + Sub<TimeDelta, Output = T>,
|
||||
{
|
||||
if let Some(span) = duration.num_nanoseconds() {
|
||||
if span < 0 {
|
||||
return Err(RoundingError::DurationExceedsLimit);
|
||||
}
|
||||
if naive.timestamp().abs() > MAX_SECONDS_TIMESTAMP_FOR_NANOS {
|
||||
return Err(RoundingError::TimestampExceedsLimit);
|
||||
}
|
||||
@ -304,10 +310,10 @@ impl std::error::Error for RoundingError {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{DurationRound, SubsecRound, TimeDelta};
|
||||
use super::{DurationRound, RoundingError, SubsecRound, TimeDelta};
|
||||
use crate::offset::{FixedOffset, TimeZone, Utc};
|
||||
use crate::NaiveDate;
|
||||
use crate::Timelike;
|
||||
use crate::{NaiveDate, NaiveDateTime};
|
||||
|
||||
#[test]
|
||||
fn test_round_subsecs() {
|
||||
@ -760,4 +766,19 @@ mod tests {
|
||||
"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));
|
||||
}
|
||||
}
|
||||
|
@ -219,9 +219,7 @@ mod tests {
|
||||
date.ordinal() as i32 + 365 * diff(1) + diff(4) - diff(100) + diff(400)
|
||||
}
|
||||
|
||||
use num_iter::range_inclusive;
|
||||
|
||||
for year in range_inclusive(NaiveDate::MIN.year(), NaiveDate::MAX.year()) {
|
||||
for year in NaiveDate::MIN.year()..=NaiveDate::MAX.year() {
|
||||
let jan1_year = NaiveDate::from_ymd_opt(year, 1, 1).unwrap();
|
||||
assert_eq!(
|
||||
jan1_year.num_days_from_ce(),
|
||||
|
@ -1,4 +1,4 @@
|
||||
use core::{convert::TryFrom, fmt};
|
||||
use core::fmt;
|
||||
|
||||
#[cfg(feature = "rkyv")]
|
||||
use rkyv::{Archive, Deserialize, Serialize};
|
||||
@ -14,7 +14,6 @@ use crate::OutOfRange;
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use chrono::Weekday;
|
||||
/// use std::convert::TryFrom;
|
||||
///
|
||||
/// let monday = "Monday".parse::<Weekday>().unwrap();
|
||||
/// assert_eq!(monday, Weekday::Mon);
|
||||
@ -199,7 +198,6 @@ impl fmt::Debug for ParseWeekdayError {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Weekday;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[test]
|
||||
fn test_num_days_from() {
|
||||
|
@ -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]
|
||||
#[cfg(unix)]
|
||||
fn try_verify_against_date_command() {
|
||||
#[cfg(not(target_os = "aix"))]
|
||||
let date_path = "/usr/bin/date";
|
||||
#[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.
|
||||
if !path::Path::new(DATE_PATH).exists() {
|
||||
eprintln!("date command {:?} not found, skipping", DATE_PATH);
|
||||
return;
|
||||
}
|
||||
assert_run_date_version();
|
||||
|
||||
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 {
|
||||
if (1975..=1977).contains(&date.year())
|
||||
|| (2020..=2022).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);
|
||||
}
|
||||
eprintln!("Command {:?} was run {} times", DATE_PATH, count);
|
||||
}
|
||||
|
||||
#[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
|
||||
|
||||
let output = process::Command::new(path)
|
||||
.env("LANG", "c")
|
||||
.arg("-d")
|
||||
.arg(format!(
|
||||
"{}-{:02}-{:02} {:02}:{:02}:{:02}",
|
||||
@ -124,15 +159,15 @@ fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTim
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn try_verify_against_date_command_format() {
|
||||
let date_path = "/usr/bin/date";
|
||||
|
||||
if !path::Path::new(date_path).exists() {
|
||||
// date command not found, skipping
|
||||
if !path::Path::new(DATE_PATH).exists() {
|
||||
eprintln!("date command {:?} not found, skipping", DATE_PATH);
|
||||
return;
|
||||
}
|
||||
assert_run_date_version();
|
||||
|
||||
let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user