Skip to content
Open
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
45 changes: 45 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ itertools = "0.14.0"
base64 = "0.22.1"
termcolor = "1.4.1"
flate2 = "1.0.30"
jiff = "0.2.0"

# =============================================================================
#
Expand Down
2 changes: 2 additions & 0 deletions crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ wasmtime_option_group! {
pub cli: Option<bool>,
/// Enable WASI APIs marked as: @unstable(feature = cli-exit-with-code)
pub cli_exit_with_code: Option<bool>,
/// Enable WASI APIs marked as: @unstable(feature = clocks-timezone)
pub clocks_timezone: Option<bool>,
/// Deprecated alias for `cli`
pub common: Option<bool>,
/// Enable support for WASI neural network imports (experimental)
Expand Down
5 changes: 5 additions & 0 deletions crates/test-programs/src/bin/cli_default_clocks.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use test_programs::wasi::clocks::{timezone, wall_clock};

fn main() {
let a = std::time::Instant::now();
let b = std::time::Instant::now();
Expand All @@ -7,4 +9,7 @@ fn main() {
let d = std::time::SystemTime::now();
let _ = c.duration_since(std::time::UNIX_EPOCH).unwrap();
let _ = d.duration_since(std::time::UNIX_EPOCH).unwrap();

let wall_time = wall_clock::now();
let _ = timezone::display(wall_time);
}
2 changes: 1 addition & 1 deletion crates/test-programs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ wit_bindgen::generate!({
"../wasi-tls/wit/deps/tls",
],
world: "wasmtime:test/test",
features: ["cli-exit-with-code", "tls"],
features: ["cli-exit-with-code", "tls", "clocks-timezone"],
generate_all,
});

Expand Down
1 change: 1 addition & 0 deletions crates/wasi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ cap-rand = { workspace = true }
cap-fs-ext = { workspace = true }
cap-net-ext = { workspace = true }
cap-time-ext = { workspace = true }
jiff = { workspace = true }
io-lifetimes = { workspace = true }
fs-set-times = { workspace = true }
bitflags = { workspace = true }
Expand Down
75 changes: 75 additions & 0 deletions crates/wasi/src/clocks.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use crate::p2::bindings::clocks::timezone::TimezoneDisplay;
use cap_std::time::{Duration, Instant, SystemClock, SystemTime};
use cap_std::{AmbientAuthority, ambient_authority};
use cap_time_ext::{MonotonicClockExt as _, SystemClockExt as _};
use jiff::Timestamp;
use jiff::tz::TimeZone as JiffTimeZone;
use std::{convert::TryFrom, str::FromStr};
use wasmtime::component::{HasData, ResourceTable};

pub(crate) struct WasiClocks;
Expand All @@ -12,13 +16,15 @@ impl HasData for WasiClocks {
pub struct WasiClocksCtx {
pub wall_clock: Box<dyn HostWallClock + Send>,
pub monotonic_clock: Box<dyn HostMonotonicClock + Send>,
pub timezone: Box<dyn HostTimezone + Send>,
}

impl Default for WasiClocksCtx {
fn default() -> Self {
Self {
wall_clock: wall_clock(),
monotonic_clock: monotonic_clock(),
timezone: timezone(),
}
}
}
Expand All @@ -42,6 +48,11 @@ pub trait HostMonotonicClock: Send {
fn now(&self) -> u64;
}

pub trait HostTimezone: Send {
fn display(&self, datetime: Duration) -> TimezoneDisplay;
fn utc_offset(&self, datetime: Duration) -> i32;
}

pub struct WallClock {
/// The underlying system clock.
clock: cap_std::time::SystemClock,
Expand Down Expand Up @@ -123,6 +134,10 @@ pub fn wall_clock() -> Box<dyn HostWallClock + Send> {
Box::new(WallClock::default())
}

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

pub(crate) struct Datetime {
pub seconds: u64,
pub nanoseconds: u32,
Expand All @@ -141,3 +156,63 @@ impl TryFrom<SystemTime> for Datetime {
})
}
}

pub struct Timezone {
timezone: JiffTimeZone,
}

impl Default for Timezone {
fn default() -> Self {
Self::new()
}
}

impl Timezone {
pub fn new() -> Self {
Self {
timezone: JiffTimeZone::try_system().unwrap_or(JiffTimeZone::UTC),
}
}

fn timezone_from_duration(&self, datetime: Duration) -> Option<TimezoneDisplay> {
let timestamp = Timestamp::from_second(datetime.as_secs() as i64).ok()?;
let localtime = self.timezone.to_offset_info(timestamp);
let utc_offset = localtime.offset().seconds();
let name = self.timezone.iana_name().unwrap_or("UTC").to_string();
let in_daylight_saving_time = jiff::tz::Dst::Yes == localtime.dst();
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,
}
}
}

impl FromStr for Timezone {
type Err = wasmtime::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let timezone = JiffTimeZone::get(s)?;
Ok(Timezone { timezone })
}
}
10 changes: 9 additions & 1 deletion crates/wasi/src/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::cli::{StdinStream, StdoutStream, WasiCliCtx};
use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx};
use crate::clocks::{HostMonotonicClock, HostTimezone, HostWallClock, WasiClocksCtx};
use crate::filesystem::{Dir, WasiFilesystemCtx};
use crate::random::WasiRandomCtx;
use crate::sockets::{SocketAddrCheck, SocketAddrUse, WasiSocketsCtx};
Expand Down Expand Up @@ -366,6 +366,14 @@ impl WasiCtxBuilder {
self
}

