Skip to content

Commit c390f5f

Browse files
committed
feat: Add timezone host functions.
Allow clocks-timezone feature to be enabled.
1 parent 1a88c70 commit c390f5f

File tree

13 files changed

+213
-3
lines changed

13 files changed

+213
-3
lines changed

Cargo.lock

Lines changed: 103 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ itertools = "0.14.0"
413413
base64 = "0.22.1"
414414
termcolor = "1.4.1"
415415
flate2 = "1.0.30"
416+
chrono = "0.4.26"
417+
chrono-tz = "0.8.3"
416418

417419
# =============================================================================
418420
#

crates/cli-flags/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ wasmtime_option_group! {
420420
pub cli: Option<bool>,
421421
/// Enable WASI APIs marked as: @unstable(feature = cli-exit-with-code)
422422
pub cli_exit_with_code: Option<bool>,
423+
/// Enable WASI APIs marked as: @unstable(feature = clocks-timezone)
424+
pub clocks_timezone: Option<bool>,
423425
/// Deprecated alias for `cli`
424426
pub common: Option<bool>,
425427
/// Enable support for WASI neural network imports (experimental)

crates/test-programs/src/bin/cli_default_clocks.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use test_programs::wasi::clocks::{timezone, wall_clock};
2+
13
fn main() {
24
let a = std::time::Instant::now();
35
let b = std::time::Instant::now();
@@ -7,4 +9,7 @@ fn main() {
79
let d = std::time::SystemTime::now();
810
let _ = c.duration_since(std::time::UNIX_EPOCH).unwrap();
911
let _ = d.duration_since(std::time::UNIX_EPOCH).unwrap();
12+
13+
let wall_time = wall_clock::now();
14+
let _ = timezone::display(wall_time);
1015
}

crates/test-programs/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ wit_bindgen::generate!({
2525
"../wasi-tls/wit/deps/tls",
2626
],
2727
world: "wasmtime:test/test",
28-
features: ["cli-exit-with-code", "tls"],
28+
features: ["cli-exit-with-code", "tls", "clocks-timezone"],
2929
generate_all,
3030
});
3131

@@ -48,6 +48,7 @@ pub mod proxy {
4848
"wasi:clocks/[email protected]": crate::wasi::clocks::monotonic_clock,
4949
"wasi:clocks/[email protected]": crate::wasi::clocks::wall_clock,
5050
},
51+
features: ["clocks-timezone"],
5152
});
5253
}
5354

crates/wasi-preview1-component-adapter/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ pub mod bindings {
6767
"poll",
6868
"[method]outgoing-datagram-stream.send",
6969
],
70+
features: ["clocks-timezone"],
7071
generate_all,
7172
disable_custom_section_link_helpers: true,
7273
});
@@ -90,6 +91,7 @@ pub mod bindings {
9091
],
9192
generate_all,
9293
disable_custom_section_link_helpers: true,
94+
features: ["clocks-timezone"],
9395
});
9496

9597
#[cfg(feature = "proxy")]
@@ -111,6 +113,7 @@ pub mod bindings {
111113
raw_strings,
112114
runtime_path: "crate::bindings::wit_bindgen_rt_shim",
113115
skip: ["poll", "[method]outgoing-datagram-stream.send"],
116+
features: ["clocks-timezone"],
114117
generate_all,
115118
disable_custom_section_link_helpers: true,
116119
});

crates/wasi/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ cap-rand = { workspace = true }
2929
cap-fs-ext = { workspace = true }
3030
cap-net-ext = { workspace = true }
3131
cap-time-ext = { workspace = true }
32+
chrono = { workspace = true }
33+
chrono-tz = { workspace = true }
3234
io-lifetimes = { workspace = true }
3335
fs-set-times = { workspace = true }
3436
bitflags = { workspace = true }

crates/wasi/src/clocks.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
use crate::p2::bindings::clocks::timezone::TimezoneDisplay;
12
use cap_std::time::{Duration, Instant, SystemClock, SystemTime};
23
use cap_std::{AmbientAuthority, ambient_authority};
34
use cap_time_ext::{MonotonicClockExt as _, SystemClockExt as _};
5+
use chrono::{DateTime, TimeZone};
6+
use chrono_tz::{OffsetComponents, TZ_VARIANTS, Tz};
47
use wasmtime::component::{HasData, ResourceTable};
58

