Merge branch '0.4.x'

This commit is contained in:
Paul Dicker 2023-09-11 08:14:15 +02:00
commit 8cd48b4796
22 changed files with 351 additions and 180 deletions

View File

@ -12,12 +12,13 @@ jobs:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v4
- name: Install Rust
run: rustup update stable
# nightly is required for --doctests, see cargo-llvm-cov#2
- name: Install Rust (nightly)
run: rustup update nightly
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
run: cargo +nightly llvm-cov --all-features --workspace --lcov --doctests --output-path lcov.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
env:

View File

@ -17,12 +17,16 @@ jobs:
- uses: Swatinem/rust-cache@v2
- run: cargo fmt --check -- --color=always
- run: cargo fmt --check --manifest-path fuzz/Cargo.toml
- run: cargo fmt --check --manifest-path bench/Cargo.toml
- run: |
cargo clippy --all-features --all-targets --color=always \
-- -D warnings
- run: |
cargo clippy --manifest-path fuzz/Cargo.toml --color=always \
-- -D warnings
- run: |
cargo clippy --manifest-path bench/Cargo.toml --color=always \
-- -D warnings
env:
RUSTFLAGS: "-Dwarnings"

View File

@ -6,22 +6,10 @@ on:
pull_request:
jobs:
timezones_linux:
timezones:
strategy:
matrix:
os: [ubuntu-latest]
tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --all-features --color=always -- --color=always
timezones_other:
strategy:
matrix:
os: [macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
tz: ["ACST-9:30", "EST4", "UTC0", "Asia/Katmandu"]
runs-on: ${{ matrix.os }}
steps:
@ -67,7 +55,7 @@ jobs:
with:
toolchain: ${{ matrix.rust_version }}
- uses: Swatinem/rust-cache@v2
- run: cargo check --benches
- run: cargo check --manifest-path bench/Cargo.toml --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
@ -119,7 +107,6 @@ jobs:
os: [ubuntu-latest]
target:
[
wasm32-wasi,
wasm32-unknown-emscripten,
aarch64-apple-ios,
aarch64-linux-android,
@ -145,7 +132,6 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: taiki-e/install-action@cargo-hack
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v3
- uses: jetli/wasm-pack-action@v0.4.0
@ -153,6 +139,27 @@ jobs:
# with the host system.
- run: TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind
test_wasi:
strategy:
matrix:
os: [ubuntu-latest]
target:
- wasm32-wasi
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasi
- uses: Swatinem/rust-cache@v2
- run: cargo install cargo-wasi
- uses: mwilliamson/setup-wasmtime-action@v2
with:
wasmtime-version: "12.0.1"
# We can't use `--all-features` because `rustc-serialize` doesn't support
# `wasm32-wasi`.
- run: cargo wasi test --features=serde,unstable-locales --color=always -- --color=always
cross-targets:
strategy:
matrix:
@ -165,6 +172,20 @@ jobs:
- uses: Swatinem/rust-cache@v2
- run: cross check --target ${{ matrix.target }}
cross-tests:
strategy:
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- run: cargo install cross
- uses: Swatinem/rust-cache@v2
- run: cross test --lib --all-features --target i686-unknown-linux-gnu --color=always
- run: cross test --doc --all-features --target i686-unknown-linux-gnu --color=always
- run: cross test --lib --all-features --target i686-unknown-linux-musl --color=always
- run: cross test --doc --all-features --target i686-unknown-linux-musl --color=always
check-docs:
runs-on: ubuntu-latest
steps:

View File

@ -3,8 +3,8 @@ cff-version: 1.2.0
message: Please cite this crate using these information.
# Version information.
date-released: 2023-08-29
version: 0.4.27
date-released: 2023-09-07
version: 0.4.30
# Project information.
abstract: Date and time library for Rust

View File

@ -25,13 +25,11 @@ std = []
clock = ["std", "winapi", "iana-time-zone", "android-tzdata"]
wasmbind = ["wasm-bindgen", "js-sys"]
unstable-locales = ["pure-rust-locales", "alloc"]
__internal_bench = ["criterion"]
__doctest = []
__internal_bench = []
[dependencies]
serde = { version = "1.0.99", default-features = false, optional = true }
pure-rust-locales = { version = "0.6", optional = true }
criterion = { version = "0.4.0", optional = true }
rkyv = { version = "0.7", optional = true }
arbitrary = { version = "1.0.0", features = ["derive"], optional = true }
@ -55,7 +53,6 @@ android-tzdata = { version = "0.1.1", optional = true }
serde_json = { version = "1" }
serde_derive = { version = "1", default-features = false }
bincode = { version = "1.3.0" }
doc-comment = { version = "0.3" }
[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dev-dependencies]
wasm-bindgen-test = "0.3"
@ -66,13 +63,3 @@ rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.playground]
features = ["serde"]
[[bench]]
name = "chrono"
required-features = ["__internal_bench"]
harness = false
[[bench]]
name = "serde"
required-features = ["__internal_bench", "serde"]
harness = false

26
bench/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "benches"
version = "0.1.0"
edition = "2021"
# Even as a `dev-dependency` Criterion and its dependencies can affect the MSRV of chrono.
# But not when it lives in a separate crate :-).
# See https://github.com/chronotope/chrono/pull/1104.
[lib]
name = "benches"
[dependencies]
chrono = { path = "..", features = ["__internal_bench", "serde"] }
[[bench]]
name = "chrono"
harness = false
[[bench]]
name = "serde"
harness = false
[dev-dependencies]
criterion = "0.5.0"
serde_json = "1"

View File

@ -1,5 +1,4 @@
//! Benchmarks for chrono that just depend on std
#![cfg(feature = "__internal_bench")]
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};