/// Configures `wasi:clocks/timezone` to use the `clock` specified.
///
/// By default the host's timezone is used.
pub fn timezone(&mut self, clock: impl HostTimezone + 'static) -> &mut Self {
self.clocks.timezone = Box::new(clock);
self
}

/// Allow all network addresses accessible to the host.
///
/// This method will inherit all network addresses meaning that any address
Expand Down
2 changes: 1 addition & 1 deletion crates/wasi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub mod runtime;
pub mod sockets;
mod view;

pub use self::clocks::{HostMonotonicClock, HostWallClock};
pub use self::clocks::{HostMonotonicClock, HostTimezone, HostWallClock};
pub use self::ctx::{WasiCtx, WasiCtxBuilder};
pub use self::error::{I32Exit, TrappableError};
pub use self::filesystem::{DirPerms, FilePerms, OpenMode};
Expand Down
13 changes: 13 additions & 0 deletions crates/wasi/src/p2/host/clocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::clocks::WasiClocksCtxView;
use crate::p2::DynPollable;
use crate::p2::bindings::{
clocks::monotonic_clock::{self, Duration as WasiDuration, Instant},
clocks::timezone::{self, TimezoneDisplay},
clocks::wall_clock::{self, Datetime},
};
use cap_std::time::SystemTime;
Expand Down Expand Up @@ -126,3 +127,15 @@ impl Pollable for Deadline {
}
}
}

impl timezone::Host for WasiClocksCtxView<'_> {
fn display(&mut self, when: Datetime) -> anyhow::Result<TimezoneDisplay> {
let duration = std::time::Duration::new(when.seconds, when.nanoseconds);
Ok(self.ctx.timezone.display(duration))
}

fn utc_offset(&mut self, when: Datetime) -> anyhow::Result<i32> {
let duration = std::time::Duration::new(when.seconds, when.nanoseconds);
Ok(self.ctx.timezone.utc_offset(duration))
}
}
3 changes: 3 additions & 0 deletions crates/wasi/src/p2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
//! [`wasi:cli/terminal-stderr`]: bindings::cli::terminal_stderr::Host
//! [`wasi:clocks/monotonic-clock`]: bindings::clocks::monotonic_clock::Host
//! [`wasi:clocks/wall-clock`]: bindings::clocks::wall_clock::Host
//! [`wasi:clocks/timezone`]: bindings::clocks::timezone::Host
//! [`wasi:filesystem/preopens`]: bindings::filesystem::preopens::Host
//! [`wasi:filesystem/types`]: bindings::filesystem::types::Host
//! [`wasi:io/error`]: wasmtime_wasi_io::bindings::wasi::io::error::Host
Expand Down Expand Up @@ -339,12 +340,14 @@ fn add_nonblocking_to_linker<'a, T: WasiView, O>(
where
bindings::sockets::network::LinkOptions: From<&'a O>,
bindings::cli::exit::LinkOptions: From<&'a O>,
bindings::clocks::timezone::LinkOptions: From<&'a O>,
{
use crate::p2::bindings::{cli, clocks, filesystem, random, sockets};

let l = linker;
clocks::wall_clock::add_to_linker::<T, WasiClocks>(l, T::clocks)?;
clocks::monotonic_clock::add_to_linker::<T, WasiClocks>(l, T::clocks)?;
clocks::timezone::add_to_linker::<T, WasiClocks>(l, &options.into(), T::clocks)?;
filesystem::preopens::add_to_linker::<T, WasiFilesystem>(l, T::filesystem)?;
random::random::add_to_linker::<T, WasiRandom>(l, |t| &mut t.ctx().ctx.random)?;
random::insecure::add_to_linker::<T, WasiRandom>(l, |t| &mut t.ctx().ctx.random)?;
Expand Down
1 change: 1 addition & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ impl RunCommon {
let mut p2_options = wasmtime_wasi::p2::bindings::LinkOptions::default();
p2_options.cli_exit_with_code(self.common.wasi.cli_exit_with_code.unwrap_or(false));
p2_options.network_error_code(self.common.wasi.network_error_code.unwrap_or(false));
p2_options.clocks_timezone(self.common.wasi.clocks_timezone.unwrap_or(false));
wasmtime_wasi::p2::add_to_linker_with_options_async(linker, &p2_options)?;

#[cfg(feature = "component-model-async")]
Expand Down
7 changes: 6 additions & 1 deletion tests/all/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1356,7 +1356,12 @@ mod test_programs {

#[test]
fn cli_default_clocks() -> Result<()> {
run_wasmtime(&["run", "-Wcomponent-model", CLI_DEFAULT_CLOCKS_COMPONENT])?;
run_wasmtime(&[
"run",
"-Wcomponent-model",
"-Sclocks-timezone=y",
CLI_DEFAULT_CLOCKS_COMPONENT,
])?;
Ok(())
}

Expand Down
Loading