69
pub(crate) struct WasiClocks;
@@ -12,13 +15,15 @@ impl HasData for WasiClocks {
1215
pub struct WasiClocksCtx {
1316
pub wall_clock: Box<dyn HostWallClock + Send>,
1417
pub monotonic_clock: Box<dyn HostMonotonicClock + Send>,
18+
pub timezone: Box<dyn HostTimezone + Send>,
1519
}
1620

1721
impl Default for WasiClocksCtx {
1822
fn default() -> Self {
1923
Self {
2024
wall_clock: wall_clock(),
2125
monotonic_clock: monotonic_clock(),
26+
timezone: timezone(),
2227
}
2328
}
2429
}
@@ -42,6 +47,11 @@ pub trait HostMonotonicClock: Send {
4247
fn now(&self) -> u64;
4348
}
4449

50+
pub trait HostTimezone: Send {
51+
fn display(&self, datetime: Duration) -> TimezoneDisplay;
52+
fn utc_offset(&self, datetime: Duration) -> i32;
53+
}
54+
4555
pub struct WallClock {
4656
/// The underlying system clock.
4757
clock: cap_std::time::SystemClock,
@@ -123,6 +133,10 @@ pub fn wall_clock() -> Box<dyn HostWallClock + Send> {
123133
Box::new(WallClock::default())
124134
}
125135

136+
pub fn timezone() -> Box<dyn HostTimezone + Send> {
137+
Box::new(Timezone::default())
138+
}
139+
126140
pub(crate) struct Datetime {
127141
pub seconds: u64,
128142
pub nanoseconds: u32,
@@ -141,3 +155,56 @@ impl TryFrom<SystemTime> for Datetime {
141155
})
142156
}
143157
}
158+
159+
pub struct Timezone {
160+
// The underlying system timezone.
161+
timezone: cap_time_ext::Timezone,
162+
}
163+
164+
impl Default for Timezone {
165+
fn default() -> Self {
166+
Self::new(ambient_authority())
167+
}
168+
}
169+
170+
impl Timezone {
171+
pub fn new(ambient_authority: AmbientAuthority) -> Self {
172+
Self {
173+
timezone: cap_time_ext::Timezone::new(ambient_authority),
174+
}
175+
}
176+
177+
fn timezone_from_duration(&self, datetime: Duration) -> Option<TimezoneDisplay> {
178+
let name = self.timezone.timezone_name().ok()?;
179+
let tz: Tz = TZ_VARIANTS.into_iter().find(|tz| tz.to_string() == name)?;
180+
let naive_datetime = DateTime::from_timestamp(datetime.as_secs() as i64, 0)?.naive_utc();
181+
let tz_offset = tz.offset_from_local_datetime(&naive_datetime).single()?;
182+
let utc_offset = tz_offset.base_utc_offset().num_hours() as i32;
183+
let in_daylight_saving_time = !tz_offset.dst_offset().is_zero();
184+
Some(TimezoneDisplay {
185+
utc_offset,
186+
name,
187+
in_daylight_saving_time,
188+
})
189+
}
190+
}
191+
192+
impl HostTimezone for Timezone {
193+
fn display(&self, datetime: Duration) -> TimezoneDisplay {
194+
match self.timezone_from_duration(datetime) {
195+
None => TimezoneDisplay {
196+
utc_offset: 0,
197+
name: "UTC".to_string(),
198+
in_daylight_saving_time: false,
199+
},
200+
Some(timezone_display) => timezone_display,
201+
}
202+
}
203+
204+
fn utc_offset(&self, datetime: Duration) -> i32 {
205+
match self.timezone_from_duration(datetime) {
206+
None => 0,
207+
Some(timezone_display) => timezone_display.utc_offset,
208+
}
209+
}
210+
}

crates/wasi/src/ctx.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::cli::{StdinStream, StdoutStream, WasiCliCtx};
2-
use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx};
2+
use crate::clocks::{HostMonotonicClock, HostTimezone, HostWallClock, WasiClocksCtx};
33
use crate::filesystem::{Dir, WasiFilesystemCtx};
44
use crate::random::WasiRandomCtx;
55
use crate::sockets::{SocketAddrCheck, SocketAddrUse, WasiSocketsCtx};
@@ -366,6 +366,14 @@ impl WasiCtxBuilder {
366366
self
367367
}
368368

369+
/// Configures `wasi:clocks/timezone` to use the `clock` specified.
370+
///
371+
/// By default the host's timezone is used.
372+
pub fn timezone(&mut self, clock: impl HostTimezone + 'static) -> &mut Self {
373+
self.clocks.timezone = Box::new(clock);
374+
self
375+
}
376+
369377
/// Allow all network addresses accessible to the host.
370378
///
371379
/// This method will inherit all network addresses meaning that any address

crates/wasi/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub mod runtime;
3232
pub mod sockets;
3333
mod view;
3434

35-
pub use self::clocks::{HostMonotonicClock, HostWallClock};
35+
pub use self::clocks::{HostMonotonicClock, HostTimezone, HostWallClock};
3636
pub use self::ctx::{WasiCtx, WasiCtxBuilder};
3737
pub use self::error::{I32Exit, TrappableError};
3838
pub use self::filesystem::{DirPerms, FilePerms, OpenMode};

0 commit comments

Comments
 (0)