Merge pull request #1141 from pitdicker/merge_0.4.x

Merge 0.4.x into main
This commit is contained in:
Dirkjan Ochtman 2023-06-08 17:46:59 +02:00 committed by GitHub
commit 38b19bbe4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1304 additions and 936 deletions

View File

@ -17,9 +17,8 @@ jobs:
- uses: Swatinem/rust-cache@v2
- 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 \
cargo clippy --all-features --all-targets --color=always \
-- -D warnings
- run: |
cargo clippy --manifest-path fuzz/Cargo.toml --color=always \
@ -27,6 +26,14 @@ jobs:
env:
RUSTFLAGS: "-Dwarnings"
toml:
runs-on: ubuntu-latest
container:
image: tamasfe/taplo:0.8.0
steps:
- run: taplo lint
- run: taplo fmt --check --diff
cargo-deny:
runs-on: ubuntu-latest
steps:

View File

@ -28,8 +28,7 @@ jobs:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --lib --all-features --color=always -- --color=always
- run: cargo test --doc --all-features --color=always -- --color=always
- run: cargo test --all-features --color=always -- --color=always
# later this may be able to be included with the below
# kept separate for now as the following don't compile on 1.56.1
@ -118,7 +117,6 @@ jobs:
os: [ubuntu-latest]
target:
[
wasm32-unknown-unknown,
wasm32-wasi,
wasm32-unknown-emscripten,
aarch64-apple-ios,
@ -131,19 +129,11 @@ jobs:
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v3
with:
node-version: "12"
- run: |
set -euxo pipefail
export RUST_BACKTRACE=1
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:
test_wasm:
strategy:
matrix:
os: [ubuntu-latest]
@ -155,10 +145,11 @@ 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
- uses: actions/setup-node@v3
- uses: jetli/wasm-pack-action@v0.4.0
# The `TZ` and `NOW` variables are used to compare the results inside the WASM environment
# with the host system.
- run: TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind
cross-targets:
strategy:

View File

@ -31,12 +31,12 @@ __doctest = []
serde = { version = "1.0.99", default-features = false, optional = true }
pure-rust-locales = { version = "0.5.2", optional = true }
criterion = { version = "0.4.0", optional = true }
rkyv = {version = "0.7", optional = true}
rkyv = { version = "0.7", optional = true }
arbitrary = { version = "1.0.0", features = ["derive"], optional = true }
[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies]
wasm-bindgen = { version = "0.2", optional = true }
js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API
js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.48.0", features = ["Win32_System_Time", "Win32_System_SystemInformation", "Win32_Foundation"], optional = true }

View File

@ -1,26 +0,0 @@
# this Makefile is mostly for the packaging convenience.
# casual users should use `cargo` to retrieve the appropriate version of Chrono.
CHANNEL=stable
.PHONY: all
all:
@echo 'Try `cargo build` instead.'
.PHONY: authors
authors:
echo 'Chrono is mainly written by Kang Seonghoon <public+rust@mearie.org>,' > AUTHORS.txt
echo 'and also the following people (in ascending order):' >> AUTHORS.txt
echo >> AUTHORS.txt
git log --format='%aN <%aE>' | grep -v 'Kang Seonghoon' | sort -u >> AUTHORS.txt
.PHONY: readme README.md
readme: README.md
.PHONY: test
test:
CHANNEL=$(CHANNEL) ./ci/travis.sh
.PHONY: doc
doc: authors readme
cargo doc --features 'serde bincode'

View File

@ -2,8 +2,8 @@
name = "core-test"
version = "0.1.0"
authors = [
"Kang Seonghoon <public+rust@mearie.org>",
"Brandon W Maister <quodlibetor@gmail.com>",
"Kang Seonghoon <public+rust@mearie.org>",
"Brandon W Maister <quodlibetor@gmail.com>",
]
edition = "2018"
@ -11,4 +11,4 @@ edition = "2018"
chrono = { path = "../..", default-features = false, features = ["serde"] }
[features]
alloc = ["chrono/alloc"]
alloc = ["chrono/alloc"]

View File

@ -4,8 +4,8 @@ copyleft = "deny"
[advisories]
ignore = [
"RUSTSEC-2021-0145", # atty (dev-deps only, dependency of criterion)
"RUSTSEC-2022-0004", # rustc_serialize, cannot remove due to compatibility
"RUSTSEC-2021-0145", # atty (dev-deps only, dependency of criterion)
"RUSTSEC-2022-0004", # rustc_serialize, cannot remove due to compatibility
]
unmaintained = "deny"
unsound = "deny"

View File

@ -74,8 +74,6 @@ pub const MAX_DATE: Date<Utc> = Date::<Utc>::MAX_UTC;
impl<Tz: TimeZone> Date<Tz> {
/// Makes a new `Date` with given *UTC* date and offset.
/// The local date should be constructed via the `TimeZone` trait.
//
// note: this constructor is purposely not named to `new` to discourage the direct usage.
#[inline]
#[must_use]
pub fn from_utc(date: NaiveDate, offset: Tz::Offset) -> Date<Tz> {
@ -85,7 +83,7 @@ impl<Tz: TimeZone> Date<Tz> {
/// Makes a new `DateTime` from the current date and given `NaiveTime`.
/// The offset in the current date is preserved.
///
/// Panics on invalid datetime.
/// Returns `None` on invalid datetime.
#[inline]
#[must_use]
pub fn and_time(&self, time: NaiveTime) -> Option<DateTime<Tz>> {

View File

@ -8,7 +8,6 @@ extern crate alloc;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::string::{String, ToString};
#[cfg(any(feature = "alloc", feature = "std", test))]
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::fmt::Write;
@ -102,8 +101,6 @@ impl<Tz: TimeZone> DateTime<Tz> {
/// let dt = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp_opt(61, 0).unwrap(), Utc);
/// assert_eq!(Utc.timestamp_opt(61, 0).unwrap(), dt);
/// ```
//
// note: this constructor is purposely not named to `new` to discourage the direct usage.
#[inline]
#[must_use]
pub fn from_utc(datetime: NaiveDateTime, offset: Tz::Offset) -> DateTime<Tz> {
@ -113,6 +110,13 @@ impl<Tz: TimeZone> DateTime<Tz> {
/// Makes a new `DateTime` with given **local** datetime and offset that
/// presents local timezone.
///
/// # Panics
///
/// Panics if the local datetime can't be converted to UTC because it would be out of range.
///
/// This can happen if `datetime` is near the end of the representable range of `NaiveDateTime`,
/// and the offset from UTC pushes it beyond that.
///
/// # Example
///
/// ```
@ -142,11 +146,19 @@ impl<Tz: TimeZone> DateTime<Tz> {
DateTime { datetime: datetime_utc, offset }
}
/// Retrieves a date component
/// Retrieves the date component with an associated timezone.
///
/// Unless you are immediately planning on turning this into a `DateTime`
/// with the same Timezone you should use the
/// [`date_naive`](DateTime::date_naive) method.
/// with the same timezone you should use the [`date_naive`](DateTime::date_naive) method.
///
/// [`NaiveDate`] is a more well-defined type, and has more traits implemented on it,
/// so should be preferred to [`Date`] any time you truly want to operate on dates.
///
/// # Panics
///
/// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This
/// method will panic if the offset from UTC would push the local date outside of the
/// representable range of a [`Date`].
#[inline]
#[deprecated(since = "0.4.23", note = "Use `date_naive()` instead")]
#[allow(deprecated)]
@ -155,10 +167,15 @@ impl<Tz: TimeZone> DateTime<Tz> {
Date::from_utc(self.naive_local().date(), self.offset.clone())
}
/// Retrieves the Date without an associated timezone
/// Retrieves the date component.
///
/// [`NaiveDate`] is a more well-defined type, and has more traits implemented on it,
/// so should be preferred to [`Date`] any time you truly want to operate on Dates.
/// # Panics
///
/// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This
/// method will panic if the offset from UTC would push the local date outside of the
/// representable range of a [`NaiveDate`].
///
/// # Example
///
/// ```
/// use chrono::prelude::*;
@ -174,8 +191,7 @@ impl<Tz: TimeZone> DateTime<Tz> {
NaiveDate::from_ymd_opt(local.year(), local.month(), local.day()).unwrap()
}
/// Retrieves a time component.
/// Unlike `date`, this is not associated to the time zone.
/// Retrieves the time component.
#[inline]
#[must_use]
pub fn time(&self) -> NaiveTime {
@ -190,12 +206,7 @@ impl<Tz: TimeZone> DateTime<Tz> {
self.datetime.timestamp()
}
/// Returns the number of non-leap-milliseconds since January 1, 1970 UTC
///
/// Note that this does reduce the number of years that can be represented
/// from ~584 Billion to ~584 Million. (If this is a problem, please file
/// an issue to let me know what domain needs millisecond precision over
/// billions of years, I'm curious.)
/// Returns the number of non-leap-milliseconds since January 1, 1970 UTC.
///
/// # Example
///
@ -214,12 +225,7 @@ impl<Tz: TimeZone> DateTime<Tz> {
self.datetime.timestamp_millis()
}
/// Returns the number of non-leap-microseconds since January 1, 1970 UTC
///
/// Note that this does reduce the number of years that can be represented
/// from ~584 Billion to ~584 Thousand. (If this is a problem, please file
/// an issue to let me know what domain needs microsecond precision over
/// millennia, I'm curious.)
/// Returns the number of non-leap-microseconds since January 1, 1970 UTC.
///
/// # Example
///
@ -238,12 +244,15 @@ impl<Tz: TimeZone> DateTime<Tz> {
self.datetime.timestamp_micros()
}
/// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC
/// Returns the number of non-leap-nanoseconds since January 1, 1970 UTC.
///
/// Note that this does reduce the number of years that can be represented
/// from ~584 Billion to ~584. (If this is a problem, please file
/// an issue to let me know what domain needs nanosecond precision over
/// millennia, I'm curious.)
/// # Panics
///
/// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on
/// an out of range `DateTime`.
///
/// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and
/// 2262-04-11T23:47:16.854775804.
///
/// # Example
///
@ -262,22 +271,18 @@ impl<Tz: TimeZone> DateTime<Tz> {
self.datetime.timestamp_nanos()
}
/// Returns the number of milliseconds since the last second boundary
/// Returns the number of milliseconds since the last second boundary.
///
/// warning: in event of a leap second, this may exceed 999
///
/// note: this is not the number of milliseconds since January 1, 1970 0:00:00 UTC
/// In event of a leap second this may exceed 999.
#[inline]
#[must_use]
pub fn timestamp_subsec_millis(&self) -> u32 {
self.datetime.timestamp_subsec_millis()
}
/// Returns the number of microseconds since the last second boundary
/// Returns the number of microseconds since the last second boundary.
///
/// warning: in event of a leap second, this may exceed 999_999
///
/// note: this is not the number of microseconds since January 1, 1970 0:00:00 UTC
/// In event of a leap second this may exceed 999,999.
#[inline]
#[must_use]
pub fn timestamp_subsec_micros(&self) -> u32 {
@ -286,9 +291,7 @@ impl<Tz: TimeZone> DateTime<Tz> {
/// Returns the number of nanoseconds since the last second boundary
///
/// warning: in event of a leap second, this may exceed 999_999_999
///
/// note: this is not the number of nanoseconds since January 1, 1970 0:00:00 UTC
/// In event of a leap second this may exceed 999,999,999.
#[inline]
#[must_use]
pub fn timestamp_subsec_nanos(&self) -> u32 {
@ -310,7 +313,8 @@ impl<Tz: TimeZone> DateTime<Tz> {
}
/// Changes the associated time zone.
/// The returned `DateTime` references the same instant of time from the perspective of the provided time zone.
/// The returned `DateTime` references the same instant of time from the perspective of the
/// provided time zone.
#[inline]
#[must_use]
pub fn with_timezone<Tz2: TimeZone>(&self, tz: &Tz2) -> DateTime<Tz2> {
@ -327,7 +331,9 @@ impl<Tz: TimeZone> DateTime<Tz> {
/// Adds given `Duration` to the current date and time.
///
/// Returns `None` when it will result in overflow.
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
#[inline]
#[must_use]
pub fn checked_add_signed(self, rhs: TimeDelta) -> Option<DateTime<Tz>> {
@ -338,10 +344,16 @@ impl<Tz: TimeZone> DateTime<Tz> {
/// Adds given `Months` to the current date and time.
///
/// Returns `None` when it will result in overflow, or if the
/// local time is not valid on the newly calculated date.
/// Uses the last day of the month if the day does not exist in the resulting month.
///
/// See [`NaiveDate::checked_add_months`] for more details on behavior
/// See [`NaiveDate::checked_add_months`] for more details on behavior.
///
/// # Errors
///
/// Returns `None` if:
/// - The resulting date would be out of range.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[must_use]
pub fn checked_add_months(self, rhs: Months) -> Option<DateTime<Tz>> {
self.naive_local()
@ -352,7 +364,9 @@ impl<Tz: TimeZone> DateTime<Tz> {
/// Subtracts given `Duration` from the current date and time.
///
/// Returns `None` when it will result in overflow.
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
#[inline]
#[must_use]
pub fn checked_sub_signed(self, rhs: TimeDelta) -> Option<DateTime<Tz>> {
@ -363,10 +377,16 @@ impl<Tz: TimeZone> DateTime<Tz> {
/// Subtracts given `Months` from the current date and time.
///
/// Returns `None` when it will result in overflow, or if the
/// local time is not valid on the newly calculated date.
/// Uses the last day of the month if the day does not exist in the resulting month.
///
/// See [`NaiveDate::checked_sub_months`] for more details on behavior
/// See [`NaiveDate::checked_sub_months`] for more details on behavior.
///
/// # Errors
///
/// Returns `None` if:
/// - The resulting date would be out of range.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[must_use]
pub fn checked_sub_months(self, rhs: Months) -> Option<DateTime<Tz>> {
self.naive_local()
@ -375,9 +395,14 @@ impl<Tz: TimeZone> DateTime<Tz> {
.single()
}
/// Add a duration in [`Days`] to the date part of the `DateTime`
/// Add a duration in [`Days`] to the date part of the `DateTime`.
///
/// Returns `None` if the resulting date would be out of range.
/// # Errors
///
/// Returns `None` if:
/// - The resulting date would be out of range.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[must_use]
pub fn checked_add_days(self, days: Days) -> Option<Self> {
self.naive_local()
@ -386,9 +411,14 @@ impl<Tz: TimeZone> DateTime<Tz> {
.single()
}
/// Subtract a duration in [`Days`] from the date part of the `DateTime`
/// Subtract a duration in [`Days`] from the date part of the `DateTime`.
///
/// Returns `None` if the resulting date would be out of range.
/// # Errors
///
/// Returns `None` if:
/// - The resulting date would be out of range.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[must_use]
pub fn checked_sub_days(self, days: Days) -> Option<Self> {
self.naive_local()
@ -401,8 +431,11 @@ impl<Tz: TimeZone> DateTime<Tz> {
/// This does not overflow or underflow at all.
#[inline]
#[must_use]
pub fn signed_duration_since<Tz2: TimeZone>(self, rhs: DateTime<Tz2>) -> TimeDelta {
self.datetime.signed_duration_since(rhs.datetime)
pub fn signed_duration_since<Tz2: TimeZone>(
self,
rhs: impl Borrow<DateTime<Tz2>>,
) -> TimeDelta {
self.datetime.signed_duration_since(rhs.borrow().datetime)
}
/// Returns a view to the naive UTC datetime.
@ -413,6 +446,12 @@ impl<Tz: TimeZone> DateTime<Tz> {
}
/// Returns a view to the naive local datetime.
///
/// # Panics
///
/// [`DateTime`] internally stores the date and time in UTC with a [`NaiveDateTime`]. This
/// method will panic if the offset from UTC would push the local datetime outside of the
/// representable range of a [`NaiveDateTime`].
#[inline]
#[must_use]
pub fn naive_local(&self) -> NaiveDateTime {
@ -420,6 +459,10 @@ impl<Tz: TimeZone> DateTime<Tz> {
}
/// Retrieve the elapsed years from now to the given [`DateTime`].
///
/// # Errors
///
/// Returns `None` if `base < self`.
#[must_use]
pub fn years_since(&self, base: Self) -> Option<u32> {
let mut years = self.year() - base.year();
@ -715,6 +758,11 @@ where
Tz::Offset: fmt::Display,
{
/// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`.
///
/// # Panics
///
/// Panics if the date can not be represented in this format: the year may not be negative and
/// can not have more than 4 digits.
#[cfg(any(feature = "alloc", feature = "std", test))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))]
#[must_use]
@ -917,35 +965,112 @@ impl<Tz: TimeZone> Datelike for DateTime<Tz> {
}
#[inline]
/// Makes a new `DateTime` with the year number changed, while keeping the same month and day.
///
/// See also the [`NaiveDate::with_year`] method.
///
/// # Errors
///
/// Returns `None` if:
/// - The resulting date does not exist.
/// - When the `NaiveDateTime` would be out of range.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
fn with_year(&self, year: i32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_year(year))
}
/// Makes a new `DateTime` with the month number (starting from 1) changed.
///
/// See also the [`NaiveDate::with_month`] method.
///
/// # Errors
///
/// Returns `None` if:
/// - The resulting date does not exist.
/// - The value for `month` is invalid.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[inline]
fn with_month(&self, month: u32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_month(month))
}
/// Makes a new `DateTime` with the month number (starting from 0) changed.
///
/// See also the [`NaiveDate::with_month0`] method.
///
/// # Errors
///
/// Returns `None` if:
/// - The resulting date does not exist.
/// - The value for `month0` is invalid.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[inline]
fn with_month0(&self, month0: u32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_month0(month0))
}
/// Makes a new `DateTime` with the month number (starting from 0) changed.
///
/// See also the [`NaiveDate::with_day`] method.
///
/// # Errors
///
/// Returns `None` if:
/// - The resulting date does not exist.
/// - The value for `day` is invalid.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[inline]
fn with_day(&self, day: u32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_day(day))
}
/// Makes a new `DateTime` with the month number (starting from 0) changed.
///
/// See also the [`NaiveDate::with_day0`] method.
///
/// # Errors
///
/// Returns `None` if:
/// - The resulting date does not exist.
/// - The value for `day0` is invalid.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[inline]
fn with_day0(&self, day0: u32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_day0(day0))
}
/// Makes a new `DateTime` with the month number (starting from 0) changed.
///
/// See also the [`NaiveDate::with_ordinal`] method.
///
/// # Errors
///
/// Returns `None` if:
/// - The resulting date does not exist.
/// - The value for `ordinal` is invalid.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[inline]
fn with_ordinal(&self, ordinal: u32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_ordinal(ordinal))
}
/// Makes a new `DateTime` with the month number (starting from 0) changed.
///
/// See also the [`NaiveDate::with_ordinal0`] method.
///
/// # Errors
///
/// Returns `None` if:
/// - The resulting date does not exist.
/// - The value for `ordinal0` is invalid.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[inline]
fn with_ordinal0(&self, ordinal0: u32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_ordinal0(ordinal0))
@ -970,21 +1095,64 @@ impl<Tz: TimeZone> Timelike for DateTime<Tz> {
self.naive_local().nanosecond()
}
/// Makes a new `DateTime` with the hour number changed.
///
/// See also the [`NaiveTime::with_hour`] method.
///
/// # Errors
///
/// Returns `None` if:
/// - The value for `hour` is invalid.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[inline]
fn with_hour(&self, hour: u32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_hour(hour))
}
/// Makes a new `DateTime` with the minute number changed.
///
/// See also the [`NaiveTime::with_minute`] method.
///
/// # Errors
///
/// - The value for `minute` is invalid.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[inline]
fn with_minute(&self, min: u32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_minute(min))
}
/// Makes a new `DateTime` with the second number changed.
///
/// As with the [`second`](#method.second) method,
/// the input range is restricted to 0 through 59.
///
/// See also the [`NaiveTime::with_second`] method.
///
/// # Errors
///
/// Returns `None` if:
/// - The value for `second` is invalid.
/// - The local time at the resulting date does not exist or is ambiguous, for example during a
/// daylight saving time transition.
#[inline]
fn with_second(&self, sec: u32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_second(sec))
}
/// Makes a new `DateTime` with nanoseconds since the whole non-leap second changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid.
/// As with the [`NaiveDateTime::nanosecond`] method,
/// the input range can exceed 1,000,000,000 for leap seconds.
///
/// See also the [`NaiveTime::with_nanosecond`] method.
///
/// # Errors
///
/// Returns `None` if `nanosecond >= 2,000,000,000`.
#[inline]
fn with_nanosecond(&self, nano: u32) -> Option<DateTime<Tz>> {
map_local(self, |datetime| datetime.with_nanosecond(nano))
@ -1099,6 +1267,15 @@ impl<Tz: TimeZone> Sub<DateTime<Tz>> for DateTime<Tz> {
}
}
impl<Tz: TimeZone> Sub<&DateTime<Tz>> for DateTime<Tz> {
type Output = TimeDelta;
#[inline]
fn sub(self, rhs: &DateTime<Tz>) -> TimeDelta {
self.signed_duration_since(rhs)
}
}
impl<Tz: TimeZone> Add<Days> for DateTime<Tz> {
type Output = DateTime<Tz>;
@ -1299,34 +1476,6 @@ where
}
}
#[test]
fn test_add_sub_months() {
let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap();
assert_eq!(utc_dt + Months::new(15), Utc.with_ymd_and_hms(2019, 12, 5, 23, 58, 0).unwrap());
let utc_dt = Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap();
assert_eq!(utc_dt + Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap());
assert_eq!(utc_dt + Months::new(2), Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap());
let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap();
assert_eq!(utc_dt - Months::new(15), Utc.with_ymd_and_hms(2017, 6, 5, 23, 58, 0).unwrap());
let utc_dt = Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap();
assert_eq!(utc_dt - Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap());
assert_eq!(utc_dt - Months::new(2), Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap());
}
#[test]
fn test_auto_conversion() {
let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap();
let cdt_dt = FixedOffset::west_opt(5 * 60 * 60)
.unwrap()
.with_ymd_and_hms(2018, 9, 5, 18, 58, 0)
.unwrap();
let utc_dt2: DateTime<Utc> = cdt_dt.into();
assert_eq!(utc_dt, utc_dt2);
}
#[cfg(all(test, feature = "serde"))]
fn test_encodable_json<FUtc, FFixed, E>(to_string_utc: FUtc, to_string_fixed: FFixed)
where

View File

@ -158,7 +158,6 @@ pub mod ts_nanoseconds {
/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -186,7 +185,6 @@ pub mod ts_nanoseconds {
/// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).unwrap() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<DateTime<Utc>, D::Error>
where
D: de::Deserializer<'de>,
@ -285,7 +283,6 @@ pub mod ts_nanoseconds_option {
/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -316,7 +313,6 @@ pub mod ts_nanoseconds_option {
/// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355733).single() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: de::Deserializer<'de>,
@ -417,7 +413,6 @@ pub mod ts_microseconds {
/// assert_eq!(as_string, r#"{"time":1526522699918355}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -445,7 +440,6 @@ pub mod ts_microseconds {
/// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).unwrap() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<DateTime<Utc>, D::Error>
where
D: de::Deserializer<'de>,
@ -543,7 +537,6 @@ pub mod ts_microseconds_option {
/// assert_eq!(as_string, r#"{"time":1526522699918355}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -574,7 +567,6 @@ pub mod ts_microseconds_option {
/// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918355000).single() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: de::Deserializer<'de>,
@ -675,7 +667,6 @@ pub mod ts_milliseconds {
/// assert_eq!(as_string, r#"{"time":1526522699918}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -703,7 +694,6 @@ pub mod ts_milliseconds {
/// assert_eq!(my_s, S { time: Utc.timestamp_opt(1526522699, 918000000).unwrap() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<DateTime<Utc>, D::Error>
where
D: de::Deserializer<'de>,
@ -798,7 +788,6 @@ pub mod ts_milliseconds_option {
/// assert_eq!(as_string, r#"{"time":1526522699918}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -840,7 +829,6 @@ pub mod ts_milliseconds_option {
/// assert_eq!(t, E::V(S { time: None }));
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: de::Deserializer<'de>,
@ -942,7 +930,6 @@ pub mod ts_seconds {
/// assert_eq!(as_string, r#"{"time":1431684000}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -970,7 +957,6 @@ pub mod ts_seconds {
/// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).unwrap() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<DateTime<Utc>, D::Error>
where
D: de::Deserializer<'de>,
@ -1062,7 +1048,6 @@ pub mod ts_seconds_option {
/// assert_eq!(as_string, r#"{"time":1431684000}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -1093,7 +1078,6 @@ pub mod ts_seconds_option {
/// assert_eq!(my_s, S { time: Utc.timestamp_opt(1431684000, 0).single() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: de::Deserializer<'de>,
@ -1136,30 +1120,36 @@ pub mod ts_seconds_option {
}
}
#[test]
fn test_serde_serialize() {
super::test_encodable_json(serde_json::to_string, serde_json::to_string);
}
#[cfg(test)]
mod tests {
use crate::datetime::{test_decodable_json, test_encodable_json};
use crate::{DateTime, TimeZone, Utc};
#[cfg(feature = "clock")]
#[test]
fn test_serde_deserialize() {
super::test_decodable_json(
|input| serde_json::from_str(input),
|input| serde_json::from_str(input),
|input| serde_json::from_str(input),
);
}
#[test]
fn test_serde_serialize() {
test_encodable_json(serde_json::to_string, serde_json::to_string);
}
#[test]
fn test_serde_bincode() {
// Bincode is relevant to test separately from JSON because
// it is not self-describing.
use bincode::{deserialize, serialize};
#[cfg(feature = "clock")]
#[test]
fn test_serde_deserialize() {
test_decodable_json(
|input| serde_json::from_str(input),
|input| serde_json::from_str(input),
|input| serde_json::from_str(input),
);
}
let dt = Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap();
let encoded = serialize(&dt).unwrap();
let decoded: DateTime<Utc> = deserialize(&encoded).unwrap();
assert_eq!(dt, decoded);
assert_eq!(dt.offset(), decoded.offset());
#[test]
fn test_serde_bincode() {
// Bincode is relevant to test separately from JSON because
// it is not self-describing.
use bincode::{deserialize, serialize};
let dt = Utc.with_ymd_and_hms(2014, 7, 24, 12, 34, 6).unwrap();
let encoded = serialize(&dt).unwrap();
let decoded: DateTime<Utc> = deserialize(&encoded).unwrap();
assert_eq!(dt, decoded);
assert_eq!(dt.offset(), decoded.offset());
}
}

View File

@ -240,6 +240,7 @@ fn test_datetime_sub_months() {
}
// local helper function to easily create a DateTime<FixedOffset>
#[allow(clippy::too_many_arguments)]
fn ymdhms(
fixedoffset: &FixedOffset,
year: i32,
@ -253,6 +254,7 @@ fn ymdhms(
}
// local helper function to easily create a DateTime<FixedOffset>
#[allow(clippy::too_many_arguments)]
fn ymdhms_milli(
fixedoffset: &FixedOffset,
year: i32,
@ -271,6 +273,7 @@ fn ymdhms_milli(
}
// local helper function to easily create a DateTime<FixedOffset>
#[allow(clippy::too_many_arguments)]
fn ymdhms_micro(
fixedoffset: &FixedOffset,
year: i32,
@ -289,6 +292,7 @@ fn ymdhms_micro(
}
// local helper function to easily create a DateTime<FixedOffset>
#[allow(clippy::too_many_arguments)]
fn ymdhms_nano(
fixedoffset: &FixedOffset,
year: i32,
@ -400,6 +404,20 @@ fn test_datetime_offset() {
assert!(*edt.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap().offset() != est);
}
#[test]
#[allow(clippy::needless_borrow, clippy::op_ref)]
fn signed_duration_since_autoref() {
let dt1 = Utc.with_ymd_and_hms(2014, 5, 6, 7, 8, 9).unwrap();
let dt2 = Utc.with_ymd_and_hms(2014, 3, 4, 5, 6, 7).unwrap();
let diff1 = dt1.signed_duration_since(dt2); // Copy/consume
let diff2 = dt2.signed_duration_since(&dt1); // Take by reference
assert_eq!(diff1, -diff2);
let diff1 = dt1 - &dt2; // We can choose to substract rhs by reference
let diff2 = dt2 - dt1; // Or consume rhs
assert_eq!(diff1, -diff2);
}
#[test]
fn test_datetime_date_and_time() {
let tz = FixedOffset::east_opt(5 * 60 * 60).unwrap();
@ -901,6 +919,10 @@ fn test_utc_datetime_from_str() {
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(),
NaiveDateTime::from_timestamp_opt(0, 0).unwrap().and_utc().fixed_offset()
);
assert_eq!(
"2015-02-18T23:16:9.15Z".parse::<DateTime<FixedOffset>>(),
@ -1549,3 +1571,31 @@ fn test_datetime_fixed_offset() {
let datetime_fixed = fixed_offset.from_local_datetime(&naivedatetime).unwrap();
assert_eq!(datetime_fixed.fixed_offset(), datetime_fixed);
}
#[test]
fn test_add_sub_months() {
let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap();
assert_eq!(utc_dt + Months::new(15), Utc.with_ymd_and_hms(2019, 12, 5, 23, 58, 0).unwrap());
let utc_dt = Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap();
assert_eq!(utc_dt + Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap());
assert_eq!(utc_dt + Months::new(2), Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap());
let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap();
assert_eq!(utc_dt - Months::new(15), Utc.with_ymd_and_hms(2017, 6, 5, 23, 58, 0).unwrap());
let utc_dt = Utc.with_ymd_and_hms(2020, 3, 31, 23, 58, 0).unwrap();
assert_eq!(utc_dt - Months::new(1), Utc.with_ymd_and_hms(2020, 2, 29, 23, 58, 0).unwrap());
assert_eq!(utc_dt - Months::new(2), Utc.with_ymd_and_hms(2020, 1, 31, 23, 58, 0).unwrap());
}
#[test]
fn test_auto_conversion() {
let utc_dt = Utc.with_ymd_and_hms(2018, 9, 5, 23, 58, 0).unwrap();
let cdt_dt = FixedOffset::west_opt(5 * 60 * 60)
.unwrap()
.with_ymd_and_hms(2018, 9, 5, 18, 58, 0)
.unwrap();
let utc_dt2: DateTime<Utc> = cdt_dt.into();
assert_eq!(utc_dt, utc_dt2);
}

View File

@ -989,12 +989,12 @@ impl FromStr for Weekday {
/// Formats single formatting item
#[cfg(feature = "unstable-locales")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable-locales")))]
pub fn format_item_localized<'a>(
pub fn format_item_localized(
w: &mut fmt::Formatter,
date: Option<&NaiveDate>,
time: Option<&NaiveTime>,
off: Option<&(String, FixedOffset)>,
item: &Item<'a>,
item: &Item<'_>,
locale: Locale,
) -> fmt::Result {
let mut result = String::new();

View File

@ -620,7 +620,12 @@ impl Parsed {
/// plus a time zone offset.
/// Either way those fields have to be consistent to each other.
pub fn to_datetime(&self) -> ParseResult<DateTime<FixedOffset>> {
let offset = self.offset.ok_or(NOT_ENOUGH)?;
// If there is no explicit offset, consider a timestamp value as indication of a UTC value.
let offset = match (self.offset, self.timestamp) {
(Some(off), _) => off,
(None, Some(_)) => 0, // UNIX timestamp may assume 0 offset
(None, None) => return Err(NOT_ENOUGH),
};
let datetime = self.to_naive_datetime_with_offset(offset)?;
let offset = FixedOffset::east_opt(offset).ok_or(OUT_OF_RANGE)?;

View File

@ -414,98 +414,103 @@ enum CommentState {
}
#[cfg(test)]
#[test]
fn test_rfc2822_comments() {
let testdata = [
("", Err(TOO_SHORT)),
(" ", Err(TOO_SHORT)),
("x", Err(INVALID)),
("(", Err(TOO_SHORT)),
("()", Ok("")),
(" \r\n\t()", Ok("")),
("() ", Ok(" ")),
("()z", Ok("z")),
("(x)", Ok("")),
("(())", Ok("")),
("((()))", Ok("")),
("(x(x(x)x)x)", Ok("")),
("( x ( x ( x ) x ) x )", Ok("")),
(r"(\)", Err(TOO_SHORT)),
(r"(\()", Ok("")),
(r"(\))", Ok("")),
(r"(\\)", Ok("")),
("(()())", Ok("")),
("( x ( x ) x ( x ) x )", Ok("")),
];
mod tests {
use super::{comment_2822, consume_colon_maybe, s_next, space, trim1};
use crate::format::{INVALID, TOO_SHORT};
for (test_in, expected) in testdata.iter() {
let actual = comment_2822(test_in).map(|(s, _)| s);
assert_eq!(
*expected, actual,
"{:?} expected to produce {:?}, but produced {:?}.",
test_in, expected, actual
);
#[test]
fn test_rfc2822_comments() {
let testdata = [
("", Err(TOO_SHORT)),
(" ", Err(TOO_SHORT)),
("x", Err(INVALID)),
("(", Err(TOO_SHORT)),
("()", Ok("")),
(" \r\n\t()", Ok("")),
("() ", Ok(" ")),
("()z", Ok("z")),
("(x)", Ok("")),
("(())", Ok("")),
("((()))", Ok("")),
("(x(x(x)x)x)", Ok("")),
("( x ( x ( x ) x ) x )", Ok("")),
(r"(\)", Err(TOO_SHORT)),
(r"(\()", Ok("")),
(r"(\))", Ok("")),
(r"(\\)", Ok("")),
("(()())", Ok("")),
("( x ( x ) x ( x ) x )", Ok("")),
];
for (test_in, expected) in testdata.iter() {
let actual = comment_2822(test_in).map(|(s, _)| s);
assert_eq!(
*expected, actual,
"{:?} expected to produce {:?}, but produced {:?}.",
test_in, expected, actual
);
}
}
#[test]
fn test_space() {
assert_eq!(space(""), Err(TOO_SHORT));
assert_eq!(space(" "), Ok(""));
assert_eq!(space(" \t"), Ok(""));
assert_eq!(space(" \ta"), Ok("a"));
assert_eq!(space(" \ta "), Ok("a "));
assert_eq!(space("a"), Err(INVALID));
assert_eq!(space("a "), Err(INVALID));
}
#[test]
fn test_s_next() {
assert_eq!(s_next(""), "");
assert_eq!(s_next(" "), "");
assert_eq!(s_next("a"), "");
assert_eq!(s_next("ab"), "b");
assert_eq!(s_next("abc"), "bc");
assert_eq!(s_next("😾b"), "b");
assert_eq!(s_next("a😾"), "😾");
assert_eq!(s_next("😾bc"), "bc");
assert_eq!(s_next("a😾c"), "😾c");
}
#[test]
fn test_trim1() {
assert_eq!(trim1(""), "");
assert_eq!(trim1(" "), "");
assert_eq!(trim1("\t"), "");
assert_eq!(trim1("\t\t"), "\t");
assert_eq!(trim1(" "), " ");
assert_eq!(trim1("a"), "a");
assert_eq!(trim1("a "), "a ");
assert_eq!(trim1("ab"), "ab");
assert_eq!(trim1("😼"), "😼");
assert_eq!(trim1("😼b"), "😼b");
}
#[test]
fn test_consume_colon_maybe() {
assert_eq!(consume_colon_maybe(""), Ok(""));
assert_eq!(consume_colon_maybe(" "), Ok(" "));
assert_eq!(consume_colon_maybe("\n"), Ok("\n"));
assert_eq!(consume_colon_maybe(" "), Ok(" "));
assert_eq!(consume_colon_maybe(":"), Ok(""));
assert_eq!(consume_colon_maybe(" :"), Ok(" :"));
assert_eq!(consume_colon_maybe(": "), Ok(" "));
assert_eq!(consume_colon_maybe(" : "), Ok(" : "));
assert_eq!(consume_colon_maybe(": "), Ok(" "));
assert_eq!(consume_colon_maybe(" :"), Ok(" :"));
assert_eq!(consume_colon_maybe(":: "), Ok(": "));
assert_eq!(consume_colon_maybe("😸"), Ok("😸"));
assert_eq!(consume_colon_maybe("😸😸"), Ok("😸😸"));
assert_eq!(consume_colon_maybe("😸:"), Ok("😸:"));
assert_eq!(consume_colon_maybe("😸 "), Ok("😸 "));
assert_eq!(consume_colon_maybe(":😸"), Ok("😸"));
assert_eq!(consume_colon_maybe(":😸 "), Ok("😸 "));
assert_eq!(consume_colon_maybe(": 😸"), Ok(" 😸"));
assert_eq!(consume_colon_maybe(": 😸"), Ok(" 😸"));
assert_eq!(consume_colon_maybe(": :😸"), Ok(" :😸"));
}
}
#[test]
fn test_space() {
assert_eq!(space(""), Err(TOO_SHORT));
assert_eq!(space(" "), Ok(""));
assert_eq!(space(" \t"), Ok(""));
assert_eq!(space(" \ta"), Ok("a"));
assert_eq!(space(" \ta "), Ok("a "));
assert_eq!(space("a"), Err(INVALID));
assert_eq!(space("a "), Err(INVALID));
}
#[test]
fn test_s_next() {
assert_eq!(s_next(""), "");
assert_eq!(s_next(" "), "");
assert_eq!(s_next("a"), "");
assert_eq!(s_next("ab"), "b");
assert_eq!(s_next("abc"), "bc");
assert_eq!(s_next("😾b"), "b");
assert_eq!(s_next("a😾"), "😾");
assert_eq!(s_next("😾bc"), "bc");
assert_eq!(s_next("a😾c"), "😾c");
}
#[test]
fn test_trim1() {
assert_eq!(trim1(""), "");
assert_eq!(trim1(" "), "");
assert_eq!(trim1("\t"), "");
assert_eq!(trim1("\t\t"), "\t");
assert_eq!(trim1(" "), " ");
assert_eq!(trim1("a"), "a");
assert_eq!(trim1("a "), "a ");
assert_eq!(trim1("ab"), "ab");
assert_eq!(trim1("😼"), "😼");
assert_eq!(trim1("😼b"), "😼b");
}
#[test]
fn test_consume_colon_maybe() {
assert_eq!(consume_colon_maybe(""), Ok(""));
assert_eq!(consume_colon_maybe(" "), Ok(" "));
assert_eq!(consume_colon_maybe("\n"), Ok("\n"));
assert_eq!(consume_colon_maybe(" "), Ok(" "));
assert_eq!(consume_colon_maybe(":"), Ok(""));
assert_eq!(consume_colon_maybe(" :"), Ok(" :"));
assert_eq!(consume_colon_maybe(": "), Ok(" "));
assert_eq!(consume_colon_maybe(" : "), Ok(" : "));
assert_eq!(consume_colon_maybe(": "), Ok(" "));
assert_eq!(consume_colon_maybe(" :"), Ok(" :"));
assert_eq!(consume_colon_maybe(":: "), Ok(": "));
assert_eq!(consume_colon_maybe("😸"), Ok("😸"));
assert_eq!(consume_colon_maybe("😸😸"), Ok("😸😸"));
assert_eq!(consume_colon_maybe("😸:"), Ok("😸:"));
assert_eq!(consume_colon_maybe("😸 "), Ok("😸 "));
assert_eq!(consume_colon_maybe(":😸"), Ok("😸"));
assert_eq!(consume_colon_maybe(":😸 "), Ok("😸 "));
assert_eq!(consume_colon_maybe(": 😸"), Ok(" 😸"));
assert_eq!(consume_colon_maybe(": 😸"), Ok(" 😸"));
assert_eq!(consume_colon_maybe(": :😸"), Ok(" :😸"));
}

View File

@ -509,293 +509,301 @@ impl<'a> Iterator for StrftimeItems<'a> {
}
#[cfg(test)]
#[test]
fn test_strftime_items() {
fn parse_and_collect(s: &str) -> Vec<Item<'_>> {
// map any error into `[Item::Error]`. useful for easy testing.
eprintln!("test_strftime_items: parse_and_collect({:?})", s);
let items = StrftimeItems::new(s);
let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
items.collect::<Option<Vec<_>>>().unwrap_or_else(|| vec![Item::Error])
mod tests {
#[cfg(feature = "unstable-locales")]
use super::Locale;
use super::{Fixed, InternalFixed, InternalInternal, Item, Numeric, Pad, StrftimeItems};
use crate::{DateTime, FixedOffset, NaiveDate, TimeZone, Timelike, Utc};
#[test]
fn test_strftime_items() {
fn parse_and_collect(s: &str) -> Vec<Item<'_>> {
// map any error into `[Item::Error]`. useful for easy testing.
eprintln!("test_strftime_items: parse_and_collect({:?})", s);
let items = StrftimeItems::new(s);
let items = items.map(|spec| if spec == Item::Error { None } else { Some(spec) });
items.collect::<Option<Vec<_>>>().unwrap_or_else(|| vec![Item::Error])
}
assert_eq!(parse_and_collect(""), []);
assert_eq!(parse_and_collect(" "), [sp!(" ")]);
assert_eq!(parse_and_collect(" "), [sp!(" ")]);
// ne!
assert_ne!(parse_and_collect(" "), [sp!(" "), sp!(" ")]);
// eq!
assert_eq!(parse_and_collect(" "), [sp!(" ")]);
assert_eq!(parse_and_collect("a"), [lit!("a")]);
assert_eq!(parse_and_collect("ab"), [lit!("ab")]);
assert_eq!(parse_and_collect("😽"), [lit!("😽")]);
assert_eq!(parse_and_collect("a😽"), [lit!("a😽")]);
assert_eq!(parse_and_collect("😽a"), [lit!("😽a")]);
assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]);
assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]);
// ne!
assert_ne!(parse_and_collect("😽😽"), [lit!("😽")]);
assert_ne!(parse_and_collect("😽"), [lit!("😽😽")]);
assert_ne!(parse_and_collect("😽😽"), [lit!("😽😽"), lit!("😽")]);
// eq!
assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]);
assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]);
assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]);
assert_eq!(
parse_and_collect("a b\t\nc"),
[lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")]
);
assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]);
assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]);
assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]);
assert_eq!(
parse_and_collect("%Y-%m-%d"),
[num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)]
);
assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]);
assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]);
assert_eq!(parse_and_collect("😽😽😽"), [lit!("😽😽😽")]);
assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]);
assert_eq!(parse_and_collect("😽😽a 😽"), [lit!("😽😽a"), sp!(" "), lit!("😽")]);
assert_eq!(parse_and_collect("😽😽a b😽"), [lit!("😽😽a"), sp!(" "), lit!("b😽")]);
assert_eq!(parse_and_collect("😽😽a b😽c"), [lit!("😽😽a"), sp!(" "), lit!("b😽c")]);
assert_eq!(parse_and_collect("😽😽 "), [lit!("😽😽"), sp!(" ")]);
assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]);
assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]);
assert_eq!(parse_and_collect(" 😽 "), [sp!(" "), lit!("😽"), sp!(" ")]);
assert_eq!(parse_and_collect(" 😽 😽"), [sp!(" "), lit!("😽"), sp!(" "), lit!("😽")]);
assert_eq!(
parse_and_collect(" 😽 😽 "),
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")]
);
assert_eq!(
parse_and_collect(" 😽 😽 "),
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")]
);
assert_eq!(
parse_and_collect(" 😽 😽😽 "),
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")]
);
assert_eq!(parse_and_collect(" 😽😽"), [sp!(" "), lit!("😽😽")]);
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
assert_eq!(
parse_and_collect(" 😽 😽😽 "),
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")]
);
assert_eq!(
parse_and_collect(" 😽 😽はい😽 ハンバーガー"),
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽はい😽"), sp!(" "), lit!("ハンバーガー")]
);
assert_eq!(parse_and_collect("%%😽%%😽"), [lit!("%"), lit!("😽"), lit!("%"), lit!("😽")]);
assert_eq!(parse_and_collect("%Y--%m"), [num0!(Year), lit!("--"), num0!(Month)]);
assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
assert_eq!(parse_and_collect("100%%😽"), [lit!("100"), lit!("%"), lit!("😽")]);
assert_eq!(
parse_and_collect("100%%😽%%a"),
[lit!("100"), lit!("%"), lit!("😽"), lit!("%"), lit!("a")]
);
assert_eq!(parse_and_collect("😽100%%"), [lit!("😽100"), lit!("%")]);
assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]);
assert_eq!(parse_and_collect("%"), [Item::Error]);
assert_eq!(parse_and_collect("%%"), [lit!("%")]);
assert_eq!(parse_and_collect("%%%"), [Item::Error]);
assert_eq!(parse_and_collect("%a"), [fix!(ShortWeekdayName)]);
assert_eq!(parse_and_collect("%aa"), [fix!(ShortWeekdayName), lit!("a")]);
assert_eq!(parse_and_collect("%%a%"), [Item::Error]);
assert_eq!(parse_and_collect("%😽"), [Item::Error]);
assert_eq!(parse_and_collect("%😽😽"), [Item::Error]);
assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]);
assert_eq!(
parse_and_collect("%%%%ハンバーガー"),
[lit!("%"), lit!("%"), lit!("ハンバーガー")]
);
assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
assert_eq!(parse_and_collect("%.j"), [Item::Error]);
assert_eq!(parse_and_collect("%:j"), [Item::Error]);
assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]);
assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]);
assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]);
assert_eq!(parse_and_collect("%.e"), [Item::Error]);
assert_eq!(parse_and_collect("%:e"), [Item::Error]);
assert_eq!(parse_and_collect("%-e"), [num!(Day)]);
assert_eq!(parse_and_collect("%0e"), [num0!(Day)]);
assert_eq!(parse_and_collect("%_e"), [nums!(Day)]);
assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]);
assert_eq!(parse_and_collect("%:z"), [fix!(TimezoneOffsetColon)]);
assert_eq!(parse_and_collect("%Z"), [fix!(TimezoneName)]);
assert_eq!(parse_and_collect("%ZZZZ"), [fix!(TimezoneName), lit!("ZZZ")]);
assert_eq!(parse_and_collect("%Z😽"), [fix!(TimezoneName), lit!("😽")]);
assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]);
assert_eq!(parse_and_collect("%#m"), [Item::Error]);
}
assert_eq!(parse_and_collect(""), []);
assert_eq!(parse_and_collect(" "), [sp!(" ")]);
assert_eq!(parse_and_collect(" "), [sp!(" ")]);
// ne!
assert_ne!(parse_and_collect(" "), [sp!(" "), sp!(" ")]);
// eq!
assert_eq!(parse_and_collect(" "), [sp!(" ")]);
assert_eq!(parse_and_collect("a"), [lit!("a")]);
assert_eq!(parse_and_collect("ab"), [lit!("ab")]);
assert_eq!(parse_and_collect("😽"), [lit!("😽")]);
assert_eq!(parse_and_collect("a😽"), [lit!("a😽")]);
assert_eq!(parse_and_collect("😽a"), [lit!("😽a")]);
assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]);
assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]);
// ne!
assert_ne!(parse_and_collect("😽😽"), [lit!("😽")]);
assert_ne!(parse_and_collect("😽"), [lit!("😽😽")]);
assert_ne!(parse_and_collect("😽😽"), [lit!("😽😽"), lit!("😽")]);
// eq!
assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]);
assert_eq!(parse_and_collect(" \t\n\r "), [sp!(" \t\n\r ")]);
assert_eq!(parse_and_collect("hello?"), [lit!("hello?")]);
assert_eq!(
parse_and_collect("a b\t\nc"),
[lit!("a"), sp!(" "), lit!("b"), sp!("\t\n"), lit!("c")]
);
assert_eq!(parse_and_collect("100%%"), [lit!("100"), lit!("%")]);
assert_eq!(parse_and_collect("100%% ok"), [lit!("100"), lit!("%"), sp!(" "), lit!("ok")]);
assert_eq!(parse_and_collect("%%PDF-1.0"), [lit!("%"), lit!("PDF-1.0")]);
assert_eq!(
parse_and_collect("%Y-%m-%d"),
[num0!(Year), lit!("-"), num0!(Month), lit!("-"), num0!(Day)]
);
assert_eq!(parse_and_collect("😽 "), [lit!("😽"), sp!(" ")]);
assert_eq!(parse_and_collect("😽😽"), [lit!("😽😽")]);
assert_eq!(parse_and_collect("😽😽😽"), [lit!("😽😽😽")]);
assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]);
assert_eq!(parse_and_collect("😽😽a 😽"), [lit!("😽😽a"), sp!(" "), lit!("😽")]);
assert_eq!(parse_and_collect("😽😽a b😽"), [lit!("😽😽a"), sp!(" "), lit!("b😽")]);
assert_eq!(parse_and_collect("😽😽a b😽c"), [lit!("😽😽a"), sp!(" "), lit!("b😽c")]);
assert_eq!(parse_and_collect("😽😽 "), [lit!("😽😽"), sp!(" ")]);
assert_eq!(parse_and_collect("😽😽 😽"), [lit!("😽😽"), sp!(" "), lit!("😽")]);
assert_eq!(parse_and_collect(" 😽"), [sp!(" "), lit!("😽")]);
assert_eq!(parse_and_collect(" 😽 "), [sp!(" "), lit!("😽"), sp!(" ")]);
assert_eq!(parse_and_collect(" 😽 😽"), [sp!(" "), lit!("😽"), sp!(" "), lit!("😽")]);
assert_eq!(
parse_and_collect(" 😽 😽 "),
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")]
);
assert_eq!(
parse_and_collect(" 😽 😽 "),
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽"), sp!(" ")]
);
assert_eq!(
parse_and_collect(" 😽 😽😽 "),
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")]
);
assert_eq!(parse_and_collect(" 😽😽"), [sp!(" "), lit!("😽😽")]);
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
assert_eq!(parse_and_collect(" 😽😽 "), [sp!(" "), lit!("😽😽"), sp!(" ")]);
assert_eq!(
parse_and_collect(" 😽 😽😽 "),
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽😽"), sp!(" ")]
);
assert_eq!(
parse_and_collect(" 😽 😽はい😽 ハンバーガー"),
[sp!(" "), lit!("😽"), sp!(" "), lit!("😽はい😽"), sp!(" "), lit!("ハンバーガー")]
);
assert_eq!(parse_and_collect("%%😽%%😽"), [lit!("%"), lit!("😽"), lit!("%"), lit!("😽")]);
assert_eq!(parse_and_collect("%Y--%m"), [num0!(Year), lit!("--"), num0!(Month)]);
assert_eq!(parse_and_collect("[%F]"), parse_and_collect("[%Y-%m-%d]"));
assert_eq!(parse_and_collect("100%%😽"), [lit!("100"), lit!("%"), lit!("😽")]);
assert_eq!(
parse_and_collect("100%%😽%%a"),
[lit!("100"), lit!("%"), lit!("😽"), lit!("%"), lit!("a")]
);
assert_eq!(parse_and_collect("😽100%%"), [lit!("😽100"), lit!("%")]);
assert_eq!(parse_and_collect("%m %d"), [num0!(Month), sp!(" "), num0!(Day)]);
assert_eq!(parse_and_collect("%"), [Item::Error]);
assert_eq!(parse_and_collect("%%"), [lit!("%")]);
assert_eq!(parse_and_collect("%%%"), [Item::Error]);
assert_eq!(parse_and_collect("%a"), [fix!(ShortWeekdayName)]);
assert_eq!(parse_and_collect("%aa"), [fix!(ShortWeekdayName), lit!("a")]);
assert_eq!(parse_and_collect("%%a%"), [Item::Error]);
assert_eq!(parse_and_collect("%😽"), [Item::Error]);
assert_eq!(parse_and_collect("%😽😽"), [Item::Error]);
assert_eq!(parse_and_collect("%%%%"), [lit!("%"), lit!("%")]);
assert_eq!(parse_and_collect("%%%%ハンバーガー"), [lit!("%"), lit!("%"), lit!("ハンバーガー")]);
assert_eq!(parse_and_collect("foo%?"), [Item::Error]);
assert_eq!(parse_and_collect("bar%42"), [Item::Error]);
assert_eq!(parse_and_collect("quux% +"), [Item::Error]);
assert_eq!(parse_and_collect("%.Z"), [Item::Error]);
assert_eq!(parse_and_collect("%:Z"), [Item::Error]);
assert_eq!(parse_and_collect("%-Z"), [Item::Error]);
assert_eq!(parse_and_collect("%0Z"), [Item::Error]);
assert_eq!(parse_and_collect("%_Z"), [Item::Error]);
assert_eq!(parse_and_collect("%.j"), [Item::Error]);
assert_eq!(parse_and_collect("%:j"), [Item::Error]);
assert_eq!(parse_and_collect("%-j"), [num!(Ordinal)]);
assert_eq!(parse_and_collect("%0j"), [num0!(Ordinal)]);
assert_eq!(parse_and_collect("%_j"), [nums!(Ordinal)]);
assert_eq!(parse_and_collect("%.e"), [Item::Error]);
assert_eq!(parse_and_collect("%:e"), [Item::Error]);
assert_eq!(parse_and_collect("%-e"), [num!(Day)]);
assert_eq!(parse_and_collect("%0e"), [num0!(Day)]);
assert_eq!(parse_and_collect("%_e"), [nums!(Day)]);
assert_eq!(parse_and_collect("%z"), [fix!(TimezoneOffset)]);
assert_eq!(parse_and_collect("%:z"), [fix!(TimezoneOffsetColon)]);
assert_eq!(parse_and_collect("%Z"), [fix!(TimezoneName)]);
assert_eq!(parse_and_collect("%ZZZZ"), [fix!(TimezoneName), lit!("ZZZ")]);
assert_eq!(parse_and_collect("%Z😽"), [fix!(TimezoneName), lit!("😽")]);
assert_eq!(parse_and_collect("%#z"), [internal_fix!(TimezoneOffsetPermissive)]);
assert_eq!(parse_and_collect("%#m"), [Item::Error]);
}
#[test]
fn test_strftime_docs() {
let dt = FixedOffset::east_opt(34200)
.unwrap()
.from_local_datetime(
&NaiveDate::from_ymd_opt(2001, 7, 8)
.unwrap()
.and_hms_nano_opt(0, 34, 59, 1_026_490_708)
.unwrap(),
)
.unwrap();
#[cfg(test)]
#[test]
fn test_strftime_docs() {
use crate::NaiveDate;
use crate::{DateTime, FixedOffset, TimeZone, Timelike, Utc};
// date specifiers
assert_eq!(dt.format("%Y").to_string(), "2001");
assert_eq!(dt.format("%C").to_string(), "20");
assert_eq!(dt.format("%y").to_string(), "01");
assert_eq!(dt.format("%m").to_string(), "07");
assert_eq!(dt.format("%b").to_string(), "Jul");
assert_eq!(dt.format("%B").to_string(), "July");
assert_eq!(dt.format("%h").to_string(), "Jul");
assert_eq!(dt.format("%d").to_string(), "08");
assert_eq!(dt.format("%e").to_string(), " 8");
assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
assert_eq!(dt.format("%a").to_string(), "Sun");
assert_eq!(dt.format("%A").to_string(), "Sunday");
assert_eq!(dt.format("%w").to_string(), "0");
assert_eq!(dt.format("%u").to_string(), "7");
assert_eq!(dt.format("%U").to_string(), "27");
assert_eq!(dt.format("%W").to_string(), "27");
assert_eq!(dt.format("%G").to_string(), "2001");
assert_eq!(dt.format("%g").to_string(), "01");
assert_eq!(dt.format("%V").to_string(), "27");
assert_eq!(dt.format("%j").to_string(), "189");
assert_eq!(dt.format("%D").to_string(), "07/08/01");
assert_eq!(dt.format("%x").to_string(), "07/08/01");
assert_eq!(dt.format("%F").to_string(), "2001-07-08");
assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
let dt = FixedOffset::east_opt(34200)
.unwrap()
.from_local_datetime(
&NaiveDate::from_ymd_opt(2001, 7, 8)
// time specifiers
assert_eq!(dt.format("%H").to_string(), "00");
assert_eq!(dt.format("%k").to_string(), " 0");
assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
assert_eq!(dt.format("%I").to_string(), "12");
assert_eq!(dt.format("%l").to_string(), "12");
assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
assert_eq!(dt.format("%P").to_string(), "am");
assert_eq!(dt.format("%p").to_string(), "AM");
assert_eq!(dt.format("%M").to_string(), "34");
assert_eq!(dt.format("%S").to_string(), "60");
assert_eq!(dt.format("%f").to_string(), "026490708");
assert_eq!(dt.format("%.f").to_string(), ".026490708");
assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490");
assert_eq!(dt.format("%.3f").to_string(), ".026");
assert_eq!(dt.format("%.6f").to_string(), ".026490");
assert_eq!(dt.format("%.9f").to_string(), ".026490708");
assert_eq!(dt.format("%3f").to_string(), "026");
assert_eq!(dt.format("%6f").to_string(), "026490");
assert_eq!(dt.format("%9f").to_string(), "026490708");
assert_eq!(dt.format("%R").to_string(), "00:34");
assert_eq!(dt.format("%T").to_string(), "00:34:60");
assert_eq!(dt.format("%X").to_string(), "00:34:60");
assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
// time zone specifiers
//assert_eq!(dt.format("%Z").to_string(), "ACST");
assert_eq!(dt.format("%z").to_string(), "+0930");
assert_eq!(dt.format("%:z").to_string(), "+09:30");
assert_eq!(dt.format("%::z").to_string(), "+09:30:00");
assert_eq!(dt.format("%:::z").to_string(), "+09");
// date & time specifiers
assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001");
assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30");
assert_eq!(
dt.with_timezone(&Utc).format("%+").to_string(),
"2001-07-07T15:04:60.026490708+00:00"
);
assert_eq!(
dt.with_timezone(&Utc),
DateTime::<FixedOffset>::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+")
.unwrap()
.and_hms_nano_opt(0, 34, 59, 1_026_490_708)
.unwrap(),
)
.unwrap();
);
assert_eq!(
dt.with_timezone(&Utc),
DateTime::<FixedOffset>::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+")
.unwrap()
);
assert_eq!(
dt.with_timezone(&Utc),
DateTime::<FixedOffset>::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+")
.unwrap()
);
// date specifiers
assert_eq!(dt.format("%Y").to_string(), "2001");
assert_eq!(dt.format("%C").to_string(), "20");
assert_eq!(dt.format("%y").to_string(), "01");
assert_eq!(dt.format("%m").to_string(), "07");
assert_eq!(dt.format("%b").to_string(), "Jul");
assert_eq!(dt.format("%B").to_string(), "July");
assert_eq!(dt.format("%h").to_string(), "Jul");
assert_eq!(dt.format("%d").to_string(), "08");
assert_eq!(dt.format("%e").to_string(), " 8");
assert_eq!(dt.format("%e").to_string(), dt.format("%_d").to_string());
assert_eq!(dt.format("%a").to_string(), "Sun");
assert_eq!(dt.format("%A").to_string(), "Sunday");
assert_eq!(dt.format("%w").to_string(), "0");
assert_eq!(dt.format("%u").to_string(), "7");
assert_eq!(dt.format("%U").to_string(), "27");
assert_eq!(dt.format("%W").to_string(), "27");
assert_eq!(dt.format("%G").to_string(), "2001");
assert_eq!(dt.format("%g").to_string(), "01");
assert_eq!(dt.format("%V").to_string(), "27");
assert_eq!(dt.format("%j").to_string(), "189");
assert_eq!(dt.format("%D").to_string(), "07/08/01");
assert_eq!(dt.format("%x").to_string(), "07/08/01");
assert_eq!(dt.format("%F").to_string(), "2001-07-08");
assert_eq!(dt.format("%v").to_string(), " 8-Jul-2001");
assert_eq!(
dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
"2001-07-08T00:34:60.026490+09:30"
);
assert_eq!(dt.format("%s").to_string(), "994518299");
// time specifiers
assert_eq!(dt.format("%H").to_string(), "00");
assert_eq!(dt.format("%k").to_string(), " 0");
assert_eq!(dt.format("%k").to_string(), dt.format("%_H").to_string());
assert_eq!(dt.format("%I").to_string(), "12");
assert_eq!(dt.format("%l").to_string(), "12");
assert_eq!(dt.format("%l").to_string(), dt.format("%_I").to_string());
assert_eq!(dt.format("%P").to_string(), "am");
assert_eq!(dt.format("%p").to_string(), "AM");
assert_eq!(dt.format("%M").to_string(), "34");
assert_eq!(dt.format("%S").to_string(), "60");
assert_eq!(dt.format("%f").to_string(), "026490708");
assert_eq!(dt.format("%.f").to_string(), ".026490708");
assert_eq!(dt.with_nanosecond(1_026_490_000).unwrap().format("%.f").to_string(), ".026490");
assert_eq!(dt.format("%.3f").to_string(), ".026");
assert_eq!(dt.format("%.6f").to_string(), ".026490");
assert_eq!(dt.format("%.9f").to_string(), ".026490708");
assert_eq!(dt.format("%3f").to_string(), "026");
assert_eq!(dt.format("%6f").to_string(), "026490");
assert_eq!(dt.format("%9f").to_string(), "026490708");
assert_eq!(dt.format("%R").to_string(), "00:34");
assert_eq!(dt.format("%T").to_string(), "00:34:60");
assert_eq!(dt.format("%X").to_string(), "00:34:60");
assert_eq!(dt.format("%r").to_string(), "12:34:60 AM");
// special specifiers
assert_eq!(dt.format("%t").to_string(), "\t");
assert_eq!(dt.format("%n").to_string(), "\n");
assert_eq!(dt.format("%%").to_string(), "%");
// time zone specifiers
//assert_eq!(dt.format("%Z").to_string(), "ACST");
assert_eq!(dt.format("%z").to_string(), "+0930");
assert_eq!(dt.format("%:z").to_string(), "+09:30");
assert_eq!(dt.format("%::z").to_string(), "+09:30:00");
assert_eq!(dt.format("%:::z").to_string(), "+09");
// complex format specifiers
assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t");
assert_eq!(
dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(),
" 20010807%%\t00:am:3460+09\t"
);
}
// date & time specifiers
assert_eq!(dt.format("%c").to_string(), "Sun Jul 8 00:34:60 2001");
assert_eq!(dt.format("%+").to_string(), "2001-07-08T00:34:60.026490708+09:30");
#[cfg(feature = "unstable-locales")]
#[test]
fn test_strftime_docs_localized() {
let dt = FixedOffset::east_opt(34200)
.unwrap()
.with_ymd_and_hms(2001, 7, 8, 0, 34, 59)
.unwrap()
.with_nanosecond(1_026_490_708)
.unwrap();
assert_eq!(
dt.with_timezone(&Utc).format("%+").to_string(),
"2001-07-07T15:04:60.026490708+00:00"
);
assert_eq!(
dt.with_timezone(&Utc),
DateTime::<FixedOffset>::parse_from_str("2001-07-07T15:04:60.026490708Z", "%+").unwrap()
);
assert_eq!(
dt.with_timezone(&Utc),
DateTime::<FixedOffset>::parse_from_str("2001-07-07T15:04:60.026490708UTC", "%+").unwrap()
);
assert_eq!(
dt.with_timezone(&Utc),
DateTime::<FixedOffset>::parse_from_str("2001-07-07t15:04:60.026490708utc", "%+").unwrap()
);
// date specifiers
assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");
assert_eq!(
dt.with_nanosecond(1_026_490_000).unwrap().format("%+").to_string(),
"2001-07-08T00:34:60.026490+09:30"
);
assert_eq!(dt.format("%s").to_string(), "994518299");
// time specifiers
assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 ");
// special specifiers
assert_eq!(dt.format("%t").to_string(), "\t");
assert_eq!(dt.format("%n").to_string(), "\n");
assert_eq!(dt.format("%%").to_string(), "%");
// date & time specifiers
assert_eq!(
dt.format_localized("%c", Locale::fr_BE).to_string(),
"dim 08 jui 2001 00:34:60 +09:30"
);
// complex format specifiers
assert_eq!(dt.format(" %Y%d%m%%%%%t%H%M%S\t").to_string(), " 20010807%%\t003460\t");
assert_eq!(
dt.format(" %Y%d%m%%%%%t%H:%P:%M%S%:::z\t").to_string(),
" 20010807%%\t00:am:3460+09\t"
);
}
#[cfg(feature = "unstable-locales")]
#[test]
fn test_strftime_docs_localized() {
use crate::{FixedOffset, NaiveDate};
let dt = NaiveDate::from_ymd_opt(2001, 7, 8)
.and_then(|d| d.and_hms_nano_opt(0, 34, 59, 1_026_490_708))
.unwrap()
.and_local_timezone(FixedOffset::east_opt(34200).unwrap())
.unwrap();
// date specifiers
assert_eq!(dt.format_localized("%b", Locale::fr_BE).to_string(), "jui");
assert_eq!(dt.format_localized("%B", Locale::fr_BE).to_string(), "juillet");
assert_eq!(dt.format_localized("%h", Locale::fr_BE).to_string(), "jui");
assert_eq!(dt.format_localized("%a", Locale::fr_BE).to_string(), "dim");
assert_eq!(dt.format_localized("%A", Locale::fr_BE).to_string(), "dimanche");
assert_eq!(dt.format_localized("%D", Locale::fr_BE).to_string(), "07/08/01");
assert_eq!(dt.format_localized("%x", Locale::fr_BE).to_string(), "08/07/01");
assert_eq!(dt.format_localized("%F", Locale::fr_BE).to_string(), "2001-07-08");
assert_eq!(dt.format_localized("%v", Locale::fr_BE).to_string(), " 8-jui-2001");
// time specifiers
assert_eq!(dt.format_localized("%P", Locale::fr_BE).to_string(), "");
assert_eq!(dt.format_localized("%p", Locale::fr_BE).to_string(), "");
assert_eq!(dt.format_localized("%R", Locale::fr_BE).to_string(), "00:34");
assert_eq!(dt.format_localized("%T", Locale::fr_BE).to_string(), "00:34:60");
assert_eq!(dt.format_localized("%X", Locale::fr_BE).to_string(), "00:34:60");
assert_eq!(dt.format_localized("%r", Locale::fr_BE).to_string(), "12:34:60 ");
// date & time specifiers
assert_eq!(
dt.format_localized("%c", Locale::fr_BE).to_string(),
"dim 08 jui 2001 00:34:60 +09:30"
);
let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap();
// date specifiers
assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul");
assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli");
assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul");
assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So");
assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag");
assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01");
assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001");
assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08");
assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001");
let nd = NaiveDate::from_ymd_opt(2001, 7, 8).unwrap();
// date specifiers
assert_eq!(nd.format_localized("%b", Locale::de_DE).to_string(), "Jul");
assert_eq!(nd.format_localized("%B", Locale::de_DE).to_string(), "Juli");
assert_eq!(nd.format_localized("%h", Locale::de_DE).to_string(), "Jul");
assert_eq!(nd.format_localized("%a", Locale::de_DE).to_string(), "So");
assert_eq!(nd.format_localized("%A", Locale::de_DE).to_string(), "Sonntag");
assert_eq!(nd.format_localized("%D", Locale::de_DE).to_string(), "07/08/01");
assert_eq!(nd.format_localized("%x", Locale::de_DE).to_string(), "08.07.2001");
assert_eq!(nd.format_localized("%F", Locale::de_DE).to_string(), "2001-07-08");
assert_eq!(nd.format_localized("%v", Locale::de_DE).to_string(), " 8-Jul-2001");
}
}

