mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
date: add ICU support for day/months names (#10457)
Fixes: https://bugs.launchpad.net/ubuntu/+source/rust-coreutils/+bug/2130859
This commit is contained in:
Generated
+139
@@ -272,6 +272,16 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "calendrical_calculations"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
"displaydoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.52"
|
||||
@@ -514,6 +524,15 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "core_maths"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreutils"
|
||||
version = "0.6.0"
|
||||
@@ -1348,6 +1367,29 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_calendar"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e"
|
||||
dependencies = [
|
||||
"calendrical_calculations",
|
||||
"displaydoc",
|
||||
"icu_calendar_data",
|
||||
"icu_locale",
|
||||
"icu_locale_core",
|
||||
"icu_provider",
|
||||
"ixdtf",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_calendar_data"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d"
|
||||
|
||||
[[package]]
|
||||
name = "icu_collator"
|
||||
version = "2.1.1"
|
||||
@@ -1386,6 +1428,35 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_datetime"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9d49f41ded8e63761b6b4c3120dfdc289415a1ed10107db6198eb311057ca5"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"fixed_decimal",
|
||||
"icu_calendar",
|
||||
"icu_datetime_data",
|
||||
"icu_decimal",
|
||||
"icu_locale",
|
||||
"icu_locale_core",
|
||||
"icu_pattern",
|
||||
"icu_plurals",
|
||||
"icu_provider",
|
||||
"icu_time",
|
||||
"potential_utf",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_datetime_data"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46597233625417b7c8052a63d916e4fdc73df21614ac0b679492a5d6e3b01aeb"
|
||||
|
||||
[[package]]
|
||||
name = "icu_decimal"
|
||||
version = "2.1.1"
|
||||
@@ -1465,6 +1536,38 @@ version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
|
||||
|
||||
[[package]]
|
||||
name = "icu_pattern"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a7ff8c0ff6f61cdce299dcb54f557b0a251adbc78f6f0c35a21332c452b4a1b"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"either",
|
||||
"serde",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_plurals"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f9cfe49f5b1d1163cc58db451562339916a9ca5cbcaae83924d41a0bf839474"
|
||||
dependencies = [
|
||||
"fixed_decimal",
|
||||
"icu_locale",
|
||||
"icu_plurals_data",
|
||||
"icu_provider",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_plurals_data"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f018a98dccf7f0eb02ba06ac0ff67d102d8ded80734724305e924de304e12ff0"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.1.2"
|
||||
@@ -1502,6 +1605,30 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_time"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8242b00da3b3b6678f731437a11c8833a43c821ae081eca60ba1b7579d45b6d8"
|
||||
dependencies = [
|
||||
"calendrical_calculations",
|
||||
"displaydoc",
|
||||
"icu_calendar",
|
||||
"icu_locale_core",
|
||||
"icu_provider",
|
||||
"icu_time_data",
|
||||
"ixdtf",
|
||||
"serde",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_time_data"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e10b0e5e87a2c84bd5fa407705732052edebe69291d347d0c3033785470edbf"
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
@@ -1600,6 +1727,12 @@ version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "ixdtf"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.18"
|
||||
@@ -3249,6 +3382,7 @@ dependencies = [
|
||||
"clap",
|
||||
"codspeed-divan-compat",
|
||||
"fluent",
|
||||
"icu_calendar",
|
||||
"jiff",
|
||||
"nix",
|
||||
"parse_datetime",
|
||||
@@ -4247,7 +4381,9 @@ dependencies = [
|
||||
"fluent-syntax",
|
||||
"glob",
|
||||
"hex",
|
||||
"icu_calendar",
|
||||
"icu_collator",
|
||||
"icu_datetime",
|
||||
"icu_decimal",
|
||||
"icu_locale",
|
||||
"icu_provider",
|
||||
@@ -4719,6 +4855,9 @@ name = "writeable"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
|
||||
@@ -329,7 +329,9 @@ gcd = "2.3"
|
||||
glob = "0.3.1"
|
||||
half = "2.4.1"
|
||||
hostname = "0.4"
|
||||
icu_calendar = "2.0.0"
|
||||
icu_collator = "2.0.0"
|
||||
icu_datetime = "2.0.0"
|
||||
icu_decimal = "2.0.0"
|
||||
icu_locale = "2.0.0"
|
||||
icu_provider = "2.0.0"
|
||||
|
||||
Generated
+138
@@ -194,6 +194,16 @@ version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "calendrical_calculations"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a0b39595c6ee54a8d0900204ba4c401d0ab4eb45adaf07178e8d017541529e7"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
"displaydoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.51"
|
||||
@@ -314,6 +324,15 @@ version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "core_maths"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
@@ -649,6 +668,29 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_calendar"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f0e52e009b6b16ba9c0693578796f2dd4aaa59a7f8f920423706714a89ac4e"
|
||||
dependencies = [
|
||||
"calendrical_calculations",
|
||||
"displaydoc",
|
||||
"icu_calendar_data",
|
||||
"icu_locale",
|
||||
"icu_locale_core",
|
||||
"icu_provider",
|
||||
"ixdtf",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_calendar_data"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527f04223b17edfe0bd43baf14a0cb1b017830db65f3950dc00224860a9a446d"
|
||||
|
||||
[[package]]
|
||||
name = "icu_collator"
|
||||
version = "2.1.1"
|
||||
@@ -687,6 +729,35 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_datetime"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9d49f41ded8e63761b6b4c3120dfdc289415a1ed10107db6198eb311057ca5"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"fixed_decimal",
|
||||
"icu_calendar",
|
||||
"icu_datetime_data",
|
||||
"icu_decimal",
|
||||
"icu_locale",
|
||||
"icu_locale_core",
|
||||
"icu_pattern",
|
||||
"icu_plurals",
|
||||
"icu_provider",
|
||||
"icu_time",
|
||||
"potential_utf",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_datetime_data"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46597233625417b7c8052a63d916e4fdc73df21614ac0b679492a5d6e3b01aeb"
|
||||
|
||||
[[package]]
|
||||
name = "icu_decimal"
|
||||
version = "2.1.1"
|
||||
@@ -766,6 +837,38 @@ version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
|
||||
|
||||
[[package]]
|
||||
name = "icu_pattern"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a7ff8c0ff6f61cdce299dcb54f557b0a251adbc78f6f0c35a21332c452b4a1b"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"either",
|
||||
"serde",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_plurals"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f9cfe49f5b1d1163cc58db451562339916a9ca5cbcaae83924d41a0bf839474"
|
||||
dependencies = [
|
||||
"fixed_decimal",
|
||||
"icu_locale",
|
||||
"icu_plurals_data",
|
||||
"icu_provider",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_plurals_data"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f018a98dccf7f0eb02ba06ac0ff67d102d8ded80734724305e924de304e12ff0"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.1.2"
|
||||
@@ -803,6 +906,30 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_time"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8242b00da3b3b6678f731437a11c8833a43c821ae081eca60ba1b7579d45b6d8"
|
||||
dependencies = [
|
||||
"calendrical_calculations",
|
||||
"displaydoc",
|
||||
"icu_calendar",
|
||||
"icu_locale_core",
|
||||
"icu_provider",
|
||||
"icu_time_data",
|
||||
"ixdtf",
|
||||
"serde",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_time_data"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e10b0e5e87a2c84bd5fa407705732052edebe69291d347d0c3033785470edbf"
|
||||
|
||||
[[package]]
|
||||
name = "intl-memoizer"
|
||||
version = "0.5.3"
|
||||
@@ -837,6 +964,12 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ixdtf"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84de9d95a6d2547d9b77ee3f25fa0ee32e3c3a6484d47a55adebc0439c077992"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.18"
|
||||
@@ -1745,7 +1878,9 @@ dependencies = [
|
||||
"fluent-syntax",
|
||||
"glob",
|
||||
"hex",
|
||||
"icu_calendar",
|
||||
"icu_collator",
|
||||
"icu_datetime",
|
||||
"icu_decimal",
|
||||
"icu_locale",
|
||||
"icu_provider",
|
||||
@@ -2073,6 +2208,9 @@ name = "writeable"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
|
||||
@@ -18,16 +18,20 @@ workspace = true
|
||||
[lib]
|
||||
path = "src/date.rs"
|
||||
|
||||
[features]
|
||||
i18n-datetime = ["uucore/i18n-datetime", "icu_calendar"]
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
fluent = { workspace = true }
|
||||
icu_calendar = { workspace = true, optional = true }
|
||||
jiff = { workspace = true, features = [
|
||||
"tzdb-bundle-platform",
|
||||
"tzdb-zoneinfo",
|
||||
"tzdb-concatenated",
|
||||
] }
|
||||
parse_datetime = { workspace = true }
|
||||
uucore = { workspace = true, features = ["parser"] }
|
||||
uucore = { workspace = true, features = ["parser", "i18n-datetime"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { workspace = true, features = ["time"] }
|
||||
|
||||
+106
-13
@@ -20,6 +20,9 @@ use std::sync::OnceLock;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::FromIo;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::i18n::datetime::{
|
||||
get_localized_day_name, get_localized_month_name, should_use_icu_locale,
|
||||
};
|
||||
use uucore::translate;
|
||||
use uucore::{format_usage, show};
|
||||
#[cfg(windows)]
|
||||
@@ -474,20 +477,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let config = Config::new().custom(PosixCustom::new()).lenient(true);
|
||||
for date in dates {
|
||||
match date {
|
||||
Ok(date) => {
|
||||
match BrokenDownTime::from(&date).to_string_with_config(&config, format_string) {
|
||||
Ok(s) => writeln!(stdout, "{s}").map_err(|e| {
|
||||
USimpleError::new(1, translate!("date-error-write", "error" => e))
|
||||
})?,
|
||||
Err(e) => {
|
||||
let _ = stdout.flush();
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("date-error-invalid-format", "format" => format_string, "error" => e),
|
||||
));
|
||||
}
|
||||
Ok(date) => match format_date_with_locale_aware_months(&date, format_string, &config) {
|
||||
Ok(s) => writeln!(stdout, "{s}").map_err(|e| {
|
||||
USimpleError::new(1, translate!("date-error-write", "error" => e))
|
||||
})?,
|
||||
Err(e) => {
|
||||
let _ = stdout.flush();
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
translate!("date-error-invalid-format", "format" => format_string, "error" => e),
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
Err((input, _err)) => {
|
||||
let _ = stdout.flush();
|
||||
show!(USimpleError::new(
|
||||
@@ -612,6 +613,98 @@ pub fn uu_app() -> Command {
|
||||
.arg(Arg::new(OPT_FORMAT).num_args(0..).trailing_var_arg(true))
|
||||
}
|
||||
|
||||
fn format_date_with_locale_aware_months(
|
||||
date: &Zoned,
|
||||
format_string: &str,
|
||||
config: &Config<PosixCustom>,
|
||||
) -> Result<String, jiff::Error> {
|
||||
// Only use ICU for non-default locales and when format string contains month or day specifiers
|
||||
let use_icu = should_use_icu_locale();
|
||||
|
||||
if (format_string.contains("%B")
|
||||
|| format_string.contains("%b")
|
||||
|| format_string.contains("%A")
|
||||
|| format_string.contains("%a"))
|
||||
&& use_icu
|
||||
{
|
||||
let broken_down = BrokenDownTime::from(date);
|
||||
// Get localized month names if needed
|
||||
let (full_month, abbrev_month) =
|
||||
if format_string.contains("%B") || format_string.contains("%b") {
|
||||
if let Some(month_val) = broken_down.month() {
|
||||
let month_u8 = if (1..=12).contains(&month_val) {
|
||||
month_val as u8
|
||||
} else {
|
||||
1 // fallback to January for invalid values
|
||||
};
|
||||
(
|
||||
get_localized_month_name(month_u8, true),
|
||||
get_localized_month_name(month_u8, false),
|
||||
)
|
||||
} else {
|
||||
(String::new(), String::new())
|
||||
}
|
||||
} else {
|
||||
(String::new(), String::new())
|
||||
};
|
||||
|
||||
// Get localized day names if needed
|
||||
let (full_day, abbrev_day) = if format_string.contains("%A") || format_string.contains("%a")
|
||||
{
|
||||
if let (Some(year), Some(month), Some(day)) =
|
||||
(broken_down.year(), broken_down.month(), broken_down.day())
|
||||
{
|
||||
(
|
||||
get_localized_day_name(year.into(), month as u8, day as u8, true),
|
||||
get_localized_day_name(year.into(), month as u8, day as u8, false),
|
||||
)
|
||||
} else {
|
||||
(String::new(), String::new())
|
||||
}
|
||||
} else {
|
||||
(String::new(), String::new())
|
||||
};
|
||||
|
||||
// Replace format specifiers with placeholders for successful ICU translations only
|
||||
let mut temp_format = format_string.to_string();
|
||||
if !full_month.is_empty() {
|
||||
temp_format = temp_format.replace("%B", "<<<FULL_MONTH>>>");
|
||||
}
|
||||
if !abbrev_month.is_empty() {
|
||||
temp_format = temp_format.replace("%b", "<<<ABBREV_MONTH>>>");
|
||||
}
|
||||
if !full_day.is_empty() {
|
||||
temp_format = temp_format.replace("%A", "<<<FULL_DAY>>>");
|
||||
}
|
||||
if !abbrev_day.is_empty() {
|
||||
temp_format = temp_format.replace("%a", "<<<ABBREV_DAY>>>");
|
||||
}
|
||||
|
||||
// Format with the temporary string
|
||||
let temp_result = broken_down.to_string_with_config(config, &temp_format)?;
|
||||
|
||||
// Replace placeholders with localized names
|
||||
let mut final_result = temp_result;
|
||||
if !full_month.is_empty() {
|
||||
final_result = final_result.replace("<<<FULL_MONTH>>>", &full_month);
|
||||
}
|
||||
if !abbrev_month.is_empty() {
|
||||
final_result = final_result.replace("<<<ABBREV_MONTH>>>", &abbrev_month);
|
||||
}
|
||||
if !full_day.is_empty() {
|
||||
final_result = final_result.replace("<<<FULL_DAY>>>", &full_day);
|
||||
}
|
||||
if !abbrev_day.is_empty() {
|
||||
final_result = final_result.replace("<<<ABBREV_DAY>>>", &abbrev_day);
|
||||
}
|
||||
|
||||
return Ok(final_result);
|
||||
}
|
||||
|
||||
// Fallback to regular formatting
|
||||
BrokenDownTime::from(date).to_string_with_config(config, format_string)
|
||||
}
|
||||
|
||||
/// Return the appropriate format string for the given settings.
|
||||
fn make_format_string(settings: &Settings) -> &str {
|
||||
match settings.format {
|
||||
|
||||
@@ -68,9 +68,15 @@ num-traits = { workspace = true, optional = true }
|
||||
selinux = { workspace = true, optional = true }
|
||||
|
||||
# icu stuff
|
||||
icu_calendar = { workspace = true, optional = true, features = [
|
||||
"compiled_data",
|
||||
] }
|
||||
icu_collator = { workspace = true, optional = true, features = [
|
||||
"compiled_data",
|
||||
] }
|
||||
icu_datetime = { workspace = true, optional = true, features = [
|
||||
"compiled_data",
|
||||
] }
|
||||
icu_decimal = { workspace = true, optional = true, features = [
|
||||
"compiled_data",
|
||||
] }
|
||||
@@ -143,10 +149,11 @@ format = [
|
||||
"quoting-style",
|
||||
"unit-prefix",
|
||||
]
|
||||
i18n-all = ["i18n-collator", "i18n-decimal"]
|
||||
i18n-all = ["i18n-collator", "i18n-decimal", "i18n-datetime"]
|
||||
i18n-common = ["icu_locale"]
|
||||
i18n-collator = ["i18n-common", "icu_collator"]
|
||||
i18n-decimal = ["i18n-common", "icu_decimal", "icu_provider"]
|
||||
i18n-datetime = ["i18n-common", "icu_calendar", "icu_datetime"]
|
||||
mode = ["libc"]
|
||||
perms = ["entries", "libc", "walkdir"]
|
||||
buf-copy = []
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! Locale-aware datetime formatting utilities using ICU
|
||||
// spell-checker:ignore fieldsets janvier
|
||||
|
||||
use icu_calendar::Date;
|
||||
use icu_datetime::DateTimeFormatter;
|
||||
use icu_datetime::fieldsets;
|
||||
use icu_locale::Locale;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::i18n::get_locale_from_env;
|
||||
|
||||
/// Get the locale for time/date formatting from LC_TIME environment variable
|
||||
pub fn get_time_locale() -> &'static (Locale, super::UEncoding) {
|
||||
static TIME_LOCALE: OnceLock<(Locale, super::UEncoding)> = OnceLock::new();
|
||||
|
||||
TIME_LOCALE.get_or_init(|| get_locale_from_env("LC_TIME"))
|
||||
}
|
||||
|
||||
/// Check if we should use ICU for locale-aware time/date formatting
|
||||
///
|
||||
/// Returns true for non-C/POSIX locales, false otherwise
|
||||
pub fn should_use_icu_locale() -> bool {
|
||||
use icu_locale::locale;
|
||||
|
||||
let (locale, _encoding) = get_time_locale();
|
||||
|
||||
// Use ICU for non-default locales (anything other than C/POSIX)
|
||||
// The default locale is "und" (undefined) representing C/POSIX
|
||||
*locale != locale!("und")
|
||||
}
|
||||
|
||||
/// Get a localized month name for the given month number (1-12)
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `month` - Month number (1 = January, 2 = February, etc.)
|
||||
/// * `full` - If true, return full month name (e.g., "January"), otherwise abbreviated (e.g., "Jan")
|
||||
///
|
||||
/// # Returns
|
||||
/// Localized month name, or falls back to English if locale is not supported
|
||||
pub fn get_localized_month_name(month: u8, full: bool) -> String {
|
||||
// Get locale from environment
|
||||
let (locale, _encoding) = get_time_locale();
|
||||
|
||||
// Create a date with the specified month (use year 2000, day 1 as arbitrary values)
|
||||
let Ok(date) = Date::try_new_gregorian(2000, month, 1) else {
|
||||
// Invalid month, return empty string to signal failure
|
||||
return String::new();
|
||||
};
|
||||
|
||||
// Configure field set for month formatting
|
||||
// Use Year-Month-Day format to ensure we get textual month names
|
||||
let field_set = if full {
|
||||
fieldsets::YMD::long()
|
||||
} else {
|
||||
fieldsets::YMD::medium()
|
||||
};
|
||||
|
||||
// Create formatter with locale
|
||||
let Ok(formatter) = DateTimeFormatter::try_new(locale.clone().into(), field_set) else {
|
||||
// Failed to create formatter, return empty string to signal failure
|
||||
return String::new();
|
||||
};
|
||||
|
||||
// Format the date to get full date, then extract month
|
||||
let formatted = formatter.format(&date).to_string();
|
||||
// Extract month name from formatted date like "15 janvier 2000" or "2000-01-15"
|
||||
// Look for a word that contains letters (the month name)
|
||||
let words: Vec<&str> = formatted.split_whitespace().collect();
|
||||
|
||||
// Return the month name as extracted from ICU (no further processing needed)
|
||||
// ICU already handles the full vs abbreviated formatting correctly
|
||||
words
|
||||
.iter()
|
||||
.find(|word| word.chars().any(|c| c.is_alphabetic()))
|
||||
.map_or_else(String::new, |s| (*s).to_string())
|
||||
}
|
||||
|
||||
/// Get a localized day name for the given date components
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `year` - The year
|
||||
/// * `month` - The month (1-12)
|
||||
/// * `day` - The day of the month
|
||||
/// * `full` - If true, return full day name (e.g., "Monday"), otherwise abbreviated (e.g., "Mon")
|
||||
///
|
||||
/// # Returns
|
||||
/// Localized day name, or falls back to empty string if locale is not supported
|
||||
pub fn get_localized_day_name(year: i32, month: u8, day: u8, full: bool) -> String {
|
||||
// Create ICU Date from components
|
||||
let Ok(date) = Date::try_new_gregorian(year, month, day) else {
|
||||
return String::new();
|
||||
};
|
||||
|
||||
// Get locale from environment
|
||||
let (locale, _encoding) = get_time_locale();
|
||||
|
||||
// Configure field set for day formatting
|
||||
let field_set = if full {
|
||||
fieldsets::E::long() // Full day name
|
||||
} else {
|
||||
fieldsets::E::short() // Abbreviated day name
|
||||
};
|
||||
|
||||
// Create formatter with locale
|
||||
let Ok(formatter) = DateTimeFormatter::try_new(locale.clone().into(), field_set) else {
|
||||
return String::new();
|
||||
};
|
||||
|
||||
// Format the date to get day name
|
||||
let formatted = formatter.format(&date).to_string();
|
||||
formatted.trim().to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_localized_month_name_fallback() {
|
||||
// This should work even if locale is not available
|
||||
let name = get_localized_month_name(1, true);
|
||||
// The function may return empty string if ICU fails, which is fine
|
||||
// The caller (date.rs) will handle this by falling back to jiff
|
||||
assert!(name.is_empty() || name.len() >= 3);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ use icu_locale::{Locale, locale};
|
||||
|
||||
#[cfg(feature = "i18n-collator")]
|
||||
pub mod collator;
|
||||
#[cfg(feature = "i18n-datetime")]
|
||||
pub mod datetime;
|
||||
#[cfg(feature = "i18n-decimal")]
|
||||
pub mod decimal;
|
||||
|
||||
@@ -31,7 +33,7 @@ const DEFAULT_LOCALE: Locale = locale!("und");
|
||||
/// 3. LANG
|
||||
///
|
||||
/// Or fallback on Posix locale, with ASCII encoding.
|
||||
fn get_locale_from_env(locale_name: &str) -> (Locale, UEncoding) {
|
||||
pub fn get_locale_from_env(locale_name: &str) -> (Locale, UEncoding) {
|
||||
let locale_var = ["LC_ALL", locale_name, "LANG"]
|
||||
.iter()
|
||||
.find_map(|&key| std::env::var(key).ok());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
//
|
||||
// spell-checker: ignore: AEDT AEST EEST NZDT NZST Kolkata Iseconds
|
||||
// spell-checker: ignore: AEDT AEST EEST NZDT NZST Kolkata Iseconds févr février janv janvier mercredi samedi sommes
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
@@ -1470,6 +1470,99 @@ fn test_date_posix_format_specifiers() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
fn test_date_format_b_french_locale() {
|
||||
// Test both %B and %b formats with French locale using a loop
|
||||
// This test expects localized month names when i18n support is available
|
||||
let test_cases = [
|
||||
("2025-01-15", "janvier", "janv."), // Wednesday = mercredi, mer.
|
||||
("2025-02-15", "février", "févr."), // Saturday = samedi, sam.
|
||||
];
|
||||
|
||||
for (date, expected_full, expected_abbrev) in &test_cases {
|
||||
let result = new_ucmd!()
|
||||
.env("LC_TIME", "fr_FR.UTF-8")
|
||||
.env("TZ", "UTC")
|
||||
.arg("-d")
|
||||
.arg(date)
|
||||
.arg("+%B %b")
|
||||
.succeeds();
|
||||
|
||||
let output = result.stdout_str().trim();
|
||||
let expected = format!("{expected_full} {expected_abbrev}");
|
||||
|
||||
if output == expected {
|
||||
// i18n feature is working - test passed
|
||||
assert_eq!(output, expected);
|
||||
} else {
|
||||
// i18n feature not available, skip test
|
||||
println!(
|
||||
"Skipping French locale test for {date} - i18n feature not available, got: {output}"
|
||||
);
|
||||
return; // Exit early if i18n not available
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
fn test_date_format_a_french_locale() {
|
||||
// Test both %A and %a formats with French locale using a loop
|
||||
// This test expects localized day names when i18n support is available
|
||||
let test_cases = [
|
||||
("2025-01-15", "mercredi", "mer."), // Wednesday
|
||||
("2025-02-15", "samedi", "sam."), // Saturday
|
||||
];
|
||||
|
||||
for (date, expected_full, expected_abbrev) in &test_cases {
|
||||
let result = new_ucmd!()
|
||||
.env("LC_TIME", "fr_FR.UTF-8")
|
||||
.env("TZ", "UTC")
|
||||
.arg("-d")
|
||||
.arg(date)
|
||||
.arg("+%A %a")
|
||||
.succeeds();
|
||||
|
||||
let output = result.stdout_str().trim();
|
||||
let expected = format!("{expected_full} {expected_abbrev}");
|
||||
|
||||
if output == expected {
|
||||
// i18n feature is working - test passed
|
||||
assert_eq!(output, expected);
|
||||
} else {
|
||||
// i18n feature not available, skip test
|
||||
println!(
|
||||
"Skipping French day locale test for {date} - i18n feature not available, got: {output}"
|
||||
);
|
||||
return; // Exit early if i18n not available
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||
fn test_date_french_full_sentence() {
|
||||
let result = new_ucmd!()
|
||||
.env("LANG", "fr_FR.UTF-8")
|
||||
.env("TZ", "UTC")
|
||||
.arg("-d")
|
||||
.arg("2026-01-21")
|
||||
.arg("+Nous sommes le %A %d %B %Y")
|
||||
.succeeds();
|
||||
|
||||
let output = result.stdout_str().trim();
|
||||
let expected = "Nous sommes le mercredi 21 janvier 2026";
|
||||
|
||||
if output == expected {
|
||||
// i18n feature is working - test passed
|
||||
assert_eq!(output, expected);
|
||||
} else {
|
||||
// i18n feature not available, skip test
|
||||
println!("Skipping French full sentence test - i18n feature not available, got: {output}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that %x format specifier respects locale settings
|
||||
/// This is a regression test for locale-aware date formatting
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user