Skip to content

Commit 0969bf7

Browse files
authored
feat(tick): implement display extension for SystemTime (#169)
I found multiple cases when the consumer wants to display `SystemTime` but currently there is no way to do it easily. One can construct `Iso8601` from SystemTime manually that implements the `Display` but it's too verbose. This PR adds an `display` extension to simplify the scenario. To fulfill the format requirements we do not fail of `SystemTime` is out of range, but rather display values saturated at boundaries `-009999-01-02T01:59:59Z` and `9999-12-30T22:00:00.999999999Z`. This is because we are limited by the jiff MIN/MAX values. Before: ```rust println!("Current time: {}", Iso8601::try_from(clock.system_time()).unwrap_or(Iso8601::MAX)); ``` After: ```rust println!("Current time: {}", clock.system_time().display_iso_8601()); ```
1 parent af97b42 commit 0969bf7

File tree

10 files changed

+163
-43
lines changed

10 files changed

+163
-43
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ testing_aids = { path = "crates/testing_aids", default-features = false }
3737
thread_aware = { path = "crates/thread_aware", default-features = false, version = "0.6.0" }
3838
thread_aware_macros = { path = "crates/thread_aware_macros", default-features = false, version = "0.6.0" }
3939
thread_aware_macros_impl = { path = "crates/thread_aware_macros_impl", default-features = false, version = "0.6.0" }
40-
tick = { path = "crates/tick", default-features = false, version = "0.1.1" }
40+
tick = { path = "crates/tick", default-features = false, version = "0.1.2" }
4141

4242
# external dependencies
4343
alloc_tracker = { version = "0.5.9", default-features = false }

constants.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# Rust toolchain versions
77
RUST_MSRV = "1.88" # used for testing, ensures the MSRV promise is kept; must match Cargo.toml [workspace.package].rust-version
88
RUST_LATEST = "1.92" # used for static analysis & mutation testing; must match rust-toolchain.toml
9-
RUST_NIGHTLY = "nightly-2025-12-20" # used for coverage and extended analysis; update on a regular basis
9+
RUST_NIGHTLY = "nightly-2025-12-29" # used for coverage and extended analysis; update on a regular basis
1010
RUST_NIGHTLY_EXTERNAL_TYPES = "nightly-2025-10-18" # used for external type exposure checks; update alongside updates to cargo-check-external-types
1111

1212
# Cargo tools

crates/tick/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## [0.1.2] - 2026-01-05
4+
5+
- ✨ Features
6+
7+
- implement display extension for SystemTime
8+
39
## [0.1.1] - 2025-12-29
410

511
- 🐛 Bug Fixes

crates/tick/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[package]
55
name = "tick"
66
description = "Provides primitives to interact with and manipulate machine time."
7-
version = "0.1.1"
7+
version = "0.1.2"
88
readme = "README.md"
99
keywords = ["time", "clock", "tick", "stopwatch"]
1010
categories = ["data-structures"]

crates/tick/README.md

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,16 @@ in production and tests, with zero runtime overhead when `test-util` is disabled
7575
* [`Stopwatch`][__link4] - Measures elapsed time.
7676
* [`Delay`][__link5] - Delays the execution for a specified duration.
7777
* [`PeriodicTimer`][__link6] - Schedules a task to run periodically.
78-
* [`FutureExt`][__link7] - Extensions for the `Future` trait.
79-
* [`Error`][__link8] - Represents an error that can occur when working with time. Provides limited
78+
* [`Error`][__link7] - Represents an error that can occur when working with time. Provides limited
8079
introspection capabilities.
81-
* [`fmt`][__link9] - Utilities for formatting `SystemTime` into various formats. Available when
80+
* [`fmt`][__link8] - Utilities for formatting `SystemTime` into various formats. Available when
8281
the `fmt` feature is enabled.
83-
* [`runtime`][__link10] - Infrastructure for integrating time primitives into async runtimes.
82+
* [`runtime`][__link9] - Infrastructure for integrating time primitives into async runtimes.
83+
84+
## Extensions
85+
86+
* [`FutureExt`][__link10] - Extensions for the `Future` trait, providing timeout functionality.
87+
* [`SystemTimeExt`][__link11] - Extensions for [`SystemTime`][__link12].
8488

8589
## Machine-Centric vs. Human-Centric Time
8690

@@ -94,7 +98,7 @@ When working with time, two different use cases are considered:
9498
Dealing with human-centric time involves significant ambiguity.
9599

96100
This crate is designed for machine-centric time. For human-centric time manipulation,
97-
consider using other crates such as [jiff][__link11], [chrono][__link12], or [time][__link13]. The time primitives in
101+
consider using other crates such as [jiff][__link13], [chrono][__link14], or [time][__link15]. The time primitives in
98102
this crate are designed for easy interoperability with these crates. See the `time_interop*`
99103
examples for more details.
100104

@@ -110,7 +114,7 @@ type, which is exposed when the `test-util` feature is enabled.
110114

111115
### Use `Clock` to retrieve absolute time
112116

113-
The clock provides absolute time as `SystemTime`. See [`Clock`][__link14] documentation for detailed
117+
The clock provides absolute time as `SystemTime`. See [`Clock`][__link16] documentation for detailed
114118
information.
115119

116120
```rust
@@ -129,7 +133,7 @@ assert!(time1 <= time2);
129133

130134
### Use `Clock` to retrieve relative time
131135

132-
The clock provides relative time via [`Clock::instant`][__link15] and [`Stopwatch`][__link16].
136+
The clock provides relative time via [`Clock::instant`][__link17] and [`Stopwatch`][__link18].
133137

134138
```rust
135139
use std::time::{Duration, Instant};
@@ -184,18 +188,18 @@ timer
184188

185189
This crate provides several optional features that can be enabled in your `Cargo.toml`:
186190

187-
* **`tokio`** - Integration with the [Tokio][__link17] runtime. Enables
188-
[`Clock::new_tokio`][__link18] for creating clocks that use Tokio’s time facilities.
189-
* **`test-util`** - Enables the [`ClockControl`][__link19] type for controlling the passage of time
191+
* **`tokio`** - Integration with the [Tokio][__link19] runtime. Enables
192+
[`Clock::new_tokio`][__link20] for creating clocks that use Tokio’s time facilities.
193+
* **`test-util`** - Enables the [`ClockControl`][__link21] type for controlling the passage of time
190194
in tests. This allows you to pause time, advance it manually, or automatically advance
191195
timers for fast, deterministic testing. **Only enable this in `dev-dependencies`.**
192-
* **`serde`** - Adds serialization and deserialization support via [serde][__link20].
193-
* **`fmt`** - Enables the [`fmt`][__link21] module with utilities for formatting `SystemTime` into
196+
* **`serde`** - Adds serialization and deserialization support via [serde][__link22].
197+
* **`fmt`** - Enables the [`fmt`][__link23] module with utilities for formatting `SystemTime` into
194198
various formats (e.g., ISO 8601, RFC 2822).
195199

196200
## Additional Examples
197201

198-
The [time examples][__link22]
202+
The [time examples][__link24]
199203
contain additional examples of how to use the time primitives.
200204

201205

@@ -204,27 +208,29 @@ contain additional examples of how to use the time primitives.
204208
This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Browse this crate's <a href="https://github.com/microsoft/oxidizer/tree/main/crates/tick">source code</a>.
205209
</sub>
206210

207-
[__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEGye19xWMtftNG5SBR1lSQd0HG1O5g9a9CyQOG5sh2LWD3CBTYWSBgmR0aWNrZTAuMS4x
208-
[__link0]: https://docs.rs/tick/0.1.1/tick/?search=ClockControl
209-
[__link1]: https://docs.rs/tick/0.1.1/tick/?search=Clock
210-
[__link10]: https://docs.rs/tick/0.1.1/tick/runtime/index.html
211-
[__link11]: https://crates.io/crates/jiff
212-
[__link12]: https://crates.io/crates/chrono
213-
[__link13]: https://crates.io/crates/time
214-
[__link14]: https://docs.rs/tick/0.1.1/tick/?search=Clock
215-
[__link15]: https://docs.rs/tick/0.1.1/tick/?search=Clock::instant
216-
[__link16]: https://docs.rs/tick/0.1.1/tick/?search=Stopwatch
217-
[__link17]: https://tokio.rs/
218-
[__link18]: https://docs.rs/tick/0.1.1/tick/?search=Clock::new_tokio
219-
[__link19]: https://docs.rs/tick/0.1.1/tick/?search=ClockControl
220-
[__link2]: https://docs.rs/tick/0.1.1/tick/?search=Clock
221-
[__link20]: https://serde.rs/
222-
[__link21]: https://docs.rs/tick/0.1.1/tick/fmt/index.html
223-
[__link22]: https://github.com/microsoft/oxidizer/tree/main/crates/tick/examples
224-
[__link3]: https://docs.rs/tick/0.1.1/tick/?search=ClockControl
225-
[__link4]: https://docs.rs/tick/0.1.1/tick/?search=Stopwatch
226-
[__link5]: https://docs.rs/tick/0.1.1/tick/?search=Delay
227-
[__link6]: https://docs.rs/tick/0.1.1/tick/?search=PeriodicTimer
228-
[__link7]: https://docs.rs/tick/0.1.1/tick/?search=FutureExt
229-
[__link8]: https://docs.rs/tick/0.1.1/tick/?search=Error
230-
[__link9]: https://docs.rs/tick/0.1.1/tick/fmt/index.html
211+
[__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEG7RNcoP3blXnG4CHqkinKPcBG0KvcQDgsaxXGxhnbuGo0h6JYWSBgmR0aWNrZTAuMS4y
212+
[__link0]: https://docs.rs/tick/0.1.2/tick/?search=ClockControl
213+
[__link1]: https://docs.rs/tick/0.1.2/tick/?search=Clock
214+
[__link10]: https://docs.rs/tick/0.1.2/tick/?search=FutureExt
215+
[__link11]: https://docs.rs/tick/0.1.2/tick/?search=SystemTimeExt
216+
[__link12]: https://doc.rust-lang.org/stable/std/?search=time::SystemTime
217+
[__link13]: https://crates.io/crates/jiff
218+
[__link14]: https://crates.io/crates/chrono
219+
[__link15]: https://crates.io/crates/time
220+
[__link16]: https://docs.rs/tick/0.1.2/tick/?search=Clock
221+
[__link17]: https://docs.rs/tick/0.1.2/tick/?search=Clock::instant
222+
[__link18]: https://docs.rs/tick/0.1.2/tick/?search=Stopwatch
223+
[__link19]: https://tokio.rs/
224+
[__link2]: https://docs.rs/tick/0.1.2/tick/?search=Clock
225+
[__link20]: https://docs.rs/tick/0.1.2/tick/?search=Clock::new_tokio
226+
[__link21]: https://docs.rs/tick/0.1.2/tick/?search=ClockControl
227+
[__link22]: https://serde.rs/
228+
[__link23]: https://docs.rs/tick/0.1.2/tick/fmt/index.html
229+
[__link24]: https://github.com/microsoft/oxidizer/tree/main/crates/tick/examples
230+
[__link3]: https://docs.rs/tick/0.1.2/tick/?search=ClockControl
231+
[__link4]: https://docs.rs/tick/0.1.2/tick/?search=Stopwatch
232+
[__link5]: https://docs.rs/tick/0.1.2/tick/?search=Delay
233+
[__link6]: https://docs.rs/tick/0.1.2/tick/?search=PeriodicTimer
234+
[__link7]: https://docs.rs/tick/0.1.2/tick/?search=Error
235+
[__link8]: https://docs.rs/tick/0.1.2/tick/fmt/index.html
236+
[__link9]: https://docs.rs/tick/0.1.2/tick/runtime/index.html

crates/tick/src/fmt/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
//!
2828
//! # Examples
2929
//!
30+
//! ## Using format types
31+
//!
3032
//! ```
3133
//! use tick::fmt::{Iso8601, Rfc2822, UnixSeconds};
3234
//!
@@ -44,6 +46,17 @@
4446
//!
4547
//! # Ok::<(), Box<dyn std::error::Error>>(())
4648
//! ```
49+
//!
50+
//! ## Using `SystemTimeExt`
51+
//!
52+
//! ```
53+
//! use std::time::{Duration, SystemTime};
54+
//! use tick::SystemTimeExt;
55+
//!
56+
//! let time = SystemTime::UNIX_EPOCH + Duration::from_secs(3600);
57+
//! println!("Time: {}", time.display_iso_8601());
58+
//! // Output: Time: 1970-01-01T01:00:00Z
59+
//! ```
4760
4861
mod iso_8601;
4962
mod rfc_2822;

crates/tick/src/future_ext.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub trait FutureExt: Future {
3838
}
3939
}
4040

41-
impl<T> FutureExt for T where T: Future {}
41+
impl<T: Future> FutureExt for T {}
4242

4343
#[cfg_attr(coverage_nightly, coverage(off))]
4444
#[cfg(test)]

crates/tick/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,17 @@
7676
//! - [`Stopwatch`] - Measures elapsed time.
7777
//! - [`Delay`] - Delays the execution for a specified duration.
7878
//! - [`PeriodicTimer`] - Schedules a task to run periodically.
79-
//! - [`FutureExt`] - Extensions for the `Future` trait.
8079
//! - [`Error`] - Represents an error that can occur when working with time. Provides limited
8180
//! introspection capabilities.
8281
//! - [`fmt`] - Utilities for formatting `SystemTime` into various formats. Available when
8382
//! the `fmt` feature is enabled.
8483
//! - [`runtime`] - Infrastructure for integrating time primitives into async runtimes.
8584
//!
85+
//! # Extensions
86+
//!
87+
//! - [`FutureExt`] - Extensions for the `Future` trait, providing timeout functionality.
88+
//! - [`SystemTimeExt`] - Extensions for [`SystemTime`][`std::time::SystemTime`].
89+
//!
8690
//! # Machine-Centric vs. Human-Centric Time
8791
//!
8892
//! When working with time, two different use cases are considered:
@@ -223,6 +227,7 @@ mod future_ext;
223227
mod periodic_timer;
224228
mod state;
225229
mod stopwatch;
230+
mod system_time_ext;
226231
mod timers;
227232

228233
pub mod runtime;
@@ -235,4 +240,5 @@ pub use error::{Error, Result};
235240
pub use future_ext::FutureExt;
236241
pub use periodic_timer::PeriodicTimer;
237242
pub use stopwatch::Stopwatch;
243+
pub use system_time_ext::SystemTimeExt;
238244
pub use timeout::Timeout;

crates/tick/src/system_time_ext.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use std::time::SystemTime;
5+
6+
/// Extension trait for [`SystemTime`] that provides formatting capabilities.
7+
pub trait SystemTimeExt: sealed::Sealed {
8+
/// Returns a value that formats the [`SystemTime`] in ISO 8601 format.
9+
///
10+
/// Times outside the valid range (before year -9999 or after year 9999) are saturated
11+
/// to the nearest boundary.
12+
///
13+
/// # Examples
14+
///
15+
/// ```
16+
/// use std::time::{Duration, SystemTime};
17+
/// use tick::SystemTimeExt;
18+
///
19+
/// let time = SystemTime::UNIX_EPOCH + Duration::from_secs(3600);
20+
/// assert_eq!(time.display_iso_8601().to_string(), "1970-01-01T01:00:00Z");
21+
/// ```
22+
#[cfg(any(feature = "fmt", test))]
23+
fn display_iso_8601(&self) -> impl std::fmt::Display;
24+
}
25+
26+
impl SystemTimeExt for SystemTime {
27+
#[cfg(any(feature = "fmt", test))]
28+
fn display_iso_8601(&self) -> impl std::fmt::Display {
29+
// jiff's Timestamp implements Display that outputs ISO 8601 format
30+
to_timestamp(*self)
31+
}
32+
}
33+
34+
#[cfg(any(feature = "fmt", test))]
35+
fn to_timestamp(system_time: SystemTime) -> jiff::Timestamp {
36+
match jiff::Timestamp::try_from(system_time) {
37+
Ok(timestamp) => timestamp,
38+
Err(_) => to_timestamp_min_max(system_time),
39+
}
40+
}
41+
42+
#[cfg(any(feature = "fmt", test))]
43+
fn to_timestamp_min_max(system_time: SystemTime) -> jiff::Timestamp {
44+
if system_time.duration_since(SystemTime::UNIX_EPOCH).is_ok() {
45+
jiff::Timestamp::MAX
46+
} else {
47+
jiff::Timestamp::MIN
48+
}
49+
}
50+
51+
mod sealed {
52+
pub trait Sealed {}
53+
impl Sealed for std::time::SystemTime {}
54+
}
55+
56+
#[cfg_attr(coverage_nightly, coverage(off))]
57+
#[cfg(test)]
58+
mod tests {
59+
use std::time::Duration;
60+
61+
use jiff::Timestamp;
62+
63+
use super::*;
64+
65+
#[test]
66+
fn display_ok() {
67+
assert_eq!(SystemTime::UNIX_EPOCH.display_iso_8601().to_string(), "1970-01-01T00:00:00Z");
68+
69+
assert_eq!(
70+
(SystemTime::UNIX_EPOCH + Duration::from_secs(3600)).display_iso_8601().to_string(),
71+
"1970-01-01T01:00:00Z"
72+
);
73+
}
74+
75+
#[test]
76+
fn display_out_of_range() {
77+
let time = SystemTime::from(Timestamp::MAX) + Duration::from_secs(12345);
78+
assert_eq!(time.display_iso_8601().to_string(), "9999-12-30T22:00:00.999999999Z");
79+
}
80+
81+
#[test]
82+
fn to_timestamp_fallback_ok() {
83+
let now = SystemTime::UNIX_EPOCH + Duration::from_secs(12345);
84+
assert_eq!(to_timestamp_min_max(now), jiff::Timestamp::MAX);
85+
86+
let past = SystemTime::UNIX_EPOCH - Duration::from_secs(12345);
87+
assert_eq!(to_timestamp_min_max(past), jiff::Timestamp::MIN);
88+
}
89+
}

0 commit comments

Comments
 (0)