View File

@ -374,6 +374,7 @@
#![deny(missing_debug_implementations)]
#![warn(unreachable_pub)]
#![deny(dead_code)]
#![deny(clippy::tests_outside_test_module)]
#![cfg_attr(not(any(feature = "std", test)), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]

View File

@ -28,7 +28,7 @@ use crate::OutOfRange;
/// Allows mapping from and to month, from 1-January to 12-December.
/// Can be Serialized/Deserialized with serde
// Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior.
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd)]
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum Month {
@ -180,7 +180,7 @@ impl TryFrom<u8> for Month {
}
/// A duration in calendar months
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Months(pub(crate) u32);
@ -245,8 +245,47 @@ mod month_serde {
deserializer.deserialize_str(MonthVisitor)
}
}
}
#[cfg(test)]
mod tests {
use super::Month;
use crate::{Datelike, OutOfRange, TimeZone, Utc};
#[test]
fn test_month_enum_try_from() {
assert_eq!(Month::try_from(1), Ok(Month::January));
assert_eq!(Month::try_from(2), Ok(Month::February));
assert_eq!(Month::try_from(12), Ok(Month::December));
assert_eq!(Month::try_from(13), Err(OutOfRange::new()));
let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
assert_eq!(Month::try_from(date.month() as u8), Ok(Month::October));
let month = Month::January;
let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
}
#[test]
fn test_month_enum_succ_pred() {
assert_eq!(Month::January.succ(), Month::February);
assert_eq!(Month::December.succ(), Month::January);
assert_eq!(Month::January.pred(), Month::December);
assert_eq!(Month::February.pred(), Month::January);
}
#[test]
fn test_month_partial_ord() {
assert!(Month::January <= Month::January);
assert!(Month::January < Month::February);
assert!(Month::January < Month::December);
assert!(Month::July >= Month::May);
assert!(Month::September > Month::March);
}
#[test]
#[cfg(feature = "serde")]
fn test_serde_serialize() {
use serde_json::to_string;
use Month::*;
@ -273,6 +312,7 @@ mod month_serde {
}
#[test]
#[cfg(feature = "serde")]
fn test_serde_deserialize() {
use serde_json::from_str;
use Month::*;
@ -307,41 +347,3 @@ mod month_serde {
}
}
}
#[cfg(test)]
mod tests {
use super::Month;
use crate::{Datelike, OutOfRange, TimeZone, Utc};
#[test]
fn test_month_enum_try_from() {
assert_eq!(Month::try_from(1), Ok(Month::January));
assert_eq!(Month::try_from(2), Ok(Month::February));
assert_eq!(Month::try_from(12), Ok(Month::December));
assert_eq!(Month::try_from(13), Err(OutOfRange::new()));
let date = Utc.with_ymd_and_hms(2019, 10, 28, 9, 10, 11).unwrap();
assert_eq!(Month::try_from(date.month() as u8).ok(), Some(Month::October));
let month = Month::January;
let dt = Utc.with_ymd_and_hms(2019, month.number_from_month(), 28, 9, 10, 11).unwrap();
assert_eq!((dt.year(), dt.month(), dt.day()), (2019, 1, 28));
}
#[test]
fn test_month_enum_succ_pred() {
assert_eq!(Month::January.succ(), Month::February);
assert_eq!(Month::December.succ(), Month::January);
assert_eq!(Month::January.pred(), Month::December);
assert_eq!(Month::February.pred(), Month::January);
}
#[test]
fn test_month_partial_ord() {
assert!(Month::January <= Month::January);
assert!(Month::January < Month::February);
assert!(Month::January < Month::December);
assert!(Month::July >= Month::May);
assert!(Month::September > Month::March);
}
}

View File

@ -5,6 +5,7 @@
#[cfg(any(feature = "alloc", feature = "std", test))]
use core::borrow::Borrow;
use core::iter::FusedIterator;
use core::ops::{Add, AddAssign, RangeInclusive, Sub, SubAssign};
use core::{fmt, str};
@ -31,29 +32,6 @@ use super::isoweek;
const MAX_YEAR: i32 = internals::MAX_YEAR;
const MIN_YEAR: i32 = internals::MIN_YEAR;
// 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
// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 days
#[cfg(test)] // only used for testing
const MAX_DAYS_FROM_YEAR_0: i32 =
MAX_YEAR * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400 + 365;
// MIN_YEAR-01-01 minus 0000-01-01
// = (MIN_YEAR+400n+1)-01-01 minus (400n+1)-01-01
// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - ((400n+1)-01-01 minus 0001-01-01)
// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - 146097n days
//
// n is set to 1000 for convenience.
#[cfg(test)] // only used for testing
const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 + (MIN_YEAR + 400_000) / 4
- (MIN_YEAR + 400_000) / 100
+ (MIN_YEAR + 400_000) / 400
- 146_097_000;
#[cfg(test)] // only used for testing, but duplicated in naive::datetime
const MAX_BITS: usize = 44;
/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first
/// day of the week.
#[derive(Debug)]
@ -65,6 +43,11 @@ pub struct NaiveWeek {
impl NaiveWeek {
/// Returns a date representing the first day of the week.
///
/// # Panics
///
/// Panics if the first day of the week happens to fall just out of range of `NaiveDate`
/// (more than ca. 262,000 years away from common era).
///
/// # Examples
///
/// ```
@ -88,6 +71,11 @@ impl NaiveWeek {
/// Returns a date representing the last day of the week.
///
/// # Panics
///
/// Panics if the last day of the week happens to fall just out of range of `NaiveDate`
/// (more than ca. 262,000 years away from common era).
///
/// # Examples
///
/// ```
@ -113,6 +101,11 @@ impl NaiveWeek {
/// [first_day](./struct.NaiveWeek.html#method.first_day) and
/// [last_day](./struct.NaiveWeek.html#method.last_day) functions.
///
/// # Panics
///
/// Panics if the either the first or last day of the week happens to fall just out of range of
/// `NaiveDate` (more than ca. 262,000 years away from common era).
///
/// # Examples
///
/// ```
@ -136,7 +129,7 @@ impl NaiveWeek {
/// that adding `TimeDelta::days(1)` doesn't increment the day value as expected due to it being a
/// fixed number of seconds. This difference applies only when dealing with `DateTime<TimeZone>` data types
/// and in other cases `TimeDelta::days(n)` and `Days::new(n)` are equivalent.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Days(pub(crate) u64);
impl Days {
@ -218,34 +211,6 @@ impl arbitrary::Arbitrary<'_> for NaiveDate {
}
}
// as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`,
// we use a separate run-time test.
#[test]
fn test_date_bounds() {
let calculated_min = NaiveDate::from_ymd_opt(MIN_YEAR, 1, 1).unwrap();
let calculated_max = NaiveDate::from_ymd_opt(MAX_YEAR, 12, 31).unwrap();
assert!(
NaiveDate::MIN == calculated_min,
"`NaiveDate::MIN` should have a year flag {:?}",
calculated_min.of().flags()
);
assert!(
NaiveDate::MAX == calculated_max,
"`NaiveDate::MAX` should have a year flag {:?}",
calculated_max.of().flags()
);
// let's also check that the entire range do not exceed 2^44 seconds
// (sometimes used for bounding `TimeDelta` against overflow)
let maxsecs = NaiveDate::MAX.signed_duration_since(NaiveDate::MIN).num_seconds();
let maxsecs = maxsecs + 86401; // also take care of DateTime
assert!(
maxsecs < (1 << MAX_BITS),
"The entire `NaiveDate` range somehow exceeds 2^{} seconds",
MAX_BITS
);
}
impl NaiveDate {
pub(crate) fn weeks_from(&self, day: Weekday) -> i32 {
(self.ordinal() as i32 - self.weekday().num_days_from(day) as i32 + 6) / 7
@ -284,7 +249,10 @@ impl NaiveDate {
/// Makes a new `NaiveDate` from the [calendar date](#calendar-date)
/// (year, month and day).
///
/// Panics on the out-of-range date, invalid month and/or day.
/// # Panics
///
/// Panics if the specified calendar day does not exist, on invalid values for `month` or `day`,
/// or if `year` is out of range for `NaiveDate`.
#[deprecated(since = "0.4.23", note = "use `from_ymd_opt()` instead")]
#[must_use]
pub fn from_ymd(year: i32, month: u32, day: u32) -> NaiveDate {
@ -294,7 +262,12 @@ impl NaiveDate {
/// Makes a new `NaiveDate` from the [calendar date](#calendar-date)
/// (year, month and day).
///
/// Returns `None` on the out-of-range date, invalid month and/or day.
/// # Errors
///
/// Returns `None` if:
/// - The specified calendar day does not exist (for example 2023-04-31).
/// - The value for `month` or `day` is invalid.
/// - `year` is out of range for `NaiveDate`.
///
/// # Example
///
@ -319,7 +292,10 @@ impl NaiveDate {
/// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date)
/// (year and day of the year).
///
/// Panics on the out-of-range date and/or invalid day of year.
/// # Panics
///
/// Panics if the specified ordinal day does not exist, on invalid values for `ordinal`, or if
/// `year` is out of range for `NaiveDate`.
#[deprecated(since = "0.4.23", note = "use `from_yo_opt()` instead")]
#[must_use]
pub fn from_yo(year: i32, ordinal: u32) -> NaiveDate {
@ -329,7 +305,12 @@ impl NaiveDate {
/// Makes a new `NaiveDate` from the [ordinal date](#ordinal-date)
/// (year and day of the year).
///
/// Returns `None` on the out-of-range date and/or invalid day of year.
/// # Errors
///
/// Returns `None` if:
/// - The specified ordinal day does not exist (for example 2023-366).
/// - The value for `ordinal` is invalid (for example: `0`, `400`).
/// - `year` is out of range for `NaiveDate`.
///
/// # Example
///
@ -356,7 +337,10 @@ impl NaiveDate {
/// (year, week number and day of the week).
/// The resulting `NaiveDate` may have a different year from the input year.
///
/// Panics on the out-of-range date and/or invalid week number.
/// # Panics
///
/// Panics if the specified week does not exist in that year, on invalid values for `week`, or
/// if the resulting date is out of range for `NaiveDate`.
#[deprecated(since = "0.4.23", note = "use `from_isoywd_opt()` instead")]
#[must_use]
pub fn from_isoywd(year: i32, week: u32, weekday: Weekday) -> NaiveDate {
@ -367,7 +351,12 @@ impl NaiveDate {
/// (year, week number and day of the week).
/// The resulting `NaiveDate` may have a different year from the input year.
///
/// Returns `None` on the out-of-range date and/or invalid week number.
/// # Errors
///
/// Returns `None` if:
/// - The specified week does not exist in that year (for example 2023 week 53).
/// - The value for `week` is invalid (for example: `0`, `60`).
/// - If the resulting date is out of range for `NaiveDate`.
///
/// # Example
///
@ -443,6 +432,8 @@ impl NaiveDate {
/// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with
/// January 1, 1 being day 1.
///
/// # Panics
///
/// Panics if the date is out of range.
#[deprecated(since = "0.4.23", note = "use `from_num_days_from_ce_opt()` instead")]
#[inline]
@ -454,6 +445,8 @@ impl NaiveDate {
/// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with
/// January 1, 1 being day 1.
///
/// # Errors
///
/// Returns `None` if the date is out of range.
///
/// # Example
@ -481,15 +474,15 @@ impl NaiveDate {
}
/// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week
/// since the beginning of the given month. For instance, if you want the 2nd Friday of March
/// since the beginning of the given month. For instance, if you want the 2nd Friday of March
/// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`.
///
/// `n` is 1-indexed.
///
/// # Panics
///
/// The resulting `NaiveDate` is guaranteed to be in `month`. If `n` is larger than the number
/// of `weekday` in `month` (eg. the 6th Friday of March 2017) then this function will panic.
///
/// `n` is 1-indexed. Passing `n=0` will cause a panic.
/// Panics if the specified day does not exist in that month, on invalid values for `month` or
/// `n`, or if `year` is out of range for `NaiveDate`.
#[deprecated(since = "0.4.23", note = "use `from_weekday_of_month_opt()` instead")]
#[must_use]
pub fn from_weekday_of_month(year: i32, month: u32, weekday: Weekday, n: u8) -> NaiveDate {
@ -497,17 +490,25 @@ impl NaiveDate {
}
/// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week
/// since the beginning of the given month. For instance, if you want the 2nd Friday of March
/// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`. `n` is 1-indexed.
/// since the beginning of the given month. For instance, if you want the 2nd Friday of March
/// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`.
///
/// `n` is 1-indexed.
///
/// # Errors
///
/// Returns `None` if:
/// - The specified day does not exist in that month (for example the 5th Monday of Apr. 2023).
/// - The value for `month` or `n` is invalid.
/// - `year` is out of range for `NaiveDate`.
///
/// # Example
///
/// ```
/// use chrono::{NaiveDate, Weekday};
/// assert_eq!(NaiveDate::from_weekday_of_month_opt(2017, 3, Weekday::Fri, 2),
/// NaiveDate::from_ymd_opt(2017, 3, 10))
/// ```
///
/// Returns `None` if `n` out-of-range; ie. if `n` is larger than the number of `weekday` in
/// `month` (eg. the 6th Friday of March 2017), or if `n == 0`.
#[must_use]
pub fn from_weekday_of_month_opt(
year: i32,
@ -596,10 +597,14 @@ impl NaiveDate {
/// Add a duration in [`Months`] to the date
///
/// If the day would be out of range for the resulting month, use the last day for that month.
/// Uses the last day of the month if the day does not exist in the resulting month.
///
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
///
/// # Example
///
/// ```
/// # use chrono::{NaiveDate, Months};
/// assert_eq!(
@ -625,10 +630,14 @@ impl NaiveDate {
/// Subtract a duration in [`Months`] from the date
///
/// If the day would be out of range for the resulting month, use the last day for that month.
/// Uses the last day of the month if the day does not exist in the resulting month.
///
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
///
/// # Example
///
/// ```
/// # use chrono::{NaiveDate, Months};
/// assert_eq!(
@ -699,8 +708,12 @@ impl NaiveDate {
/// Add a duration in [`Days`] to the date
///
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
///
/// # Example
///
/// ```
/// # use chrono::{NaiveDate, Days};
/// assert_eq!(
@ -727,8 +740,12 @@ impl NaiveDate {
/// Subtract a duration in [`Days`] from the date
///
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
///
/// # Example
///
/// ```
/// # use chrono::{NaiveDate, Days};
/// assert_eq!(
@ -782,6 +799,8 @@ impl NaiveDate {
/// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here;
/// use `NaiveDate::and_hms_*` methods with a subsecond parameter instead.
///
/// # Panics
///
/// Panics on invalid hour, minute and/or second.
#[deprecated(since = "0.4.23", note = "use `and_hms_opt()` instead")]
#[inline]
@ -795,6 +814,8 @@ impl NaiveDate {
/// No [leap second](./struct.NaiveTime.html#leap-second-handling) is allowed here;
/// use `NaiveDate::and_hms_*_opt` methods with a subsecond parameter instead.
///
/// # Errors
///
/// Returns `None` on invalid hour, minute and/or second.
///
/// # Example
@ -819,6 +840,8 @@ impl NaiveDate {
/// The millisecond part can exceed 1,000
/// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
///
/// # Panics
///
/// Panics on invalid hour, minute, second and/or millisecond.
#[deprecated(since = "0.4.23", note = "use `and_hms_milli_opt()` instead")]
#[inline]
@ -832,6 +855,8 @@ impl NaiveDate {
/// The millisecond part can exceed 1,000
/// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
///
/// # Errors
///
/// Returns `None` on invalid hour, minute, second and/or millisecond.
///
/// # Example
@ -864,6 +889,8 @@ impl NaiveDate {
/// The microsecond part can exceed 1,000,000
/// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
///
/// # Panics
///
/// Panics on invalid hour, minute, second and/or microsecond.
///
/// # Example
@ -891,6 +918,8 @@ impl NaiveDate {
/// The microsecond part can exceed 1,000,000
/// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
///
/// # Errors
///
/// Returns `None` on invalid hour, minute, second and/or microsecond.
///
/// # Example
@ -923,6 +952,8 @@ impl NaiveDate {
/// The nanosecond part can exceed 1,000,000,000
/// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
///
/// # Panics
///
/// Panics on invalid hour, minute, second and/or nanosecond.
#[deprecated(since = "0.4.23", note = "use `and_hms_nano_opt()` instead")]
#[inline]
@ -936,6 +967,8 @@ impl NaiveDate {
/// The nanosecond part can exceed 1,000,000,000
/// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
///
/// # Errors
///
/// Returns `None` on invalid hour, minute, second and/or nanosecond.
///
/// # Example
@ -994,6 +1027,8 @@ impl NaiveDate {
/// Makes a new `NaiveDate` for the next calendar date.
///
/// # Panics
///
/// Panics when `self` is the last representable date.
#[deprecated(since = "0.4.23", note = "use `succ_opt()` instead")]
#[inline]
@ -1004,6 +1039,8 @@ impl NaiveDate {
/// Makes a new `NaiveDate` for the next calendar date.
///
/// # Errors
///
/// Returns `None` when `self` is the last representable date.
///
/// # Example
@ -1026,6 +1063,8 @@ impl NaiveDate {
/// Makes a new `NaiveDate` for the previous calendar date.
///
/// # Panics
///
/// Panics when `self` is the first representable date.
#[deprecated(since = "0.4.23", note = "use `pred_opt()` instead")]
#[inline]
@ -1036,6 +1075,8 @@ impl NaiveDate {
/// Makes a new `NaiveDate` for the previous calendar date.
///
/// # Errors
///
/// Returns `None` when `self` is the first representable date.
///
/// # Example
@ -1056,9 +1097,11 @@ impl NaiveDate {
}
}
/// Adds the `days` part of given `Duration` to the current date.
/// Adds the number of whole days in the given `Duration` to the current date.
///
/// Returns `None` when it will result in overflow.
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
///
/// # Example
///
@ -1088,9 +1131,11 @@ impl NaiveDate {
NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags)
}
/// Subtracts the `days` part of given `TimeDelta` from the current date.
/// Subtracts the number of whole days in the given `TimeDelta` from the current date.
///
/// Returns `None` when it will result in overflow.
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
///
/// # Example
///
@ -1156,6 +1201,10 @@ impl NaiveDate {
}
/// Returns the number of whole years from the given `base` until `self`.
///
/// # Errors
///
/// Returns `None` if `base < self`.
#[must_use]
pub fn years_since(&self, base: Self) -> Option<u32> {
let mut years = self.year() - base.year();
@ -1539,9 +1588,12 @@ impl Datelike for NaiveDate {
isoweek::iso_week_from_yof(self.year(), self.of())
}
/// Makes a new `NaiveDate` with the year number changed.
/// Makes a new `NaiveDate` with the year number changed, while keeping the same month and day.
///
/// Returns `None` when the resulting `NaiveDate` would be invalid.
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or when the `NaiveDate` would be
/// out of range.
///
/// # Example
///
@ -1575,7 +1627,9 @@ impl Datelike for NaiveDate {
/// Makes a new `NaiveDate` with the month number (starting from 1) changed.
///
/// Returns `None` when the resulting `NaiveDate` would be invalid.
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `month` is invalid.
///
/// # Example
///
@ -1594,7 +1648,10 @@ impl Datelike for NaiveDate {
/// Makes a new `NaiveDate` with the month number (starting from 0) changed.
///
/// Returns `None` when the resulting `NaiveDate` would be invalid.
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `month0` is
/// invalid.
///
/// # Example
///
@ -1614,7 +1671,9 @@ impl Datelike for NaiveDate {
/// Makes a new `NaiveDate` with the day of month (starting from 1) changed.
///
/// Returns `None` when the resulting `NaiveDate` would be invalid.
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `day` is invalid.
///
/// # Example
///
@ -1633,7 +1692,9 @@ impl Datelike for NaiveDate {
/// Makes a new `NaiveDate` with the day of month (starting from 0) changed.
///
/// Returns `None` when the resulting `NaiveDate` would be invalid.
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `day0` is invalid.
///
/// # Example
///
@ -1653,7 +1714,10 @@ impl Datelike for NaiveDate {
/// Makes a new `NaiveDate` with the day of year (starting from 1) changed.
///
/// Returns `None` when the resulting `NaiveDate` would be invalid.
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `ordinal` is
/// invalid.
///
/// # Example
///
@ -1677,7 +1741,10 @@ impl Datelike for NaiveDate {
/// Makes a new `NaiveDate` with the day of year (starting from 0) changed.
///
/// Returns `None` when the resulting `NaiveDate` would be invalid.
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `ordinal0` is
/// invalid.
///
/// # Example
///
@ -1898,13 +1965,10 @@ impl Iterator for NaiveDateDaysIterator {
type Item = NaiveDate;
fn next(&mut self) -> Option<Self::Item> {
if self.value == NaiveDate::MAX {
return None;
}
// current < NaiveDate::MAX from here on:
// We return the current value, and have no way to return `NaiveDate::MAX`.
let current = self.value;
// This can't panic because current is < NaiveDate::MAX:
self.value = current.succ_opt().unwrap();
self.value = current.succ_opt()?;
Some(current)
}
@ -1918,15 +1982,16 @@ impl ExactSizeIterator for NaiveDateDaysIterator {}
impl DoubleEndedIterator for NaiveDateDaysIterator {
fn next_back(&mut self) -> Option<Self::Item> {
if self.value == NaiveDate::MIN {
return None;
}
// We return the current value, and have no way to return `NaiveDate::MIN`.
let current = self.value;
self.value = current.pred_opt().unwrap();
self.value = current.pred_opt()?;
Some(current)
}
}
impl FusedIterator for NaiveDateDaysIterator {}
/// Iterator over `NaiveDate` with a step size of one week.
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)]
pub struct NaiveDateWeeksIterator {
value: NaiveDate,
@ -1936,11 +2001,8 @@ impl Iterator for NaiveDateWeeksIterator {
type Item = NaiveDate;
fn next(&mut self) -> Option<Self::Item> {
if NaiveDate::MAX - self.value < TimeDelta::weeks(1) {
return None;
}
let current = self.value;
self.value = current + TimeDelta::weeks(1);
self.value = current.checked_add_signed(TimeDelta::weeks(1))?;
Some(current)
}
@ -1954,18 +2016,13 @@ impl ExactSizeIterator for NaiveDateWeeksIterator {}
impl DoubleEndedIterator for NaiveDateWeeksIterator {
fn next_back(&mut self) -> Option<Self::Item> {
if self.value - NaiveDate::MIN < TimeDelta::weeks(1) {
return None;
}
let current = self.value;
self.value = current - TimeDelta::weeks(1);
self.value = current.checked_sub_signed(TimeDelta::weeks(1))?;
Some(current)
}
}
// TODO: NaiveDateDaysIterator and NaiveDateWeeksIterator should implement FusedIterator,
// TrustedLen, and Step once they becomes stable.
// See: https://github.com/chronotope/chrono/issues/208
impl FusedIterator for NaiveDateWeeksIterator {}
/// The `Debug` output of the naive date `d` is the same as
/// [`d.format("%Y-%m-%d")`](../format/strftime/index.html).
@ -2219,38 +2276,70 @@ mod serde {
}
}
#[test]
fn test_serde_serialize() {
super::test_encodable_json(serde_json::to_string);
}
#[cfg(test)]
mod tests {
use crate::naive::date::{test_decodable_json, test_encodable_json};
use crate::NaiveDate;
#[test]
fn test_serde_deserialize() {
super::test_decodable_json(|input| serde_json::from_str(input));
}
#[test]
fn test_serde_serialize() {
test_encodable_json(serde_json::to_string);
}
#[test]
fn test_serde_bincode() {
// Bincode is relevant to test separately from JSON because
// it is not self-describing.
use bincode::{deserialize, serialize};
#[test]
fn test_serde_deserialize() {
test_decodable_json(|input| serde_json::from_str(input));
}
let d = NaiveDate::from_ymd_opt(2014, 7, 24).unwrap();
let encoded = serialize(&d).unwrap();
let decoded: NaiveDate = deserialize(&encoded).unwrap();
assert_eq!(d, decoded);
#[test]
fn test_serde_bincode() {
// Bincode is relevant to test separately from JSON because
// it is not self-describing.
use bincode::{deserialize, serialize};
let d = NaiveDate::from_ymd_opt(2014, 7, 24).unwrap();
let encoded = serialize(&d).unwrap();
let decoded: NaiveDate = deserialize(&encoded).unwrap();
assert_eq!(d, decoded);
}
}
}
#[cfg(test)]
mod tests {
use super::{
Days, Months, NaiveDate, MAX_DAYS_FROM_YEAR_0, MAX_YEAR, MIN_DAYS_FROM_YEAR_0, MIN_YEAR,
};
use super::{Days, Months, NaiveDate, MAX_YEAR, MIN_YEAR};
use crate::time_delta::TimeDelta;
use crate::{Datelike, Weekday};
use std::{i32, u32};
// as it is hard to verify year flags in `NaiveDate::MIN` and `NaiveDate::MAX`,
// we use a separate run-time test.
#[test]
fn test_date_bounds() {
let calculated_min = NaiveDate::from_ymd_opt(MIN_YEAR, 1, 1).unwrap();
let calculated_max = NaiveDate::from_ymd_opt(MAX_YEAR, 12, 31).unwrap();
assert!(
NaiveDate::MIN == calculated_min,
"`NaiveDate::MIN` should have a year flag {:?}",
calculated_min.of().flags()
);
assert!(
NaiveDate::MAX == calculated_max,
"`NaiveDate::MAX` should have a year flag {:?}",
calculated_max.of().flags()
);
// let's also check that the entire range do not exceed 2^44 seconds
// (sometimes used for bounding `Duration` against overflow)
let maxsecs = NaiveDate::MAX.signed_duration_since(NaiveDate::MIN).num_seconds();
let maxsecs = maxsecs + 86401; // also take care of DateTime
assert!(
maxsecs < (1 << MAX_BITS),
"The entire `NaiveDate` range somehow exceeds 2^{} seconds",
MAX_BITS
);
}
#[test]
fn diff_months() {
// identity
@ -3102,4 +3191,25 @@ mod tests {
assert!(dt.with_day0(4294967295).is_none());
assert!(dt.with_ordinal0(4294967295).is_none());
}
// 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
// = MAX_YEAR * 365 + (# of leap years from 0001 to MAX_YEAR) + 365 days
const MAX_DAYS_FROM_YEAR_0: i32 =
MAX_YEAR * 365 + MAX_YEAR / 4 - MAX_YEAR / 100 + MAX_YEAR / 400 + 365;
// MIN_YEAR-01-01 minus 0000-01-01
// = (MIN_YEAR+400n+1)-01-01 minus (400n+1)-01-01
// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - ((400n+1)-01-01 minus 0001-01-01)
// = ((MIN_YEAR+400n+1)-01-01 minus 0001-01-01) - 146097n days
//
// n is set to 1000 for convenience.
const MIN_DAYS_FROM_YEAR_0: i32 = (MIN_YEAR + 400_000) * 365 + (MIN_YEAR + 400_000) / 4
- (MIN_YEAR + 400_000) / 100
+ (MIN_YEAR + 400_000) / 400
- 146_097_000;
// only used for testing, but duplicated in naive::datetime
const MAX_BITS: usize = 44;
}

View File

@ -109,7 +109,11 @@ impl NaiveDateTime {
/// [leap second](./struct.NaiveTime.html#leap-second-handling). (The true "UNIX
/// timestamp" cannot represent a leap second unambiguously.)
///
/// Panics on the out-of-range number of seconds and/or invalid nanosecond.
/// # Panics
///
/// Panics if the number of seconds would be out of range for a `NaiveDateTime` (more than
/// ca. 262,000 years away from common era), and panics on an invalid nanosecond (2 seconds or
/// more).
#[deprecated(since = "0.4.23", note = "use `from_timestamp_opt()` instead")]
#[inline]
#[must_use]
@ -122,7 +126,10 @@ impl NaiveDateTime {
///
/// The UNIX epoch starts on midnight, January 1, 1970, UTC.
///
/// Returns `None` on an out-of-range number of milliseconds.
/// # Errors
///
/// Returns `None` if the number of milliseconds would be out of range for a `NaiveDateTime`
/// (more than ca. 262,000 years away from common era)
///
/// # Example
///
@ -151,7 +158,10 @@ impl NaiveDateTime {
///
/// The UNIX epoch starts on midnight, January 1, 1970, UTC.
///
/// Returns `None` on an out-of-range number of microseconds.
/// # Errors
///
/// Returns `None` if the number of microseconds would be out of range for a `NaiveDateTime`
/// (more than ca. 262,000 years away from common era)
///
/// # Example
///
@ -185,8 +195,11 @@ impl NaiveDateTime {
/// in order to represent the [leap second](./struct.NaiveTime.html#leap-second-handling).
/// (The true "UNIX timestamp" cannot represent a leap second unambiguously.)
///
/// Returns `None` on the out-of-range number of seconds (more than 262 000 years away
/// from common era) and/or invalid nanosecond (2 seconds or more).
/// # Errors
///
/// Returns `None` if the number of seconds would be out of range for a `NaiveDateTime` (more
/// than ca. 262,000 years away from common era), and panics on an invalid nanosecond
/// (2 seconds or more).
///
/// # Example
///
@ -386,11 +399,6 @@ impl NaiveDateTime {
/// Note that this does *not* account for the timezone!
/// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch.
///
/// Note also that this does reduce the number of years that can be
/// represented from ~584 Billion to ~584 Million. (If this is a problem,
/// please file an issue to let me know what domain needs millisecond
/// precision over billions of years, I'm curious.)
///
/// # Example
///
/// ```
@ -417,11 +425,6 @@ impl NaiveDateTime {
/// Note that this does *not* account for the timezone!
/// The true "UNIX timestamp" would count seconds since the midnight *UTC* on the epoch.
///
/// Note also that this does reduce the number of years that can be
/// represented from ~584 Billion to ~584 Thousand. (If this is a problem,
/// please file an issue to let me know what domain needs microsecond
/// precision over millennia, I'm curious.)
///
/// # Example
///
/// ```
@ -447,13 +450,11 @@ impl NaiveDateTime {
///
/// # Panics
///
/// Note also that this does reduce the number of years that can be
/// represented from ~584 Billion to ~584 years. The dates that can be
/// represented as nanoseconds are between 1677-09-21T00:12:44.0 and
/// 2262-04-11T23:47:16.854775804.
/// An `i64` with nanosecond precision can span a range of ~584 years. This function panics on
/// an out of range `NaiveDateTime`.
///
/// (If this is a problem, please file an issue to let me know what domain
/// needs nanosecond precision over millennia, I'm curious.)
/// The dates that can be represented as nanoseconds are between 1677-09-21T00:12:44.0 and
/// 2262-04-11T23:47:16.854775804.
///
/// # Example
///
@ -476,8 +477,10 @@ impl NaiveDateTime {
#[inline]
#[must_use]
pub fn timestamp_nanos(&self) -> i64 {
let as_ns = self.timestamp() * 1_000_000_000;
as_ns + i64::from(self.timestamp_subsec_nanos())
self.timestamp()
.checked_mul(1_000_000_000)
.and_then(|ns| ns.checked_add(i64::from(self.timestamp_subsec_nanos())))
.expect("value can not be represented in a timestamp with nanosecond precision.")
}
/// Returns the number of milliseconds since the last whole non-leap second.
@ -553,7 +556,9 @@ impl NaiveDateTime {
/// except when the `NaiveDateTime` itself represents a leap second
/// in which case the assumption becomes that **there is exactly a single leap second ever**.
///
/// Returns `None` when it will result in overflow.
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
///
/// # Example
///
@ -626,9 +631,11 @@ impl NaiveDateTime {
/// Adds given `Months` to the current date and time.
///
/// Returns `None` when it will result in overflow.
/// Uses the last day of the month if the day does not exist in the resulting month.
///
/// Overflow returns `None`.
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
///
/// # Example
///
@ -659,7 +666,9 @@ impl NaiveDateTime {
/// except when the `NaiveDateTime` itself represents a leap second
/// in which case the assumption becomes that **there is exactly a single leap second ever**.
///
/// Returns `None` when it will result in overflow.
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
///
/// # Example
///
@ -728,9 +737,11 @@ impl NaiveDateTime {
/// Subtracts given `Months` from the current date and time.
///
/// Returns `None` when it will result in overflow.
/// Uses the last day of the month if the day does not exist in the resulting month.
///
/// Overflow returns `None`.
/// # Errors
///
/// Returns `None` if the resulting date would be out of range.
///
/// # Example
///
@ -1091,12 +1102,16 @@ impl Datelike for NaiveDateTime {
self.date.iso_week()
}
/// Makes a new `NaiveDateTime` with the year number changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid.
/// Makes a new `NaiveDateTime` with the year number changed, while keeping the same month and
/// day.
///
/// See also the [`NaiveDate::with_year`] method.
///
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or when the `NaiveDateTime` would be
/// out of range.
///
/// # Example
///
/// ```
@ -1113,10 +1128,12 @@ impl Datelike for NaiveDateTime {
/// Makes a new `NaiveDateTime` with the month number (starting from 1) changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid.
///
/// See also the [`NaiveDate::with_month`] method.
///
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `month` is invalid.
///
/// # Example
///
/// ```
@ -1134,10 +1151,13 @@ impl Datelike for NaiveDateTime {
/// Makes a new `NaiveDateTime` with the month number (starting from 0) changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid.
///
/// See also the [`NaiveDate::with_month0`] method.
///
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `month0` is
/// invalid.
///
/// # Example
///
/// ```
@ -1155,10 +1175,12 @@ impl Datelike for NaiveDateTime {
/// Makes a new `NaiveDateTime` with the day of month (starting from 1) changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid.
///
/// See also the [`NaiveDate::with_day`] method.
///
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `day` is invalid.
///
/// # Example
///
/// ```
@ -1175,10 +1197,12 @@ impl Datelike for NaiveDateTime {
/// Makes a new `NaiveDateTime` with the day of month (starting from 0) changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid.
///
/// See also the [`NaiveDate::with_day0`] method.
///
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `day0` is invalid.
///
/// # Example
///
/// ```
@ -1195,10 +1219,13 @@ impl Datelike for NaiveDateTime {
/// Makes a new `NaiveDateTime` with the day of year (starting from 1) changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid.
///
/// See also the [`NaiveDate::with_ordinal`] method.
///
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `ordinal` is
/// invalid.
///
/// # Example
///
/// ```
@ -1222,10 +1249,13 @@ impl Datelike for NaiveDateTime {
/// Makes a new `NaiveDateTime` with the day of year (starting from 0) changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid.
///
/// See also the [`NaiveDate::with_ordinal0`] method.
///
/// # Errors
///
/// Returns `None` if the resulting date does not exist, or if the value for `ordinal0` is
/// invalid.
///
/// # Example
///
/// ```
@ -1321,10 +1351,12 @@ impl Timelike for NaiveDateTime {
/// Makes a new `NaiveDateTime` with the hour number changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid.
///
/// See also the [`NaiveTime::with_hour`] method.
///
/// # Errors
///
/// Returns `None` if the value for `hour` is invalid.
///
/// # Example
///
/// ```
@ -1342,10 +1374,11 @@ impl Timelike for NaiveDateTime {
/// Makes a new `NaiveDateTime` with the minute number changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid.
/// See also the [`NaiveTime::with_minute`] method.
///
/// See also the
/// [`NaiveTime::with_minute`] method.
/// # Errors
///
/// Returns `None` if the value for `minute` is invalid.
///
/// # Example
///
@ -1364,12 +1397,15 @@ impl Timelike for NaiveDateTime {
/// Makes a new `NaiveDateTime` with the second number changed.
///
/// Returns `None` when the resulting `NaiveDateTime` would be invalid. As
/// with the [`NaiveDateTime::second`] method, the input range is
/// restricted to 0 through 59.
/// As with the [`second`](#method.second) method,
/// the input range is restricted to 0 through 59.
///
/// See also the [`NaiveTime::with_second`] method.
///
/// # Errors
///
/// Returns `None` if the value for `second` is invalid.
///
/// # Example
///
/// ```
@ -1393,6 +1429,10 @@ impl Timelike for NaiveDateTime {
///
/// See also the [`NaiveTime::with_nanosecond`] method.
///
/// # Errors
///
/// Returns `None` if `nanosecond >= 2,000,000,000`.
///
/// # Example
///
/// ```
@ -1417,7 +1457,9 @@ impl Timelike for NaiveDateTime {
/// second ever**, except when the `NaiveDateTime` itself represents a leap second in which case
/// the assumption becomes that **there is exactly a single leap second ever**.
///
/// Panics on underflow or overflow. Use [`NaiveDateTime::checked_add_signed`]
/// # Panics
///
/// Panics if the resulting date would be out of range. Use [`NaiveDateTime::checked_add_signed`]
/// to detect that.
///
/// # Example

View File

@ -110,7 +110,6 @@ pub mod ts_nanoseconds {
/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -138,7 +137,6 @@ pub mod ts_nanoseconds {
/// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733).unwrap() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
where
D: de::Deserializer<'de>,
@ -233,7 +231,6 @@ pub mod ts_nanoseconds_option {
/// assert_eq!(as_string, r#"{"time":1526522699918355733}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(opt: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -264,7 +261,6 @@ pub mod ts_nanoseconds_option {
/// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355733) });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<Option<NaiveDateTime>, D::Error>
where
D: de::Deserializer<'de>,
@ -362,7 +358,6 @@ pub mod ts_microseconds {
/// assert_eq!(as_string, r#"{"time":1526522699918355}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -390,7 +385,6 @@ pub mod ts_microseconds {
/// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000).unwrap() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
where
D: de::Deserializer<'de>,
@ -488,7 +482,6 @@ pub mod ts_microseconds_option {
/// assert_eq!(as_string, r#"{"time":1526522699918355}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(opt: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -519,7 +512,6 @@ pub mod ts_microseconds_option {
/// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918355000) });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<Option<NaiveDateTime>, D::Error>
where
D: de::Deserializer<'de>,
@ -617,7 +609,6 @@ pub mod ts_milliseconds {
/// assert_eq!(as_string, r#"{"time":1526522699918}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -645,7 +636,6 @@ pub mod ts_milliseconds {
/// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000).unwrap() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
where
D: de::Deserializer<'de>,
@ -740,7 +730,6 @@ pub mod ts_milliseconds_option {
/// assert_eq!(as_string, r#"{"time":1526522699918}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(opt: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -771,7 +760,6 @@ pub mod ts_milliseconds_option {
/// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1526522699, 918000000) });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<Option<NaiveDateTime>, D::Error>
where
D: de::Deserializer<'de>,
@ -869,7 +857,6 @@ pub mod ts_seconds {
/// assert_eq!(as_string, r#"{"time":1431684000}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(dt: &NaiveDateTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -897,7 +884,6 @@ pub mod ts_seconds {
/// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1431684000, 0).unwrap() });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
where
D: de::Deserializer<'de>,
@ -989,7 +975,6 @@ pub mod ts_seconds_option {
/// assert_eq!(as_string, r#"{"time":1526522699}"#);
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn serialize<S>(opt: &Option<NaiveDateTime>, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
@ -1020,7 +1005,6 @@ pub mod ts_seconds_option {
/// assert_eq!(my_s, S { time: NaiveDateTime::from_timestamp_opt(1431684000, 0) });
/// # Ok::<(), serde_json::Error>(())
/// ```
#[must_use]
pub fn deserialize<'de, D>(d: D) -> Result<Option<NaiveDateTime>, D::Error>
where
D: de::Deserializer<'de>,
@ -1063,51 +1047,6 @@ pub mod ts_seconds_option {
}
}
#[test]
fn test_serde_serialize() {
super::test_encodable_json(serde_json::to_string);
}
#[test]
fn test_serde_deserialize() {
super::test_decodable_json(|input| serde_json::from_str(input));
}
// Bincode is relevant to test separately from JSON because
// it is not self-describing.
#[test]
fn test_serde_bincode() {
use crate::NaiveDate;
use bincode::{deserialize, serialize};
let dt = NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap();
let encoded = serialize(&dt).unwrap();
let decoded: NaiveDateTime = deserialize(&encoded).unwrap();
assert_eq!(dt, decoded);
}
#[test]
fn test_serde_bincode_optional() {
use crate::prelude::*;
use crate::serde::ts_nanoseconds_option;
use bincode::{deserialize, serialize};
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Test {
one: Option<i64>,
#[serde(with = "ts_nanoseconds_option")]
two: Option<DateTime<Utc>>,
}
let expected =
Test { one: Some(1), two: Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap()) };
let bytes: Vec<u8> = serialize(&expected).unwrap();
let actual = deserialize::<Test>(&(bytes)).unwrap();
assert_eq!(expected, actual);
}
// lik? function to convert a LocalResult into a serde-ish Result
pub(crate) fn serde_from<T, E, V>(me: LocalResult<T>, ts: &V) -> Result<T, E>
where
@ -1155,3 +1094,51 @@ impl<V: fmt::Display, D: fmt::Display> fmt::Display for SerdeError<V, D> {
}
}
}
#[cfg(test)]
mod tests {
use crate::naive::datetime::{test_decodable_json, test_encodable_json};
use crate::serde::ts_nanoseconds_option;
use crate::{DateTime, NaiveDate, NaiveDateTime, TimeZone, Utc};
use bincode::{deserialize, serialize};
use serde_derive::{Deserialize, Serialize};
#[test]
fn test_serde_serialize() {
test_encodable_json(serde_json::to_string);
}
#[test]
fn test_serde_deserialize() {
test_decodable_json(|input| serde_json::from_str(input));
}
// Bincode is relevant to test separately from JSON because
// it is not self-describing.
#[test]
fn test_serde_bincode() {
let dt =
NaiveDate::from_ymd_opt(2016, 7, 8).unwrap().and_hms_milli_opt(9, 10, 48, 90).unwrap();
let encoded = serialize(&dt).unwrap();
let decoded: NaiveDateTime = deserialize(&encoded).unwrap();
assert_eq!(dt, decoded);
}
#[test]
fn test_serde_bincode_optional() {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Test {
one: Option<i64>,
#[serde(with = "ts_nanoseconds_option")]
two: Option<DateTime<Utc>>,
}
let expected =
Test { one: Some(1), two: Some(Utc.with_ymd_and_hms(1970, 1, 1, 0, 1, 1).unwrap()) };
let bytes: Vec<u8> = serialize(&expected).unwrap();
let actual = deserialize::<Test>(&(bytes)).unwrap();
assert_eq!(expected, actual);
}
}

View File

@ -369,6 +369,24 @@ fn test_nanosecond_range() {
);
}
#[test]
#[should_panic]
fn test_nanosecond_just_beyond_range() {
let maximum = "2262-04-11T23:47:16.854775804";
let parsed: NaiveDateTime = maximum.parse().unwrap();
let beyond_max = parsed + TimeDelta::milliseconds(300);
let _ = beyond_max.timestamp_nanos();
}
#[test]
#[should_panic]
fn test_nanosecond_far_beyond_range() {
let maximum = "2262-04-11T23:47:16.854775804";
let parsed: NaiveDateTime = maximum.parse().unwrap();
let beyond_max = parsed + TimeDelta::days(365);
let _ = beyond_max.timestamp_nanos();
}
#[test]
fn test_and_local_timezone() {
let ndt = NaiveDate::from_ymd_opt(2022, 6, 15).unwrap().and_hms_opt(18, 59, 36).unwrap();

View File

@ -10,8 +10,9 @@ mod internals;
mod isoweek;
mod time;
pub use self::date::{Days, NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator, NaiveWeek};
#[allow(deprecated)]
pub use self::date::{Days, NaiveDate, NaiveWeek, MAX_DATE, MIN_DATE};
pub use self::date::{MAX_DATE, MIN_DATE};
#[allow(deprecated)]
pub use self::datetime::{NaiveDateTime, MAX_DATETIME, MIN_DATETIME};
pub use self::isoweek::IsoWeek;

View File

@ -208,6 +208,8 @@ impl NaiveTime {
/// No [leap second](#leap-second-handling) is allowed here;
/// use `NaiveTime::from_hms_*` methods with a subsecond parameter instead.
///
/// # Panics
///
/// Panics on invalid hour, minute and/or second.
#[deprecated(since = "0.4.23", note = "use `from_hms_opt()` instead")]
#[inline]
@ -221,6 +223,8 @@ impl NaiveTime {
/// No [leap second](#leap-second-handling) is allowed here;
/// use `NaiveTime::from_hms_*_opt` methods with a subsecond parameter instead.
///
/// # Errors
///
/// Returns `None` on invalid hour, minute and/or second.
///
/// # Example
@ -247,6 +251,8 @@ impl NaiveTime {
/// The millisecond part can exceed 1,000
/// in order to represent the [leap second](#leap-second-handling).
///
/// # Panics
///
/// Panics on invalid hour, minute, second and/or millisecond.
#[deprecated(since = "0.4.23", note = "use `from_hms_milli_opt()` instead")]
#[inline]
@ -260,6 +266,8 @@ impl NaiveTime {
/// The millisecond part can exceed 1,000
/// in order to represent the [leap second](#leap-second-handling).
///
/// # Errors
///
/// Returns `None` on invalid hour, minute, second and/or millisecond.
///
/// # Example
@ -290,6 +298,8 @@ impl NaiveTime {
/// The microsecond part can exceed 1,000,000
/// in order to represent the [leap second](#leap-second-handling).
///
/// # Panics
///
/// Panics on invalid hour, minute, second and/or microsecond.
#[deprecated(since = "0.4.23", note = "use `from_hms_micro_opt()` instead")]
#[inline]
@ -303,6 +313,8 @@ impl NaiveTime {
/// The microsecond part can exceed 1,000,000
/// in order to represent the [leap second](#leap-second-handling).
///
/// # Errors
///
/// Returns `None` on invalid hour, minute, second and/or microsecond.
///
/// # Example
@ -331,6 +343,8 @@ impl NaiveTime {
/// The nanosecond part can exceed 1,000,000,000
/// in order to represent the [leap second](#leap-second-handling).
///
/// # Panics
///
/// Panics on invalid hour, minute, second and/or nanosecond.
#[deprecated(since = "0.4.23", note = "use `from_hms_nano_opt()` instead")]
#[inline]
@ -344,6 +358,8 @@ impl NaiveTime {
/// The nanosecond part can exceed 1,000,000,000
/// in order to represent the [leap second](#leap-second-handling).
///
/// # Errors
///
/// Returns `None` on invalid hour, minute, second and/or nanosecond.
///
/// # Example
@ -376,6 +392,8 @@ impl NaiveTime {
/// The nanosecond part can exceed 1,000,000,000
/// in order to represent the [leap second](#leap-second-handling).
///
/// # Panics
///
/// Panics on invalid number of seconds and/or nanosecond.
#[deprecated(since = "0.4.23", note = "use `from_num_seconds_from_midnight_opt()` instead")]
#[inline]
@ -389,6 +407,8 @@ impl NaiveTime {
/// The nanosecond part can exceed 1,000,000,000
/// in order to represent the [leap second](#leap-second-handling).
///
/// # Errors
///
/// Returns `None` on invalid number of seconds and/or nanosecond.
///
/// # Example
@ -502,10 +522,8 @@ impl NaiveTime {
parsed.to_naive_time().map(|t| (t, remainder))
}
/// Adds given `TimeDelta` to the current time,
/// and also returns the number of *seconds*
/// Adds given `TimeDelta` to the current time, and also returns the number of *seconds*
/// in the integral number of days ignored from the addition.
/// (We cannot return `TimeDelta` because it is subject to overflow or underflow.)
///
/// # Example
///
@ -585,10 +603,8 @@ impl NaiveTime {
(NaiveTime { secs: secs as u32, frac: frac as u32 }, morerhssecs)
}
/// Subtracts given `TimeDelta` from the current time,
/// and also returns the number of *seconds*
/// Subtracts given `TimeDelta` from the current time, and also returns the number of *seconds*
/// in the integral number of days ignored from the subtraction.
/// (We cannot return `TimeDelta` because it is subject to overflow or underflow.)
///
/// # Example
///
@ -882,7 +898,9 @@ impl Timelike for NaiveTime {
/// Makes a new `NaiveTime` with the hour number changed.
///
/// Returns `None` when the resulting `NaiveTime` would be invalid.
/// # Errors
///
/// Returns `None` if the value for `hour` is invalid.
///
/// # Example
///
@ -904,7 +922,9 @@ impl Timelike for NaiveTime {
/// Makes a new `NaiveTime` with the minute number changed.
///
/// Returns `None` when the resulting `NaiveTime` would be invalid.
/// # Errors
///
/// Returns `None` if the value for `minute` is invalid.
///
/// # Example
///
@ -926,10 +946,13 @@ impl Timelike for NaiveTime {
/// Makes a new `NaiveTime` with the second number changed.
///
/// Returns `None` when the resulting `NaiveTime` would be invalid.
/// As with the [`second`](#method.second) method,
/// the input range is restricted to 0 through 59.
///
/// # Errors
///
/// Returns `None` if the value for `second` is invalid.
///
/// # Example
///
/// ```
@ -950,10 +973,13 @@ impl Timelike for NaiveTime {
/// Makes a new `NaiveTime` with nanoseconds since the whole non-leap second changed.
///
/// Returns `None` when the resulting `NaiveTime` would be invalid.
/// As with the [`nanosecond`](#method.nanosecond) method,
/// the input range can exceed 1,000,000,000 for leap seconds.
///
/// # Errors
///
/// Returns `None` if `nanosecond >= 2,000,000,000`.
///
/// # Example
///
/// ```

View File

@ -42,24 +42,30 @@ impl<'de> de::Deserialize<'de> for NaiveTime {
}
}
#[test]
fn test_serde_serialize() {
super::test_encodable_json(serde_json::to_string);
}
#[cfg(test)]
mod tests {
use crate::naive::time::{test_decodable_json, test_encodable_json};
use crate::NaiveTime;
#[test]
fn test_serde_deserialize() {
super::test_decodable_json(|input| serde_json::from_str(input));
}
#[test]
fn test_serde_serialize() {
test_encodable_json(serde_json::to_string);
}
#[test]
fn test_serde_bincode() {
// Bincode is relevant to test separately from JSON because
// it is not self-describing.
use bincode::{deserialize, serialize};
#[test]
fn test_serde_deserialize() {
test_decodable_json(|input| serde_json::from_str(input));
}
let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap();
let encoded = serialize(&t).unwrap();
let decoded: NaiveTime = deserialize(&encoded).unwrap();
assert_eq!(t, decoded);
#[test]
fn test_serde_bincode() {
// Bincode is relevant to test separately from JSON because
// it is not self-describing.
use bincode::{deserialize, serialize};
let t = NaiveTime::from_hms_nano_opt(3, 5, 7, 98765432).unwrap();
let encoded = serialize(&t).unwrap();
let decoded: NaiveTime = deserialize(&encoded).unwrap();
assert_eq!(t, decoded);
}
}

View File

@ -50,15 +50,34 @@ mod inner {
not(any(target_os = "emscripten", target_os = "wasi"))
))]
mod inner {
use crate::{FixedOffset, LocalResult, NaiveDateTime};
use crate::{Datelike, FixedOffset, LocalResult, NaiveDateTime, Timelike};
pub(super) fn offset_from_utc_datetime(_utc: &NaiveDateTime) -> LocalResult<FixedOffset> {
let offset = js_sys::Date::new_0().get_timezone_offset();
pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> LocalResult<FixedOffset> {
let offset = js_sys::Date::from(utc.and_utc()).get_timezone_offset();
LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap())
}
pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> LocalResult<FixedOffset> {
offset_from_utc_datetime(local)
let mut year = local.year();
if year < 100 {
// The API in `js_sys` does not let us create a `Date` with negative years.
// And values for years from `0` to `99` map to the years `1900` to `1999`.
// Shift the value by a multiple of 400 years until it is `>= 100`.
let shift_cycles = (year - 100).div_euclid(400);
year -= shift_cycles * 400;
}
let js_date = js_sys::Date::new_with_year_month_day_hr_min_sec(
year as u32,
local.month0() as i32,
local.day() as i32,
local.hour() as i32,
local.minute() as i32,
local.second() as i32,
// ignore milliseconds, our representation of leap seconds may be problematic
);
let offset = js_date.get_timezone_offset();
// We always get a result, even if this time does not exist or is ambiguous.
LocalResult::Single(FixedOffset::west_opt((offset as i32) * 60).unwrap())
}
}
@ -95,33 +114,9 @@ impl Local {
}
/// Returns a `DateTime` which corresponds to the current date and time.
#[cfg(not(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
)))]
#[must_use]
pub fn now() -> DateTime<Local> {
Utc::now().with_timezone(&Local)
}
/// Returns a `DateTime` which corresponds to the current date and time.
#[cfg(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
))]
#[must_use]
pub fn now() -> DateTime<Local> {
use super::Utc;
let now: DateTime<Utc> = super::Utc::now();
// Workaround missing timezone logic in `time` crate
let offset =
FixedOffset::west_opt((js_sys::Date::new_0().get_timezone_offset() as i32) * 60)
.unwrap();
DateTime::from_utc(now.naive_utc(), offset)
}
}
impl TimeZone for Local {

View File

@ -321,7 +321,7 @@ impl<'a> TimeZoneRef<'a> {
// Check leap seconds
if !(self.leap_seconds.is_empty()
|| self.leap_seconds[0].unix_leap_time >= 0
&& saturating_abs(self.leap_seconds[0].correction) == 1)
&& self.leap_seconds[0].correction.saturating_abs() == 1)
{
return Err(Error::TimeZone("invalid leap second"));
}
@ -336,7 +336,7 @@ impl<'a> TimeZoneRef<'a> {
let diff_unix_leap_time = x1.unix_leap_time.saturating_sub(x0.unix_leap_time);
let abs_diff_correction =
saturating_abs(x1.correction.saturating_sub(x0.correction));
x1.correction.saturating_sub(x0.correction).saturating_abs();
if !(diff_unix_leap_time >= min_interval && abs_diff_correction == 1) {
return Err(Error::TimeZone("invalid leap second"));
@ -615,17 +615,6 @@ fn find_tz_file(path: impl AsRef<Path>) -> Result<File, Error> {
}
}
#[inline]
const fn saturating_abs(v: i32) -> i32 {
if v.is_positive() {
v
} else if v == i32::min_value() {
i32::max_value()
} else {
-v
}
}
// Possible system timezone directories
#[cfg(unix)]
const ZONE_INFO_DIRECTORIES: [&str; 4] =

View File

@ -53,7 +53,7 @@ pub trait Datelike: Sized {
/// Returns the ISO week.
fn iso_week(&self) -> IsoWeek;
/// Makes a new value with the year number changed.
/// Makes a new value with the year number changed, while keeping the same month and day.
///
/// Returns `None` when the resulting value would be invalid.
fn with_year(&self, year: i32) -> Option<Self>;

View File

@ -195,43 +195,6 @@ impl fmt::Debug for ParseWeekdayError {
}
}
#[cfg(test)]
mod tests {
use super::Weekday;
#[test]
fn test_num_days_from() {
for i in 0..7 {
let base_day = Weekday::try_from(i).unwrap();
assert_eq!(base_day.num_days_from_monday(), base_day.num_days_from(Weekday::Mon));
assert_eq!(base_day.num_days_from_sunday(), base_day.num_days_from(Weekday::Sun));
assert_eq!(base_day.num_days_from(base_day), 0);
assert_eq!(base_day.num_days_from(base_day.pred()), 1);
assert_eq!(base_day.num_days_from(base_day.pred().pred()), 2);
assert_eq!(base_day.num_days_from(base_day.pred().pred().pred()), 3);
assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred()), 4);
assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred().pred()), 5);
assert_eq!(
base_day.num_days_from(base_day.pred().pred().pred().pred().pred().pred()),
6
);
assert_eq!(base_day.num_days_from(base_day.succ()), 6);
assert_eq!(base_day.num_days_from(base_day.succ().succ()), 5);
assert_eq!(base_day.num_days_from(base_day.succ().succ().succ()), 4);
assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ()), 3);
assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ().succ()), 2);
assert_eq!(
base_day.num_days_from(base_day.succ().succ().succ().succ().succ().succ()),
1
);
}
}
}
// the actual `FromStr` implementation is in the `format` module to leverage the existing code
#[cfg(feature = "serde")]
@ -275,8 +238,46 @@ mod weekday_serde {
deserializer.deserialize_str(WeekdayVisitor)
}
}
}
#[cfg(test)]
mod tests {
use super::Weekday;
#[test]
fn test_num_days_from() {
for i in 0..7 {
let base_day = Weekday::try_from(i).unwrap();
assert_eq!(base_day.num_days_from_monday(), base_day.num_days_from(Weekday::Mon));
assert_eq!(base_day.num_days_from_sunday(), base_day.num_days_from(Weekday::Sun));
assert_eq!(base_day.num_days_from(base_day), 0);
assert_eq!(base_day.num_days_from(base_day.pred()), 1);
assert_eq!(base_day.num_days_from(base_day.pred().pred()), 2);
assert_eq!(base_day.num_days_from(base_day.pred().pred().pred()), 3);
assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred()), 4);
assert_eq!(base_day.num_days_from(base_day.pred().pred().pred().pred().pred()), 5);
assert_eq!(
base_day.num_days_from(base_day.pred().pred().pred().pred().pred().pred()),
6
);
assert_eq!(base_day.num_days_from(base_day.succ()), 6);
assert_eq!(base_day.num_days_from(base_day.succ().succ()), 5);
assert_eq!(base_day.num_days_from(base_day.succ().succ().succ()), 4);
assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ()), 3);
assert_eq!(base_day.num_days_from(base_day.succ().succ().succ().succ().succ()), 2);
assert_eq!(
base_day.num_days_from(base_day.succ().succ().succ().succ().succ().succ()),
1
);
}
}
#[test]
#[cfg(feature = "serde")]
fn test_serde_serialize() {
use serde_json::to_string;
use Weekday::*;
@ -298,6 +299,7 @@ mod weekday_serde {
}
#[test]
#[cfg(feature = "serde")]
fn test_serde_deserialize() {
use serde_json::from_str;
use Weekday::*;

4
taplo.toml Normal file
View File

@ -0,0 +1,4 @@
include = ["deny.toml", "**/Cargo.toml"]
[formatting]
inline_table_expand = false

View File

@ -58,12 +58,13 @@ fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) {
/// for testing only
#[allow(dead_code)]
#[cfg(not(target_os = "aix"))]
const DATE_PATH: &'static str = "/usr/bin/date";
const DATE_PATH: &str = "/usr/bin/date";
#[allow(dead_code)]
#[cfg(target_os = "aix")]
const DATE_PATH: &'static str = "/opt/freeware/bin/date";
const DATE_PATH: &str = "/opt/freeware/bin/date";
#[cfg(test)]
#[cfg(unix)]
/// test helper to sanity check the date command behaves as expected
/// asserts the command succeeded
fn assert_run_date_version() {

View File

@ -1,11 +1,18 @@
//! Run this test with:
//! `env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind`
//!
//! The `TZ` and `NOW` variables are used to compare the results inside the WASM environment with
//! the host system.
//! The check will fail if the local timezone does not match one of the timezones defined below.
#![cfg(all(
target_arch = "wasm32",
feature = "wasmbind",
not(any(target_os = "emscripten", target_os = "wasi"))
))]
use self::chrono::prelude::*;
use self::wasm_bindgen_test::*;
use chrono::prelude::*;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn now() {
@ -17,7 +24,7 @@ fn now() {
let actual = Utc.datetime_from_str(&now, "%s").unwrap();
let diff = utc - actual;
assert!(
diff < chrono::Duration::minutes(5),
diff < chrono::TimeDelta::minutes(5),
"expected {} - {} == {} < 5m (env var: {})",
utc,
actual,
@ -52,7 +59,7 @@ fn from_is_exact() {
let dt = DateTime::<Utc>::from(now.clone());
assert_eq!(now.get_time() as i64, dt.timestamp_millis_opt().unwrap());
assert_eq!(now.get_time() as i64, dt.timestamp_millis());
}
#[wasm_bindgen_test]
@ -72,7 +79,7 @@ fn convert_all_parts_with_milliseconds() {
let js_date = js_sys::Date::from(time);
assert_eq!(js_date.get_utc_full_year(), 2020);
assert_eq!(js_date.get_utc_month(), 12);
assert_eq!(js_date.get_utc_month(), 11); // months are numbered 0..=11
assert_eq!(js_date.get_utc_date(), 1);
assert_eq!(js_date.get_utc_hours(), 3);
assert_eq!(js_date.get_utc_minutes(), 1);