use crate::{Clock, Timer, Tz, ZonedDateTime};
#[cfg(not(feature = "native"))]
use crate::LocalParts;
use futures::channel::oneshot;
use futures::future::BoxFuture;
use time::{Date, Duration, Month, OffsetDateTime};
use wasm_bindgen::JsCast;
pub struct WasmClock;
impl Clock for WasmClock {
fn now_utc(&self) -> OffsetDateTime {
let ms = js_sys::Date::now(); let secs = (ms / 1000.0) as i64;
let nanos = ((ms % 1000.0) * 1_000_000.0) as u32;
OffsetDateTime::from_unix_timestamp(secs)
.ok()
.and_then(|dt| dt.replace_nanosecond(nanos).ok())
.unwrap_or(OffsetDateTime::UNIX_EPOCH)
}
fn now_in(&self, tz: &Tz) -> ZonedDateTime {
let utc = self.now_utc();
ZonedDateTime::new(utc, tz.clone())
}
fn today_in(&self, tz: &Tz) -> Date {
let p = self.now_in(tz).local_parts();
Month::try_from(p.month_1)
.ok()
.and_then(|m| Date::from_calendar_date(p.year, m, p.day).ok())
.unwrap_or_else(|| {
let js_date = js_sys::Date::new_0();
let year = js_date.get_full_year() as i32;
let month_0 = js_date.get_month() as u8; let day = js_date.get_date() as u8;
Month::try_from(month_0 + 1)
.ok()
.and_then(|m| Date::from_calendar_date(year, m, day).ok())
.unwrap_or(OffsetDateTime::UNIX_EPOCH.date())
})
}
fn now_unix_millis(&self) -> i64 {
js_sys::Date::now() as i64
}
}
#[cfg(not(feature = "native"))]
pub(crate) fn intl_local_parts(utc_ms: f64, iana: &str) -> Option<LocalParts> {
use js_sys::{Array, Date, Object, Reflect};
use wasm_bindgen::JsValue;
let opts = Object::new();
Reflect::set(&opts, &JsValue::from("timeZone"), &JsValue::from(iana)).ok()?;
for key in &["year", "month", "day", "hour", "minute", "second"] {
Reflect::set(&opts, &JsValue::from(*key), &JsValue::from("numeric")).ok()?;
}
let fmt = js_sys::Intl::DateTimeFormat::new(&Array::new(), &opts);
let date = Date::new(&JsValue::from_f64(utc_ms));
let parts_arr = fmt.format_to_parts(&date);
let mut year = None::<i32>;
let mut month_1 = None::<u8>;
let mut day = None::<u8>;
let mut hour = None::<u8>;
let mut minute = None::<u8>;
let mut second = None::<u8>;
for item in parts_arr.iter() {
let obj: Object = item.dyn_into().ok()?;
let typ = Reflect::get(&obj, &JsValue::from("type"))
.ok()?
.as_string()?;
let val = Reflect::get(&obj, &JsValue::from("value"))
.ok()?
.as_string()?;
let n: i64 = val.parse().ok()?;
match typ.as_str() {
"year" => year = Some(n as i32),
"month" => month_1 = Some(n as u8),
"day" => day = Some(n as u8),
"hour" => hour = Some(n as u8),
"minute" => minute = Some(n as u8),
"second" => second = Some(n as u8),
_ => {}
}
}
let instant_for_dow =
OffsetDateTime::from_unix_timestamp_nanos((utc_ms * 1_000_000.0) as i128).ok()?;
Some(LocalParts {
year: year?,
month_1: month_1?,
day: day?,
hour: hour?,
minute: minute?,
second: second?,
nano: 0,
dow_monday0: instant_for_dow.weekday().number_days_from_monday(),
})
}
pub fn browser_local_tz() -> Tz {
use js_sys::{Array, Intl, Object, Reflect};
use wasm_bindgen::JsValue;
let fmt = Intl::DateTimeFormat::new(&Array::new(), &Object::new());
let resolved = fmt.resolved_options();
Reflect::get(&resolved, &JsValue::from_str("timeZone"))
.ok()
.and_then(|v| v.as_string())
.and_then(|s| Tz::parse(&s).ok())
.unwrap_or_else(Tz::utc)
}
pub struct WasmTimer;
impl Timer for WasmTimer {
fn sleep(&self, dur: Duration) -> BoxFuture<'static, ()> {
let ms = dur.whole_milliseconds().max(0) as i32;
let (tx, rx) = oneshot::channel::<()>();
let closure = wasm_bindgen::closure::Closure::once(move || {
let _ = tx.send(());
});
if let Some(win) = web_sys::window() {
let _ = win.set_timeout_with_callback_and_timeout_and_arguments_0(
closure.as_ref().unchecked_ref(),
ms,
);
}
closure.forget();
Box::pin(async move {
let _ = rx.await;
})
}
fn next_tick(&self) -> BoxFuture<'static, ()> {
self.sleep(Duration::ZERO)
}
}