use std::future::Future;
use std::time::Duration;
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
not(feature = "wasm_js")
))]
pub mod host_clock;
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
not(feature = "wasm_js")
))]
pub(crate) use host_clock::{Instant, SystemTime, UNIX_EPOCH};
#[cfg(all(target_arch = "wasm32", target_os = "unknown", feature = "wasm_js"))]
pub(crate) use web_time::{Instant, SystemTime, UNIX_EPOCH};
#[cfg(all(target_arch = "wasm32", not(target_os = "unknown")))]
pub(crate) use std::time::{Instant, SystemTime, UNIX_EPOCH};
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use tokio::time::Instant;
pub(crate) async fn sleep(duration: Duration) {
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
not(feature = "wasm_js")
))]
{
let deadline = Instant::now() + duration;
while Instant::now() < deadline {
std::hint::spin_loop();
}
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown", feature = "wasm_js"))]
send_wrapper::SendWrapper::new(gloo_timers::future::TimeoutFuture::new(duration_millis(
duration,
)))
.await;
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
tokio::time::sleep(duration).await;
}
pub(crate) struct TimeoutElapsed;
pub(crate) async fn timeout<F: Future>(
duration: Duration,
future: F,
) -> Result<F::Output, TimeoutElapsed> {
#[cfg(all(
target_arch = "wasm32",
target_os = "unknown",
not(feature = "wasm_js")
))]
{
use std::pin::pin;
use std::task::Poll;
let deadline = Instant::now() + duration;
let mut future = pin!(future);
return std::future::poll_fn(move |cx| match future.as_mut().poll(cx) {
Poll::Ready(output) => Poll::Ready(Ok(output)),
Poll::Pending if Instant::now() >= deadline => Poll::Ready(Err(TimeoutElapsed)),
Poll::Pending => Poll::Pending,
})
.await;
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown", feature = "wasm_js"))]
{
use futures_util::future::{Either, select};
let timer = send_wrapper::SendWrapper::new(gloo_timers::future::TimeoutFuture::new(
duration_millis(duration),
));
futures_util::pin_mut!(future, timer);
match select(future, timer).await {
Either::Left((output, _)) => Ok(output),
Either::Right(_) => Err(TimeoutElapsed),
}
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
tokio::time::timeout(duration, future)
.await
.map_err(|_| TimeoutElapsed)
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown", feature = "wasm_js"))]
fn duration_millis(duration: Duration) -> u32 {
(duration.as_secs_f64() * 1000.0)
.ceil()
.clamp(0.0, u32::MAX as f64) as u32
}
pub(crate) fn to_chrono_utc(t: SystemTime) -> chrono::DateTime<chrono::Utc> {
let epoch = || chrono::DateTime::from_timestamp(0, 0).expect("epoch is representable");
match t.duration_since(UNIX_EPOCH) {
Ok(dur) => chrono::DateTime::from_timestamp(dur.as_secs() as i64, dur.subsec_nanos())
.unwrap_or_else(epoch),
Err(e) => {
let dur = e.duration();
let (secs, nanos) = if dur.subsec_nanos() == 0 {
(-(dur.as_secs() as i64), 0)
} else {
(
-(dur.as_secs() as i64) - 1,
1_000_000_000 - dur.subsec_nanos(),
)
};
chrono::DateTime::from_timestamp(secs, nanos).unwrap_or_else(epoch)
}
}
}
pub(crate) fn now_utc() -> chrono::DateTime<chrono::Utc> {
to_chrono_utc(SystemTime::now())
}
pub(crate) fn from_chrono<Tz: chrono::TimeZone>(dt: chrono::DateTime<Tz>) -> SystemTime {
let utc = dt.with_timezone(&chrono::Utc);
let secs = utc.timestamp();
let nanos = utc.timestamp_subsec_nanos();
if secs >= 0 {
UNIX_EPOCH + std::time::Duration::new(secs as u64, nanos)
} else if nanos == 0 {
UNIX_EPOCH - std::time::Duration::new((-secs) as u64, 0)
} else {
UNIX_EPOCH - std::time::Duration::new((-secs - 1) as u64, 1_000_000_000 - nanos)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn now_utc_tracks_the_system_clock() {
let before = chrono::Utc::now();
let sampled = now_utc();
let after = chrono::Utc::now();
assert!(
sampled >= before && sampled <= after,
"now_utc() {sampled} outside [{before}, {after}]"
);
}
#[test]
fn now_utc_is_monotonic_across_calls() {
assert!(now_utc() <= now_utc());
}
#[test]
fn to_chrono_utc_epoch() {
let dt = to_chrono_utc(UNIX_EPOCH);
assert_eq!((dt.timestamp(), dt.timestamp_subsec_nanos()), (0, 0));
}
#[test]
fn to_chrono_utc_pre_epoch_whole_second() {
let t = UNIX_EPOCH - std::time::Duration::from_secs(2);
let dt = to_chrono_utc(t);
assert_eq!((dt.timestamp(), dt.timestamp_subsec_nanos()), (-2, 0));
}
#[test]
fn to_chrono_utc_pre_epoch_subsecond() {
let t = UNIX_EPOCH - std::time::Duration::from_millis(500);
let dt = to_chrono_utc(t);
assert_eq!(
(dt.timestamp(), dt.timestamp_subsec_nanos()),
(-1, 500_000_000)
);
assert_eq!(dt.to_rfc3339(), "1969-12-31T23:59:59.500+00:00");
}
#[test]
fn from_chrono_pre_epoch_subsecond() {
let dt = chrono::DateTime::from_timestamp(-1, 500_000_000).unwrap();
let t = from_chrono(dt);
assert_eq!(
t.duration_since(UNIX_EPOCH).unwrap_err().duration(),
std::time::Duration::from_millis(500)
);
}
#[test]
fn round_trip_pre_epoch_subsecond() {
let original = UNIX_EPOCH - std::time::Duration::from_millis(500);
let round_tripped = from_chrono(to_chrono_utc(original));
assert_eq!(round_tripped, original);
}
#[test]
fn round_trip_post_epoch_subsecond() {
let original = UNIX_EPOCH + std::time::Duration::from_millis(500);
let round_tripped = from_chrono(to_chrono_utc(original));
assert_eq!(round_tripped, original);
}
}