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 timezone host functions. #6899

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
97 changes: 97 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ criterion = { version = "0.5.0", default-features = false, features = ["html_rep
rustc-hash = "1.1.0"
libtest-mimic = "0.7.0"
semver = { version = "1.0.17", default-features = false }
chrono = "0.4.26"
chrono-tz = "0.8.3"

# =============================================================================
#
Expand Down
3 changes: 2 additions & 1 deletion crates/test-programs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub mod nn;
pub mod preview1;
pub mod sockets;

wit_bindgen::generate!("test-command" in "../wasi/wit");
wit_bindgen::generate!("test-command" in "../wasi/wit", features: ["clocks-timezone"]);

pub mod proxy {
wit_bindgen::generate!({
Expand All @@ -15,5 +15,6 @@ pub mod proxy {
"wasi:http/[email protected]": crate::wasi::http::types,
"wasi:http/[email protected]": crate::wasi::http::outgoing_handler,
},
features: ["clocks-timezone"],
});
}
2 changes: 2 additions & 0 deletions crates/wasi-http/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ mod generated {
trappable_error_type: {
"wasi:http/types/error-code" => crate::HttpError,
},
features: ["clocks-timezone"],
});
}

Expand All @@ -64,6 +65,7 @@ pub mod sync {
"wasi": wasmtime_wasi::bindings, // everything else
},
require_store_data_send: true,
features: ["clocks-timezone"],
});
}

Expand Down
2 changes: 2 additions & 0 deletions crates/wasi-http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ where
let closure = type_annotate_wasi::<T, _>(|t| wasmtime_wasi::WasiImpl(t));
wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?;
wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?;
wasmtime_wasi::bindings::clocks::timezone::add_to_linker_get_host(l, closure)?;
wasmtime_wasi::bindings::io::poll::add_to_linker_get_host(l, closure)?;
wasmtime_wasi::bindings::io::error::add_to_linker_get_host(l, closure)?;
wasmtime_wasi::bindings::io::streams::add_to_linker_get_host(l, closure)?;
Expand Down Expand Up @@ -375,6 +376,7 @@ where

wasmtime_wasi::bindings::clocks::wall_clock::add_to_linker_get_host(l, closure)?;
wasmtime_wasi::bindings::clocks::monotonic_clock::add_to_linker_get_host(l, closure)?;
wasmtime_wasi::bindings::clocks::timezone::add_to_linker_get_host(l, closure)?;
wasmtime_wasi::bindings::sync::io::poll::add_to_linker_get_host(l, closure)?;
wasmtime_wasi::bindings::sync::io::streams::add_to_linker_get_host(l, closure)?;
wasmtime_wasi::bindings::io::error::add_to_linker_get_host(l, closure)?;
Expand Down
54 changes: 54 additions & 0 deletions crates/wasi-http/wit/deps/clocks/timezone.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package wasi:[email protected];

@unstable(feature = clocks-timezone)
interface timezone {
use wall-clock.{datetime};

/// Return information needed to display the given `datetime`. This includes
/// the UTC offset, the time zone name, and a flag indicating whether
/// daylight saving time is active.
///
/// If the timezone cannot be determined for the given `datetime`, return a
/// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight
/// saving time.
@unstable(feature = clocks-timezone)
display: func(when: datetime) -> timezone-display;

/// The same as `display`, but only return the UTC offset.
@unstable(feature = clocks-timezone)
utc-offset: func(when: datetime) -> s32;

/// Information useful for displaying the timezone of a specific `datetime`.
///
/// This information may vary within a single `timezone` to reflect daylight
/// saving time adjustments.
@unstable(feature = clocks-timezone)
record timezone-display {
/// The number of seconds difference between UTC time and the local
/// time of the timezone.
///
/// The returned value will always be less than 86400 which is the
/// number of seconds in a day (24*60*60).
///
/// In implementations that do not expose an actual time zone, this
/// should return 0.
utc-offset: s32,

/// The abbreviated name of the timezone to display to a user. The name
/// `UTC` indicates Coordinated Universal Time. Otherwise, this should
/// reference local standards for the name of the time zone.
///
/// In implementations that do not expose an actual time zone, this
/// should be the string `UTC`.
///
/// In time zones that do not have an applicable name, a formatted
/// representation of the UTC offset may be returned, such as `-04:00`.
name: string,

/// Whether daylight saving time is active.
///
/// In implementations that do not expose an actual time zone, this
/// should return false.
in-daylight-saving-time: bool,
}
}
2 changes: 2 additions & 0 deletions crates/wasi-http/wit/deps/clocks/world.wit
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ package wasi:[email protected];
world imports {
import monotonic-clock;
import wall-clock;
@unstable(feature = clocks-timezone)
import timezone;
}
3 changes: 3 additions & 0 deletions crates/wasi-preview1-component-adapter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pub mod bindings {
// Instead, we manually define the bindings for these functions in
// terms of raw pointers.
skip: ["run", "get-environment", "poll"],
features: ["clocks-timezone"],
});

