time_format: Add Windows implementation (#50227)

<img width="827" height="643" alt="图片"
src="https://github.com/user-attachments/assets/de6279fa-c224-460d-8210-3eada416aca5"
/>


Release Notes:

- Date and time formatting on Windows now respects the system time
formatting preferences.

---------

Co-authored-by: John Tur <john-tur@outlook.com>
This commit is contained in:
scuzqy
2026-03-01 10:47:30 +08:00
committed by GitHub
parent 4668aeb728
commit 6a4dfd46ba
5 changed files with 108 additions and 21 deletions
Generated
+1
View File
@@ -17527,6 +17527,7 @@ dependencies = [
"core-foundation-sys",
"sys-locale",
"time",
"windows 0.61.3",
]
[[package]]
+2
View File
@@ -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",
+1 -7
View File
@@ -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
+3
View File
@@ -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
+101 -14
View File
@@ -86,10 +86,25 @@ fn format_absolute_date(
macos::format_date(&timestamp)
}
}
#[cfg(not(target_os = "macos"))]
#[cfg(target_os = "windows")]
{
if !enhanced_date_formatting {
return windows::format_date(&timestamp);
}
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(&timestamp)
}
}
#[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(&timestamp)
}
#[cfg(not(target_os = "macos"))]
#[cfg(target_os = "windows")]
{
windows::format_time(&timestamp)
}
#[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(&timestamp)
}
}
#[cfg(not(target_os = "macos"))]
#[cfg(target_os = "windows")]
{
if !enhanced_formatting {
return windows::format_date_medium(&timestamp);
}
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(&timestamp)
}
}
#[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<String> = 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<DateTimeFormatter>,
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::*;