From 6a4dfd46ba57bbb4bdabd45751790ac40af8e792 Mon Sep 17 00:00:00 2001
From: scuzqy <80660355+scuzqy@users.noreply.github.com>
Date: Sun, 1 Mar 2026 10:47:30 +0800
Subject: [PATCH] time_format: Add Windows implementation (#50227)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Release Notes:
- Date and time formatting on Windows now respects the system time
formatting preferences.
---------
Co-authored-by: John Tur
---
Cargo.lock | 1 +
Cargo.toml | 2 +
crates/etw_tracing/Cargo.toml | 8 +-
crates/time_format/Cargo.toml | 3 +
crates/time_format/src/time_format.rs | 115 ++++++++++++++++++++++----
5 files changed, 108 insertions(+), 21 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index c28d777c3f4723fc5a498e3047de759e711dafad..18fd93aed13bebee782d4204bfbf095e750d7096 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17527,6 +17527,7 @@ dependencies = [
"core-foundation-sys",
"sys-locale",
"time",
+ "windows 0.61.3",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 148a909ccc8edb8f37ea7fd992ea6464c46ce0d5..39d331fd9ebde7ac0b861b6bf7dfc2ad28805c10 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -783,11 +783,13 @@ zstd = "0.11"
version = "0.61"
features = [
"Foundation_Numerics",
+ "Globalization_DateTimeFormatting",
"Storage_Search",
"Storage_Streams",
"System_Threading",
"UI_ViewManagement",
"Wdk_System_SystemServices",
+ "Win32_Foundation",
"Win32_Globalization",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D11",
diff --git a/crates/etw_tracing/Cargo.toml b/crates/etw_tracing/Cargo.toml
index 7f287307bc90e4462257fbeae8d5716dc5056ee7..c46e3b820a950f30f991f7de3dd27510db8825f8 100644
--- a/crates/etw_tracing/Cargo.toml
+++ b/crates/etw_tracing/Cargo.toml
@@ -21,10 +21,4 @@ workspace.workspace = true
[target.'cfg(target_os = "windows")'.dependencies]
wprcontrol = { git = "https://github.com/zed-industries/wprcontrol", rev = "cd811f7" }
windows-core = "0.61"
-windows = { workspace = true, features = [
- "Win32_Foundation",
- "Win32_System_Com",
- "Win32_System_Ole",
- "Win32_System_Variant",
- "Win32_UI_Shell",
-] }
+windows.workspace = true
diff --git a/crates/time_format/Cargo.toml b/crates/time_format/Cargo.toml
index b598d19887e128a0c5951c1d1bd5ec42f27f975b..7f5f2d9f1b56666036816d43bfa3564bf9721f05 100644
--- a/crates/time_format/Cargo.toml
+++ b/crates/time_format/Cargo.toml
@@ -19,3 +19,6 @@ time.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation.workspace = true
core-foundation-sys.workspace = true
+
+[target.'cfg(target_os = "windows")'.dependencies]
+windows.workspace = true
diff --git a/crates/time_format/src/time_format.rs b/crates/time_format/src/time_format.rs
index 25a7ae84232b69570e8c800c5955e684a13dc08a..bbf214623eb4b5b9dd978a675551c25f5e937a8d 100644
--- a/crates/time_format/src/time_format.rs
+++ b/crates/time_format/src/time_format.rs
@@ -86,10 +86,25 @@ fn format_absolute_date(
macos::format_date(×tamp)
}
}
- #[cfg(not(target_os = "macos"))]
+ #[cfg(target_os = "windows")]
+ {
+ if !enhanced_date_formatting {
+ return windows::format_date(×tamp);
+ }
+
+ let timestamp_date = timestamp.date();
+ let reference_date = reference.date();
+ if timestamp_date == reference_date {
+ "Today".to_string()
+ } else if reference_date.previous_day() == Some(timestamp_date) {
+ "Yesterday".to_string()
+ } else {
+ windows::format_date(×tamp)
+ }
+ }
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
// todo(linux) respect user's date/time preferences
- // todo(windows) respect user's date/time preferences
let current_locale = CURRENT_LOCALE
.get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
format_timestamp_naive_date(
@@ -105,10 +120,13 @@ fn format_absolute_time(timestamp: OffsetDateTime) -> String {
{
macos::format_time(×tamp)
}
- #[cfg(not(target_os = "macos"))]
+ #[cfg(target_os = "windows")]
+ {
+ windows::format_time(×tamp)
+ }
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
// todo(linux) respect user's date/time preferences
- // todo(windows) respect user's date/time preferences
let current_locale = CURRENT_LOCALE
.get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
format_timestamp_naive_time(
@@ -123,7 +141,7 @@ fn format_absolute_timestamp(
reference: OffsetDateTime,
#[allow(unused_variables)] enhanced_date_formatting: bool,
) -> String {
- #[cfg(target_os = "macos")]
+ #[cfg(any(target_os = "macos", target_os = "windows"))]
{
if !enhanced_date_formatting {
return format!(
@@ -147,10 +165,9 @@ fn format_absolute_timestamp(
)
}
}
- #[cfg(not(target_os = "macos"))]
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
// todo(linux) respect user's date/time preferences
- // todo(windows) respect user's date/time preferences
format_timestamp_fallback(timestamp, reference)
}
}
@@ -176,10 +193,25 @@ fn format_absolute_date_medium(
macos::format_date_medium(×tamp)
}
}
- #[cfg(not(target_os = "macos"))]
+ #[cfg(target_os = "windows")]
+ {
+ if !enhanced_formatting {
+ return windows::format_date_medium(×tamp);
+ }
+
+ let timestamp_date = timestamp.date();
+ let reference_date = reference.date();
+ if timestamp_date == reference_date {
+ "Today".to_string()
+ } else if reference_date.previous_day() == Some(timestamp_date) {
+ "Yesterday".to_string()
+ } else {
+ windows::format_date_medium(×tamp)
+ }
+ }
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
// todo(linux) respect user's date/time preferences
- // todo(windows) respect user's date/time preferences
let current_locale = CURRENT_LOCALE
.get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
if !enhanced_formatting {
@@ -212,7 +244,11 @@ fn format_absolute_timestamp_medium(
{
format_absolute_date_medium(timestamp, reference, false)
}
- #[cfg(not(target_os = "macos"))]
+ #[cfg(target_os = "windows")]
+ {
+ format_absolute_date_medium(timestamp, reference, false)
+ }
+ #[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
// todo(linux) respect user's date/time preferences
// todo(windows) respect user's date/time preferences
@@ -360,7 +396,7 @@ fn format_timestamp_naive_date(
}
}
-#[cfg(not(target_os = "macos"))]
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn format_timestamp_naive_date_medium(
timestamp_local: OffsetDateTime,
is_12_hour_time: bool,
@@ -415,10 +451,10 @@ pub fn format_timestamp_naive(
}
}
-#[cfg(not(target_os = "macos"))]
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
static CURRENT_LOCALE: std::sync::OnceLock = std::sync::OnceLock::new();
-#[cfg(not(target_os = "macos"))]
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn format_timestamp_fallback(timestamp: OffsetDateTime, reference: OffsetDateTime) -> String {
let current_locale = CURRENT_LOCALE
.get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
@@ -428,7 +464,7 @@ fn format_timestamp_fallback(timestamp: OffsetDateTime, reference: OffsetDateTim
}
/// Returns `true` if the locale is recognized as a 12-hour time locale.
-#[cfg(not(target_os = "macos"))]
+#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn is_12_hour_time_by_locale(locale: &str) -> bool {
[
"es-MX", "es-CO", "es-SV", "es-NI",
@@ -522,6 +558,57 @@ mod macos {
}
}
+#[cfg(target_os = "windows")]
+mod windows {
+ use windows::Globalization::DateTimeFormatting::DateTimeFormatter;
+
+ pub fn format_time(timestamp: &time::OffsetDateTime) -> String {
+ format_with_formatter(DateTimeFormatter::ShortTime(), timestamp, true)
+ }
+
+ pub fn format_date(timestamp: &time::OffsetDateTime) -> String {
+ format_with_formatter(DateTimeFormatter::ShortDate(), timestamp, false)
+ }
+
+ pub fn format_date_medium(timestamp: &time::OffsetDateTime) -> String {
+ format_with_formatter(
+ DateTimeFormatter::CreateDateTimeFormatter(windows::core::h!(
+ "month.abbreviated day year.full"
+ )),
+ timestamp,
+ false,
+ )
+ }
+
+ fn format_with_formatter(
+ formatter: windows::core::Result,
+ timestamp: &time::OffsetDateTime,
+ is_time: bool,
+ ) -> String {
+ formatter
+ .and_then(|formatter| formatter.Format(to_winrt_datetime(timestamp)))
+ .map(|hstring| hstring.to_string())
+ .unwrap_or_else(|_| {
+ if is_time {
+ super::format_timestamp_naive_time(*timestamp, true)
+ } else {
+ super::format_timestamp_naive_date(*timestamp, *timestamp, true)
+ }
+ })
+ }
+
+ fn to_winrt_datetime(timestamp: &time::OffsetDateTime) -> windows::Foundation::DateTime {
+ // DateTime uses 100-nanosecond intervals since January 1, 1601 (UTC).
+ const WINDOWS_EPOCH: time::OffsetDateTime = time::macros::datetime!(1601-01-01 0:00 UTC);
+ let duration_since_winrt_epoch = *timestamp - WINDOWS_EPOCH;
+ let universal_time = duration_since_winrt_epoch.whole_nanoseconds() / 100;
+
+ windows::Foundation::DateTime {
+ UniversalTime: universal_time as i64,
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;