#[cfg(feature = "reactor")]
Expand All @@ -78,6 +79,7 @@ pub mod bindings {
// Instead, we manually define the bindings for these functions in
// terms of raw pointers.
skip: ["get-environment", "poll"],
features: ["clocks-timezone"],
});

#[cfg(feature = "proxy")]
Expand All @@ -99,6 +101,7 @@ pub mod bindings {
raw_strings,
runtime_path: "crate::bindings::wit_bindgen_rt_shim",
skip: ["poll"],
features: ["clocks-timezone"],
});

pub mod wit_bindgen_rt_shim {
Expand Down
2 changes: 2 additions & 0 deletions crates/wasi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ cap-rand = { workspace = true }
cap-fs-ext = { workspace = true }
cap-net-ext = { workspace = true }
cap-time-ext = { workspace = true }
chrono = { workspace = true }
chrono-tz = { workspace = true }
io-lifetimes = { workspace = true }
fs-set-times = { workspace = true }
bitflags = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions crates/wasi/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ pub mod sync {
"wasi:sockets/udp/udp-socket": super::super::sockets::udp::UdpSocket,
},
require_store_data_send: true,
features: ["clocks-timezone"],
});
}
pub use self::generated::exports;
Expand Down Expand Up @@ -413,6 +414,7 @@ mod async_io {
"wasi:cli/terminal-input/terminal-input": crate::stdio::TerminalInput,
"wasi:cli/terminal-output/terminal-output": crate::stdio::TerminalOutput,
},
features: ["clocks-timezone"],
});
}

Expand Down
6 changes: 6 additions & 0 deletions crates/wasi/src/clocks.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod host;
use crate::bindings::clocks::timezone::TimezoneDisplay;
use cap_std::time::Duration;

pub trait HostWallClock: Send {
Expand All @@ -10,3 +11,8 @@ pub trait HostMonotonicClock: Send {
fn resolution(&self) -> u64;
fn now(&self) -> u64;
}

pub trait HostTimezone: Send {
fn display(&self, datetime: Duration) -> TimezoneDisplay;
fn utc_offset(&self, datetime: Duration) -> i32;
}
56 changes: 55 additions & 1 deletion crates/wasi/src/clocks/host.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use super::{HostMonotonicClock, HostWallClock};
use super::{HostMonotonicClock, HostTimezone, HostWallClock};
use crate::bindings::clocks::timezone::TimezoneDisplay;
use cap_std::time::{Duration, Instant, SystemClock};
use cap_std::{ambient_authority, AmbientAuthority};
use cap_time_ext::{MonotonicClockExt, SystemClockExt};
use chrono::{NaiveDateTime, TimeZone};
use chrono_tz::{OffsetComponents, Tz, TZ_VARIANTS};

pub struct WallClock {
/// The underlying system clock.
Expand Down Expand Up @@ -64,10 +67,61 @@ impl HostMonotonicClock for MonotonicClock {
}
}

pub struct Timezone {
// The underlying system timezone.
timezone: cap_time_ext::Timezone,
}

impl Timezone {
pub fn new(ambient_authority: AmbientAuthority) -> Self {
Self {
timezone: cap_time_ext::Timezone::new(ambient_authority),
}
}

fn timezone_from_duration(&self, datetime: Duration) -> Option<TimezoneDisplay> {
let name = self.timezone.timezone_name().ok()?;
let tz: Tz = TZ_VARIANTS.into_iter().find(|tz| tz.to_string() == name)?;
let naive_datetime = NaiveDateTime::from_timestamp_opt(datetime.as_secs() as i64, 0)?;
let tz_offset = tz.offset_from_local_datetime(&naive_datetime).single()?;
let utc_offset = tz_offset.base_utc_offset().num_hours() as i32;
let in_daylight_saving_time = !tz_offset.dst_offset().is_zero();
Some(TimezoneDisplay {
utc_offset,
name,
in_daylight_saving_time,
})
}
}

impl HostTimezone for Timezone {
fn display(&self, datetime: Duration) -> TimezoneDisplay {
match self.timezone_from_duration(datetime) {
None => TimezoneDisplay {
utc_offset: 0,
name: "UTC".to_string(),
in_daylight_saving_time: false,
},
Some(timezone_display) => timezone_display,
}
}

fn utc_offset(&self, datetime: Duration) -> i32 {
match self.timezone_from_duration(datetime) {
None => 0,
Some(timezone_display) => timezone_display.utc_offset,
}
}
}

pub fn monotonic_clock() -> Box<dyn HostMonotonicClock + Send> {
Box::new(MonotonicClock::new(ambient_authority()))
}

pub fn wall_clock() -> Box<dyn HostWallClock + Send> {
Box::new(WallClock::new(ambient_authority()))
}

pub fn timezone() -> Box<dyn HostTimezone + Send> {
Box::new(Timezone::new(ambient_authority()))
}
Loading