Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a feature and method to override now #1244

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ rkyv-16 = ["dep:rkyv", "rkyv?/size_16"]
rkyv-32 = ["dep:rkyv", "rkyv?/size_32"]
rkyv-64 = ["dep:rkyv", "rkyv?/size_64"]
rkyv-validation = ["rkyv?/validation"]
test-override = []
# Features for internal use only:
__internal_bench = []

Expand Down Expand Up @@ -71,7 +72,7 @@ bincode = { version = "1.3.0" }
wasm-bindgen-test = "0.3"

[package.metadata.docs.rs]
features = ["arbitrary", "rkyv", "serde", "unstable-locales"]
features = ["arbitrary", "rkyv", "serde", "test-override", "unstable-locales"]
rustdoc-args = ["--cfg", "docsrs"]

[package.metadata.playground]
Expand Down
46 changes: 46 additions & 0 deletions src/offset/local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use rkyv::{Archive, Deserialize, Serialize};
use super::fixed::FixedOffset;
use super::{LocalResult, TimeZone};
use crate::naive::{NaiveDate, NaiveDateTime, NaiveTime};
#[cfg(feature = "test-override")]
use crate::offset::utc::OVERRIDE_NOW;
#[allow(deprecated)]
use crate::Date;
use crate::{DateTime, Utc};
Expand Down Expand Up @@ -151,8 +153,38 @@ impl Local {
/// let now_with_offset = Local::now().with_timezone(&offset);
/// ```
pub fn now() -> DateTime<Local> {
#[cfg(all(feature = "test-override", test))]
if let Some(t) = OVERRIDE_NOW.with(|o| *o.borrow()) {
return t.into();
}
Utc::now().with_timezone(&Local)
}

/// Override the value that will be returned by `Local::now()` and `Utc::now()`, for use in
/// tests.
///
/// This method is only available behind the `test-override` feature, only works within the
/// current thread, and only in `cfg(test)`.
///
/// ```no_run
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codecov correctly says the lines of this doctest are not covered. But I can't help it 😄.

/// # // Doctests don't have `cfg(test)`
/// use chrono::{Local, FixedOffset, TimeZone, Datelike};
/// fn is_today_leap_day() -> bool {
/// let today = Local::now().date_naive();
/// today.month() == 2 && today.day() == 29
/// }
///
/// let now =
/// FixedOffset::east_opt(3 * 60 * 60).unwrap().with_ymd_and_hms(2020, 2, 29, 0, 0, 0).unwrap();
/// Local::override_now(Some(now));
/// assert!(is_today_leap_day());
/// Local::override_now(None);
/// ```
#[cfg(feature = "test-override")]
#[cfg_attr(docsrs, doc(cfg(feature = "test-override")))]
pub fn override_now(datetime: Option<DateTime<FixedOffset>>) {
OVERRIDE_NOW.with(|o| *o.borrow_mut() = datetime);
}
}

impl TimeZone for Local {
Expand Down Expand Up @@ -187,6 +219,8 @@ impl TimeZone for Local {
mod tests {
use super::Local;
use crate::offset::TimeZone;
#[cfg(feature = "test-override")]
use crate::FixedOffset;
use crate::{Datelike, Duration, Utc};

#[test]
Expand Down Expand Up @@ -276,4 +310,16 @@ mod tests {
// wrapping object
assert_eq!(rkyv::from_bytes::<Local>(&bytes).unwrap(), super::ArchivedLocal);
}

#[cfg(all(feature = "clock", feature = "test-override"))]
#[test]
fn test_override() {
let now =
FixedOffset::east_opt(10800).unwrap().with_ymd_and_hms(2020, 2, 29, 12, 0, 0).unwrap();
Local::override_now(Some(now));
assert_eq!(Local::now(), now);

Local::override_now(None);
assert_ne!(Local::now(), now);
}
}
66 changes: 66 additions & 0 deletions src/offset/utc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

//! The UTC (Coordinated Universal Time) time zone.

#[cfg(all(feature = "clock", feature = "test-override"))]
use core::cell::RefCell;
use core::fmt;
#[cfg(all(
feature = "now",
Expand All @@ -23,6 +25,12 @@ use crate::naive::{NaiveDate, NaiveDateTime};
#[allow(deprecated)]
use crate::{Date, DateTime};

// Value to use for `Utc::now()` and `Local::now()`, when set with `Local::override_now`.
#[cfg(all(feature = "clock", feature = "test-override"))]
thread_local!(
pub(super) static OVERRIDE_NOW: RefCell<Option<DateTime<FixedOffset>>> = RefCell::new(None)
);

/// The UTC time zone. This is the most efficient time zone when you don't need the local time.
/// It is also used as an offset (which is also a dummy type).
///
Expand Down Expand Up @@ -93,6 +101,10 @@ impl Utc {
)))]
#[must_use]
pub fn now() -> DateTime<Utc> {
#[cfg(all(feature = "test-override", test))]
if let Some(t) = OVERRIDE_NOW.with(|o| *o.borrow()) {
return t.into();
}
let now =
SystemTime::now().duration_since(UNIX_EPOCH).expect("system time before Unix epoch");
let naive =
Expand Down Expand Up @@ -152,3 +164,57 @@ impl fmt::Display for Utc {
write!(f, "UTC")
}
}

#[cfg(test)]
mod tests {
#[cfg(all(feature = "clock", feature = "test-override"))]
use crate::{FixedOffset, Local, TimeZone, Utc};

#[cfg(all(feature = "clock", feature = "test-override"))]
#[test]
fn test_override() {
let now =
FixedOffset::east_opt(10800).unwrap().with_ymd_and_hms(2020, 2, 29, 12, 0, 0).unwrap();
Local::override_now(Some(now));
assert_eq!(Utc::now(), now);

Local::override_now(None);
assert_ne!(Utc::now(), now);
}

#[cfg(all(feature = "clock", feature = "test-override"))]
#[test]
fn test_override_multiple_threads() {
use std::sync::{Arc, Barrier};
use std::thread::spawn;

let barrier = Arc::new(Barrier::new(3));

let barrier_1 = barrier.clone();
let thread_1 = spawn(move || {
Local::override_now(Some(Utc.with_ymd_and_hms(2020, 2, 29, 12, 0, 0).unwrap().into()));
barrier_1.wait();

assert_eq!(Utc::now(), Utc.with_ymd_and_hms(2020, 2, 29, 12, 0, 0).unwrap());
});

let barrier_2 = barrier.clone();
let thread_2 = spawn(move || {
Local::override_now(Some(Utc.with_ymd_and_hms(2016, 2, 29, 12, 0, 0).unwrap().into()));
barrier_2.wait();

assert_eq!(Utc::now(), Utc.with_ymd_and_hms(2016, 2, 29, 12, 0, 0).unwrap());
});

let barrier_3 = barrier;
let thread_3 = spawn(move || {
barrier_3.wait();

assert!(Utc::now() > Utc.with_ymd_and_hms(2021, 8, 7, 13, 0, 0).unwrap());
});

thread_1.join().expect("Thread 1 should succeed");
thread_2.join().expect("Thread 2 should succeed");
thread_3.join().expect("Thread 3 should succeed");
}
}
Loading