View File

@ -1,5 +1,3 @@
#![cfg(feature = "__internal_bench")]
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use chrono::NaiveDateTime;

1
bench/src/lib.rs Normal file
View File

@ -0,0 +1 @@
// This file only exists to make `benches` a valid crate.

View File

@ -660,10 +660,24 @@ impl DateTime<FixedOffset> {
/// and returns a new [`DateTime`] instance with the parsed timezone as the [`FixedOffset`].
///
/// RFC 2822 is the internet message standard that specifies the representation of times in HTTP
/// and email headers.
/// and email headers. It is the 2001 revision of RFC 822, and is itself revised as RFC 5322 in
/// 2008.
///
/// The RFC 2822 standard allows arbitrary intermixed whitespace.
/// See [RFC 2822 Appendix A.5]
/// # Support for the obsolete date format
///
/// - A 2-digit year is interpreted to be a year in 1950-2049.
/// - The standard allows comments and whitespace between many of the tokens. See [4.3] and
/// [Appendix A.5]
/// - Single letter 'military' time zone names are parsed as a `-0000` offset.
/// They were defined with the wrong sign in RFC 822 and corrected in RFC 2822. But because
/// the meaning is now ambiguous, the standard says they should be be considered as `-0000`
/// unless there is out-of-band information confirming their meaning.
/// The exception is `Z`, which remains identical to `+0000`.
///
/// [4.3]: https://www.rfc-editor.org/rfc/rfc2822#section-4.3
/// [Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5
///
/// # Example
///
/// ```
/// # use chrono::{DateTime, FixedOffset, TimeZone};
@ -672,8 +686,6 @@ impl DateTime<FixedOffset> {
/// FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2015, 2, 18, 23, 16, 9).unwrap()
/// );
/// ```
///
/// [RFC 2822 Appendix A.5]: https://www.rfc-editor.org/rfc/rfc2822#appendix-A.5
pub fn parse_from_rfc2822(s: &str) -> ParseResult<DateTime<FixedOffset>> {
const ITEMS: &[Item<'static>] = &[Item::Fixed(Fixed::RFC2822)];
let mut parsed = Parsed::new();

View File

@ -311,6 +311,7 @@ fn ymdhms_nano(
}
// local helper function to easily create a DateTime<Utc>
#[cfg(any(feature = "alloc", feature = "std"))]
fn ymdhms_utc(year: i32, month: u32, day: u32, hour: u32, min: u32, sec: u32) -> DateTime<Utc> {
Utc.with_ymd_and_hms(year, month, day, hour, min, sec).unwrap()
}
@ -818,6 +819,7 @@ fn test_datetime_from_str() {
.unwrap())
);
assert!("2015-2-18T23:16:9.15".parse::<DateTime<Utc>>().is_err());
assert!("2015-02-18T23:16:9.15øøø".parse::<DateTime<Utc>>().is_err());
// no test for `DateTime<Local>`, we cannot verify that much.
}
@ -915,7 +917,7 @@ fn test_parse_datetime_utc() {
}
#[test]
fn test_utc_datetime_from_str() {
fn test_parse_from_str() {
let edt = FixedOffset::east_opt(570 * 60).unwrap();
let edt0 = FixedOffset::east_opt(0).unwrap();
let wdt = FixedOffset::west_opt(10 * 3600).unwrap();
@ -930,11 +932,7 @@ fn test_utc_datetime_from_str() {
)
.is_err());
assert_eq!(
Utc.datetime_from_str("Fri, 09 Aug 2013 23:54:35 GMT", "%a, %d %b %Y %H:%M:%S GMT"),
Ok(Utc.with_ymd_and_hms(2013, 8, 9, 23, 54, 35).unwrap())
);
assert_eq!(
DateTime::<FixedOffset>::parse_from_str("0", "%s").unwrap(),
DateTime::<Utc>::parse_from_str("0", "%s").unwrap(),
NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset()
);
@ -978,53 +976,6 @@ fn test_utc_datetime_from_str() {
// no test for `DateTime<Local>`, we cannot verify that much.
}
#[test]
fn test_utc_datetime_from_str_with_spaces() {
let dt = ymdhms_utc(2013, 8, 9, 23, 54, 35);
// with varying spaces - should succeed
assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),);
assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt),);
assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt),);
assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),);
assert_eq!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt),);
assert_eq!(
Utc.datetime_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "),
Ok(dt),
);
assert_eq!(Utc.datetime_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt),);
assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),);
assert_eq!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt),);
assert_eq!(Utc.datetime_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt),);
assert_eq!(Utc.datetime_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt),);
// with varying spaces - should fail
// leading whitespace in format
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S").is_err());
// trailing whitespace in format
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err());
// extra mid-string whitespace in format
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err());
// mismatched leading whitespace
assert!(Utc.datetime_from_str("\tAug 09 2013 23:54:35", "\n%b %d %Y %H:%M:%S").is_err());
// mismatched trailing whitespace
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n").is_err());
// mismatched mid-string whitespace
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S").is_err());
// trailing whitespace in format
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err());
// trailing whitespace (newline) in format
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err());
// leading space in data
assert!(Utc.datetime_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err());
// trailing space in data
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err());
// trailing tab in data
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err());
// mismatched newlines
assert!(Utc.datetime_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err());
// trailing literal in data
assert!(Utc.datetime_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err());
}
#[test]
fn test_datetime_parse_from_str() {
let dt = ymdhms(&FixedOffset::east_opt(-9 * 60 * 60).unwrap(), 2013, 8, 9, 23, 54, 35);
@ -1218,8 +1169,11 @@ fn test_subsecond_part() {
assert_eq!(1234567, datetime.timestamp_subsec_nanos());
}
// Some targets, such as `wasm32-wasi`, have a problematic definition of `SystemTime`, such as an
// `i32` (year 2035 problem), or an `u64` (no values before `UNIX-EPOCH`).
// See https://github.com/rust-lang/rust/issues/44394.
#[test]
#[cfg(feature = "std")]
#[cfg(all(feature = "std", not(all(target_arch = "wasm32", target_os = "wasi"))))]
fn test_from_system_time() {
use std::time::{Duration, SystemTime, UNIX_EPOCH};

View File

@ -18,14 +18,14 @@
//! # Example
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
#![cfg_attr(feature = "std", doc = "```rust")]
//! use chrono::{TimeZone, Utc};
//! use chrono::{NaiveDateTime, TimeZone, Utc};
//!
//! let date_time = Utc.with_ymd_and_hms(2020, 11, 10, 0, 1, 32).unwrap();
//!
//! let formatted = format!("{}", date_time.format("%Y-%m-%d %H:%M:%S"));
//! assert_eq!(formatted, "2020-11-10 00:01:32");
//!
//! let parsed = Utc.datetime_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?;
//! let parsed = NaiveDateTime::parse_from_str(&formatted, "%Y-%m-%d %H:%M:%S")?.and_utc();
//! assert_eq!(parsed, date_time);
//! # Ok::<(), chrono::ParseError>(())
//! ```
@ -451,7 +451,7 @@ const BAD_FORMAT: ParseError = ParseError(ParseErrorKind::BadFormat);
// this implementation is here only because we need some private code from `scan`
/// Parsing a `str` into a `Weekday` uses the format [`%W`](./format/strftime/index.html).
/// Parsing a `str` into a `Weekday` uses the format [`%A`](./format/strftime/index.html).
///
/// # Example
///
@ -487,7 +487,7 @@ impl FromStr for Weekday {
}
}
/// Parsing a `str` into a `Month` uses the format [`%W`](./format/strftime/index.html).
/// Parsing a `str` into a `Month` uses the format [`%B`](./format/strftime/index.html).
///
/// # Example
///

View File

@ -614,7 +614,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult
Ok(_) => return Err(NOT_ENOUGH),
};
s = s.trim_start();
let (s, offset) = if s.len() >= 3 && "UTC".eq_ignore_ascii_case(&s[..3]) {
let (s, offset) = if s.len() >= 3 && "UTC".as_bytes().eq_ignore_ascii_case(&s.as_bytes()[..3]) {
(&s[3..], 0)
} else {
scan::timezone_offset(s, scan::consume_colon_maybe, true, false, true)?
@ -626,7 +626,7 @@ fn parse_rfc3339_relaxed<'a>(parsed: &mut Parsed, mut s: &'a str) -> ParseResult
#[cfg(test)]
mod tests {
use crate::format::*;
use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc};
use crate::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Timelike, Utc};
macro_rules! parsed {
($($k:ident: $v:expr),*) => (#[allow(unused_mut)] {
@ -1733,7 +1733,10 @@ mod tests {
assert_eq!(dt.format(RFC850_FMT).to_string(), "Sunday, 06-Nov-94 08:49:37 GMT");
// Check that it parses correctly
assert_eq!(Ok(dt), Utc.datetime_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT));
assert_eq!(
NaiveDateTime::parse_from_str("Sunday, 06-Nov-94 08:49:37 GMT", RFC850_FMT),
Ok(dt.naive_utc())
);
// Check that the rest of the weekdays parse correctly (this test originally failed because
// Sunday parsed incorrectly).
@ -1765,7 +1768,7 @@ mod tests {
];
for val in &testdates {
assert_eq!(Ok(val.0), Utc.datetime_from_str(val.1, RFC850_FMT));
assert_eq!(NaiveDateTime::parse_from_str(val.1, RFC850_FMT), Ok(val.0.naive_utc()));
}
let test_dates_fail = [
@ -1784,7 +1787,7 @@ mod tests {
];
for val in &test_dates_fail {
assert!(Utc.datetime_from_str(val, RFC850_FMT).is_err());
assert!(NaiveDateTime::parse_from_str(val, RFC850_FMT).is_err());
}
}

View File

@ -909,11 +909,20 @@ mod tests {
}
#[test]
#[cfg(feature = "unstable-locales")]
#[cfg(all(feature = "unstable-locales", target_pointer_width = "64"))]
fn test_type_sizes() {
use core::mem::size_of;
assert_eq!(size_of::<Item>(), 24);
assert_eq!(size_of::<StrftimeItems>(), 56);
assert_eq!(size_of::<Locale>(), 2);
}
#[test]
#[cfg(all(feature = "unstable-locales", target_pointer_width = "32"))]
fn test_type_sizes() {
use core::mem::size_of;
assert_eq!(size_of::<Item>(), 12);
assert_eq!(size_of::<StrftimeItems>(), 28);
assert_eq!(size_of::<Locale>(), 2);
}
}

View File

@ -1,20 +1,21 @@
//! # Chrono: Date and Time for Rust
//!
//! 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 a `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:
//!
//! * [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)
//! 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.
//!
//! ### Features
//!
@ -29,10 +30,13 @@
//! and traits.
//! - `clock`: Enables reading the system time (`now`) that depends on the standard library for
//! UNIX-like operating systems and the Windows API (`winapi`) for Windows.
//! - `wasmbind`: Interface with the JS Date API for the `wasm32` target.
//!
//! Optional features:
//!
//! - [`serde`][]: Enable serialization/deserialization via serde.
//! - `rkyv`: Enable serialization/deserialization via rkyv.
//! - `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.
@ -284,16 +288,12 @@
//! Ok(fixed_dt.clone()));
//! assert_eq!(DateTime::<FixedOffset>::parse_from_rfc3339("2014-11-28T21:00:09+09:00"), Ok(fixed_dt.clone()));
//!
//! // method 3
//! assert_eq!(Utc.datetime_from_str("2014-11-28 12:00:09", "%Y-%m-%d %H:%M:%S"), Ok(dt.clone()));
//! assert_eq!(Utc.datetime_from_str("Fri Nov 28 12:00:09 2014", "%a %b %e %T %Y"), Ok(dt.clone()));
//!
//! // oops, the year is missing!
//! assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err());
//! assert!(DateTime::<FixedOffset>::parse_from_str("Fri Nov 28 12:00:09", "%a %b %e %T %Y").is_err());
//! // oops, the format string does not include the year at all!
//! assert!(Utc.datetime_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err());
//! assert!(DateTime::<FixedOffset>::parse_from_str("Fri Nov 28 12:00:09", "%a %b %e %T").is_err());
//! // oops, the weekday is incorrect!
//! assert!(Utc.datetime_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err());
//! assert!(DateTime::<FixedOffset>::parse_from_str("Sat Nov 28 12:00:09 2014", "%a %b %e %T %Y").is_err());
//! ```
//!
//! Again : See [`format::strftime`](./format/strftime/index.html#specifiers)
@ -343,18 +343,18 @@
//!
//! ## 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.
//! ## Rust version requirements
//!
//! [Leap seconds are supported in the representation but
//! Chrono doesn't try to make use of them](./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.
//! The Minimum Supported Rust Version (MSRV) is currently **Rust 1.57.0**.
//!
//! The MSRV is explicitly tested in CI. It may be bumped in minor releases, but this is not done
//! lightly.
//!
//! 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.
@ -367,6 +367,89 @@
//!
//! Advanced time zone handling is not yet supported.
//! For now you can try the [Chrono-tz](https://github.com/chronotope/chrono-tz/) crate instead.
//!
//! ## Relation between chrono and time 0.1
//!
//! Rust first had a `time` module added to `std` in its 0.7 release. It later moved to
//! `libextra`, and then to a `libtime` library shipped alongside the standard library. In 2014
//! work on chrono started in order to provide a full-featured date and time library in Rust.
//! Some improvements from chrono made it into the standard library; notably, `chrono::Duration`
//! was included as `std::time::Duration` ([rust#15934]) in 2014.
//!
//! In preparation of Rust 1.0 at the end of 2014 `libtime` was moved out of the Rust distro and
//! into the `time` crate to eventually be redesigned ([rust#18832], [rust#18858]), like the
//! `num` and `rand` crates. Of course chrono kept its dependency on this `time` crate. `time`
//! started re-exporting `std::time::Duration` during this period. Later, the standard library was
//! changed to have a more limited unsigned `Duration` type ([rust#24920], [RFC 1040]), while the
//! `time` crate kept the full functionality with `time::Duration`. `time::Duration` had been a
//! part of chrono's public API.
//!
//! By 2016 `time` 0.1 lived under the `rust-lang-deprecated` organisation and was not actively
//! maintained ([time#136]). chrono absorbed the platform functionality and `Duration` type of the
//! `time` crate in [chrono#478] (the work started in [chrono#286]). In order to preserve
//! compatibility with downstream crates depending on `time` and `chrono` sharing a `Duration`
//! type, chrono kept depending on time 0.1. chrono offered the option to opt out of the `time`
//! dependency by disabling the `oldtime` feature (swapping it out for an effectively similar
//! chrono type). In 2019, @jhpratt took over maintenance on the `time` crate and released what
//! amounts to a new crate as `time` 0.2.
//!
//! [rust#15934]: https://github.com/rust-lang/rust/pull/15934
//! [rust#18832]: https://github.com/rust-lang/rust/pull/18832#issuecomment-62448221
//! [rust#18858]: https://github.com/rust-lang/rust/pull/18858
//! [rust#24920]: https://github.com/rust-lang/rust/pull/24920
//! [RFC 1040]: https://rust-lang.github.io/rfcs/1040-duration-reform.html
//! [time#136]: https://github.com/time-rs/time/issues/136
//! [chrono#286]: https://github.com/chronotope/chrono/pull/286
//! [chrono#478]: https://github.com/chronotope/chrono/pull/478
//!
//! ## Security advisories
//!
//! In November of 2020 [CVE-2020-26235] and [RUSTSEC-2020-0071] were opened against the `time` crate.
//! @quininer had found that calls to `localtime_r` may be unsound ([chrono#499]). Eventually, almost
//! a year later, this was also made into a security advisory against chrono as [RUSTSEC-2020-0159],
//! which had platform code similar to `time`.
//!
//! On Unix-like systems a process is given a timezone id or description via the `TZ` environment
//! variable. We need this timezone data to calculate the current local time from a value that is
//! in UTC, such as the time from the system clock. `time` 0.1 and chrono used the POSIX function
//! `localtime_r` to do the conversion to local time, which reads the `TZ` variable.
//!
//! Rust assumes the environment to be writable and uses locks to access it from multiple threads.
//! Some other programming languages and libraries use similar locking strategies, but these are
//! typically not shared across languages. More importantly, POSIX declares modifying the
//! environment in a multi-threaded process as unsafe, and `getenv` in libc can't be changed to
//! take a lock because it returns a pointer to the data (see [rust#27970] for more discussion).
//!
//! Since version 4.20 chrono no longer uses `localtime_r`, instead using Rust code to query the
//! timezone (from the `TZ` variable or via `iana-time-zone` as a fallback) and work with data
//! from the system timezone database directly. The code for this was forked from the [tz-rs crate]
//! by @x-hgg-x. As such, chrono now respects the Rust lock when reading the `TZ` environment
//! variable. In general, code should avoid modifying the environment.
//!
//! [CVE-2020-26235]: https://nvd.nist.gov/vuln/detail/CVE-2020-26235
//! [RUSTSEC-2020-0071]: https://rustsec.org/advisories/RUSTSEC-2020-0071
//! [chrono#499]: https://github.com/chronotope/chrono/pull/499
//! [RUSTSEC-2020-0159]: https://rustsec.org/advisories/RUSTSEC-2020-0159.html
//! [rust#27970]: https://github.com/rust-lang/rust/issues/27970
//! [chrono#677]: https://github.com/chronotope/chrono/pull/677
//! [tz-rs crate]: https://crates.io/crates/tz-rs
//!
//! ## Removing time 0.1
//!
//! Because time 0.1 has been unmaintained for years, however, the security advisory mentioned
//! above has not been addressed. While chrono maintainers were careful not to break backwards
//! compatibility with the `time::Duration` type, there has been a long stream of issues from
//! users inquiring about the time 0.1 dependency with the vulnerability. We investigated the
//! potential breakage of removing the time 0.1 dependency in [chrono#1095] using a crater-like
//! experiment and determined that the potential for breaking (public) dependencies is very low.
//! We reached out to those few crates that did still depend on compatibility with time 0.1.
//!
//! As such, for chrono 0.4.30 we have decided to swap out the time 0.1 `Duration` implementation
//! for a local one that will offer a strict superset of the existing API going forward. This
//! will prevent most downstream users from being affected by the security vulnerability in time
//! 0.1 while minimizing the ecosystem impact of semver-incompatible version churn.
//!
//! [chrono#1095]: https://github.com/chronotope/chrono/pull/1095
#![doc(html_root_url = "https://docs.rs/chrono/latest/", test(attr(deny(warnings))))]
#![cfg_attr(feature = "bench", feature(test))] // lib stability features as per RFC #507
@ -385,14 +468,6 @@ use core::fmt;
mod time_delta;
pub use time_delta::TimeDelta;
#[cfg(feature = "__doctest")]
#[cfg_attr(feature = "__doctest", cfg(doctest))]
use doc_comment::doctest;
#[cfg(feature = "__doctest")]
#[cfg_attr(feature = "__doctest", cfg(doctest))]
doctest!("../README.md");
/// A convenience module appropriate for glob imports (`use chrono::prelude::*;`).
pub mod prelude {
#[doc(no_inline)]

View File

@ -1409,6 +1409,21 @@ impl NaiveDate {
NaiveWeek { date: *self, start }
}
/// Returns `true` if this is a leap year.
///
/// ```
/// # use chrono::NaiveDate;
/// assert_eq!(NaiveDate::from_ymd_opt(2000, 1, 1).unwrap().leap_year(), true);
/// assert_eq!(NaiveDate::from_ymd_opt(2001, 1, 1).unwrap().leap_year(), false);
/// assert_eq!(NaiveDate::from_ymd_opt(2002, 1, 1).unwrap().leap_year(), false);
/// assert_eq!(NaiveDate::from_ymd_opt(2003, 1, 1).unwrap().leap_year(), false);
/// assert_eq!(NaiveDate::from_ymd_opt(2004, 1, 1).unwrap().leap_year(), true);
/// assert_eq!(NaiveDate::from_ymd_opt(2100, 1, 1).unwrap().leap_year(), false);
/// ```
pub const fn leap_year(&self) -> bool {
self.ymdf & (0b1000) == 0
}
// This duplicates `Datelike::year()`, because trait methods can't be const yet.
#[inline]
const fn year(&self) -> i32 {
@ -3172,6 +3187,16 @@ mod tests {
assert!(dt.with_ordinal0(4294967295).is_none());
}
#[test]
fn test_leap_year() {
for year in 0..=MAX_YEAR {
let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap();
let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
assert_eq!(date.leap_year(), is_leap);
assert_eq!(date.leap_year(), date.with_ordinal(366).is_some());
}
}
// MAX_YEAR-12-31 minus 0000-01-01
// = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + (0001-01-01 minus 0000-01-01) - 1 day
// = ((MAX_YEAR+1)-01-01 minus 0001-01-01) + 365 days

View File

@ -348,6 +348,51 @@ fn test_datetime_parse_from_str() {
);
}
#[test]
fn test_datetime_parse_from_str_with_spaces() {
let parse_from_str = NaiveDateTime::parse_from_str;
let dt = NaiveDate::from_ymd_opt(2013, 8, 9).unwrap().and_hms_opt(23, 54, 35).unwrap();
// with varying spaces - should succeed
assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S "), Ok(dt));
assert_eq!(parse_from_str(" Aug 09 2013 23:54:35 ", " %b %d %Y %H:%M:%S "), Ok(dt));
assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str(" Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("\n\tAug 09 2013 23:54:35 ", "\n\t%b %d %Y %H:%M:%S "), Ok(dt),);
assert_eq!(parse_from_str("\tAug 09 2013 23:54:35\t", "\t%b %d %Y %H:%M:%S\t"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013\t23:54:35", "%b %d %Y\t%H:%M:%S"), Ok(dt));
assert_eq!(parse_from_str("Aug 09 2013\t\t23:54:35", "%b %d %Y\t\t%H:%M:%S"), Ok(dt));
// with varying spaces - should fail
// leading whitespace in format
assert!(parse_from_str("Aug 09 2013 23:54:35", " %b %d %Y %H:%M:%S").is_err());
// trailing whitespace in format
assert!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err());
// extra mid-string whitespace in format
assert!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err());
// mismatched leading whitespace
assert!(parse_from_str("\tAug 09 2013 23:54:35", "\n%b %d %Y %H:%M:%S").is_err());
// mismatched trailing whitespace
assert!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S\n").is_err());
// mismatched mid-string whitespace
assert!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y\t%H:%M:%S").is_err());
// trailing whitespace in format
assert!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S ").is_err());
// trailing whitespace (newline) in format
assert!(parse_from_str("Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err());
// leading space in data
assert!(parse_from_str(" Aug 09 2013 23:54:35", "%b %d %Y %H:%M:%S").is_err());
// trailing space in data
assert!(parse_from_str("Aug 09 2013 23:54:35 ", "%b %d %Y %H:%M:%S").is_err());
// trailing tab in data
assert!(parse_from_str("Aug 09 2013 23:54:35\t", "%b %d %Y %H:%M:%S").is_err());
// mismatched newlines
assert!(parse_from_str("\nAug 09 2013 23:54:35", "%b %d %Y %H:%M:%S\n").is_err());
// trailing literal in data
assert!(parse_from_str("Aug 09 2013 23:54:35 !!!", "%b %d %Y %H:%M:%S ").is_err());
}
#[test]
fn test_datetime_add_sub_invariant() {
// issue #37

View File

@ -257,12 +257,4 @@ mod tests {
);
}
}
/// Test Issue #866
#[test]
fn test_issue_866() {
#[allow(deprecated)]
let local_20221106 = Local.ymd(2022, 11, 6);
let _dt_20221106 = local_20221106.and_hms_milli_opt(1, 2, 59, 1000).unwrap();
}
}

View File

@ -859,8 +859,12 @@ mod tests {
assert_eq!(time_zone_local, time_zone_local_1);
}
let time_zone_utc = TimeZone::from_posix_tz("UTC")?;
assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0);
// `TimeZone::from_posix_tz("UTC")` will return `Error` if the environment does not have
// a time zone database, like for example some docker containers.
// In that case skip the test.
if let Ok(time_zone_utc) = TimeZone::from_posix_tz("UTC") {
assert_eq!(time_zone_utc.find_local_time_type(0)?.offset(), 0);
}
}
assert!(TimeZone::from_posix_tz("EST5EDT,0/0,J365/25").is_err());

View File

@ -444,6 +444,7 @@ pub trait TimeZone: Sized + Clone {
///
/// See also [`DateTime::parse_from_str`] which gives a [`DateTime`] with
/// parsed [`FixedOffset`].
#[deprecated(since = "0.4.29", note = "use `DateTime::parse_from_str` instead")]
fn datetime_from_str(&self, s: &str, fmt: &str) -> ParseResult<DateTime<Self>> {
let mut parsed = Parsed::new();
parse(&mut parsed, s, StrftimeItems::new(fmt))?;

View File

@ -34,9 +34,9 @@ const SECS_PER_MINUTE: i64 = 60;
/// The number of seconds in an hour.
const SECS_PER_HOUR: i64 = 3600;
/// The number of (non-leap) seconds in days.
const SECS_PER_DAY: i64 = 86400;
const SECS_PER_DAY: i64 = 86_400;
/// The number of (non-leap) seconds in a week.
const SECS_PER_WEEK: i64 = 604800;
const SECS_PER_WEEK: i64 = 604_800;
macro_rules! try_opt {
($e:expr) => {
@ -492,19 +492,22 @@ mod tests {
assert!(TimeDelta::seconds(1) != TimeDelta::zero());
assert_eq!(TimeDelta::seconds(1) + TimeDelta::seconds(2), TimeDelta::seconds(3));
assert_eq!(
TimeDelta::seconds(86399) + TimeDelta::seconds(4),
TimeDelta::seconds(86_399) + TimeDelta::seconds(4),
TimeDelta::days(1) + TimeDelta::seconds(3)
);
assert_eq!(TimeDelta::days(10) - TimeDelta::seconds(1000), TimeDelta::seconds(863000));
assert_eq!(TimeDelta::days(10) - TimeDelta::seconds(1000000), TimeDelta::seconds(-136000));
assert_eq!(TimeDelta::days(10) - TimeDelta::seconds(1000), TimeDelta::seconds(863_000));
assert_eq!(
TimeDelta::days(2) + TimeDelta::seconds(86399) + TimeDelta::nanoseconds(1234567890),
TimeDelta::days(3) + TimeDelta::nanoseconds(234567890)
TimeDelta::days(10) - TimeDelta::seconds(1_000_000),
TimeDelta::seconds(-136_000)
);
assert_eq!(
TimeDelta::days(2) + TimeDelta::seconds(86_399) + TimeDelta::nanoseconds(1_234_567_890),
TimeDelta::days(3) + TimeDelta::nanoseconds(234_567_890)
);
assert_eq!(-TimeDelta::days(3), TimeDelta::days(-3));
assert_eq!(
-(TimeDelta::days(3) + TimeDelta::seconds(70)),
TimeDelta::days(-4) + TimeDelta::seconds(86400 - 70)
TimeDelta::days(-4) + TimeDelta::seconds(86_400 - 70)
);
}
@ -513,10 +516,10 @@ mod tests {
assert_eq!(TimeDelta::zero().num_days(), 0);
assert_eq!(TimeDelta::days(1).num_days(), 1);
assert_eq!(TimeDelta::days(-1).num_days(), -1);
assert_eq!(TimeDelta::seconds(86399).num_days(), 0);
assert_eq!(TimeDelta::seconds(86401).num_days(), 1);
assert_eq!(TimeDelta::seconds(-86399).num_days(), 0);
assert_eq!(TimeDelta::seconds(-86401).num_days(), -1);
assert_eq!(TimeDelta::seconds(86_399).num_days(), 0);
assert_eq!(TimeDelta::seconds(86_401).num_days(), 1);
assert_eq!(TimeDelta::seconds(-86_399).num_days(), 0);
assert_eq!(TimeDelta::seconds(-86_401).num_days(), -1);
assert_eq!(TimeDelta::days(i32::MAX as i64).num_days(), i32::MAX as i64);
assert_eq!(TimeDelta::days(i32::MIN as i64).num_days(), i32::MIN as i64);
}
@ -705,7 +708,7 @@ mod tests {
assert_eq!(TimeDelta::microseconds(42).to_string(), "PT0.000042S");
assert_eq!(TimeDelta::nanoseconds(42).to_string(), "PT0.000000042S");
assert_eq!((TimeDelta::days(7) + TimeDelta::milliseconds(6543)).to_string(), "P7DT6.543S");
assert_eq!(TimeDelta::seconds(-86401).to_string(), "-P1DT1S");
assert_eq!(TimeDelta::seconds(-86_401).to_string(), "-P1DT1S");
assert_eq!(TimeDelta::nanoseconds(-1).to_string(), "-PT0.000000001S");
// the format specifier should have no effect on `TimeDelta`
@ -718,11 +721,14 @@ mod tests {
#[test]
fn test_to_std() {
assert_eq!(TimeDelta::seconds(1).to_std(), Ok(StdDuration::new(1, 0)));
assert_eq!(TimeDelta::seconds(86401).to_std(), Ok(StdDuration::new(86401, 0)));
assert_eq!(TimeDelta::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123000000)));
assert_eq!(TimeDelta::milliseconds(123765).to_std(), Ok(StdDuration::new(123, 765000000)));
assert_eq!(TimeDelta::seconds(86_401).to_std(), Ok(StdDuration::new(86_401, 0)));
assert_eq!(TimeDelta::milliseconds(123).to_std(), Ok(StdDuration::new(0, 123_000_000)));
assert_eq!(
TimeDelta::milliseconds(123_765).to_std(),
Ok(StdDuration::new(123, 765_000_000))
);
assert_eq!(TimeDelta::nanoseconds(777).to_std(), Ok(StdDuration::new(0, 777)));
assert_eq!(MAX.to_std(), Ok(StdDuration::new(9223372036854775, 807000000)));
assert_eq!(MAX.to_std(), Ok(StdDuration::new(9_223_372_036_854_775, 807_000_000)));
assert_eq!(TimeDelta::seconds(-1).to_std(), Err(OutOfRangeError(())));
assert_eq!(TimeDelta::milliseconds(-1).to_std(), Err(OutOfRangeError(())));
}
@ -730,23 +736,26 @@ mod tests {
#[test]
fn test_from_std() {
assert_eq!(Ok(TimeDelta::seconds(1)), TimeDelta::from_std(StdDuration::new(1, 0)));
assert_eq!(Ok(TimeDelta::seconds(86401)), TimeDelta::from_std(StdDuration::new(86401, 0)));
assert_eq!(Ok(TimeDelta::seconds(86401)), TimeDelta::from_std(StdDuration::new(86_401, 0)));
assert_eq!(
Ok(TimeDelta::milliseconds(123)),
TimeDelta::from_std(StdDuration::new(0, 123000000))
TimeDelta::from_std(StdDuration::new(0, 123_000_000))
);
assert_eq!(
Ok(TimeDelta::milliseconds(123765)),
TimeDelta::from_std(StdDuration::new(123, 765000000))
Ok(TimeDelta::milliseconds(123_765)),
TimeDelta::from_std(StdDuration::new(123, 765_000_000))
);
assert_eq!(Ok(TimeDelta::nanoseconds(777)), TimeDelta::from_std(StdDuration::new(0, 777)));
assert_eq!(Ok(MAX), TimeDelta::from_std(StdDuration::new(9223372036854775, 807000000)));
assert_eq!(
TimeDelta::from_std(StdDuration::new(9223372036854776, 0)),
Ok(MAX),
TimeDelta::from_std(StdDuration::new(9_223_372_036_854_775, 807_000_000))
);
assert_eq!(
TimeDelta::from_std(StdDuration::new(9_223_372_036_854_776, 0)),
Err(OutOfRangeError(()))
);
assert_eq!(
TimeDelta::from_std(StdDuration::new(9223372036854775, 807000001)),
TimeDelta::from_std(StdDuration::new(9_223_372_036_854_775, 807_000_001)),
Err(OutOfRangeError(()))
);
}

View File

@ -316,6 +316,11 @@ pub trait Timelike: Sized {
fn with_nanosecond(&self, nano: u32) -> Option<Self>;
/// Returns the number of non-leap seconds past the last midnight.
///
/// Every value in 00:00:00-23:59:59 maps to an integer in 0-86399.
///
/// This method is not intended to provide the real number of seconds since midnight on a given
/// day. It does not take things like DST transitions into account.
#[inline]
fn num_seconds_from_midnight(&self) -> u32 {
self.hour() * 3600 + self.minute() * 60 + self.second()