Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/l10n.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ jobs:
ubuntu-*)
# selinux headers needed for testing
sudo apt-get -y update ; sudo apt-get -y install libselinux1-dev
# Locales required by tests/by-util/test_date.rs and
# tests/by-util/test_dd.rs (fr_FR is the ISO-8859-1 variant
# used by test_iso8859_1_case_conversion).
sudo locale-gen --keep-existing fr_FR
sudo locale-gen --keep-existing fr_FR.UTF-8
sudo locale-gen --keep-existing es_ES.UTF-8
sudo locale-gen --keep-existing en_US.UTF-8
sudo locale-gen --keep-existing fa_IR.UTF-8 # Iran
sudo locale-gen --keep-existing am_ET.UTF-8 # Ethiopia
sudo locale-gen --keep-existing th_TH.UTF-8 # Thailand
sudo locale-gen --keep-existing hu_HU.UTF-8 # Hungary
sudo update-locale
;;
macos-*)
# needed for testing
Expand Down
38 changes: 17 additions & 21 deletions src/uu/date/src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,31 +709,27 @@ fn format_date_with_locale_aware_months(
#[cfg(feature = "i18n-datetime")] skip_localization: bool,
#[cfg(not(feature = "i18n-datetime"))] _skip_localization: bool,
) -> Result<String, String> {
// First check if format string has GNU modifiers (width/flags) and format if present
// This optimization combines detection and formatting in a single pass
if let Some(result) =
format_modifiers::format_with_modifiers_if_present(date, format_string, config)
{
// Apply locale-aware name substitution (month/day names) before modifier
// processing, so that formats like "%-e" don't bypass localization of "%b"/"%A".
// The owned String is kept in `localized` so `fmt` can borrow from it for the
// rest of the function without a dangling reference.
#[cfg(feature = "i18n-datetime")]
let localized: Option<String> = (!skip_localization && should_use_icu_locale())
.then(|| localize_format_string(format_string, date.date()));
#[cfg(feature = "i18n-datetime")]
let fmt: &str = localized.as_deref().unwrap_or(format_string);
#[cfg(not(feature = "i18n-datetime"))]
let fmt = format_string;

// Check if format string has GNU modifiers (width/flags) and format if present
if let Some(result) = format_modifiers::format_with_modifiers_if_present(date, fmt, config) {
return result.map_err(|e| e.to_string());
}

let broken_down = BrokenDownTime::from(date);

// When the i18n-datetime feature is enabled (default), use ICU locale-aware
// formatting if the locale requires it. Without the feature (e.g. wasi/wasm
// builds that use --no-default-features), skip localization entirely and
// format with the raw strftime string.
#[cfg(feature = "i18n-datetime")]
let result = if !should_use_icu_locale() || skip_localization {
broken_down.to_string_with_config(config, format_string)
} else {
let fmt = localize_format_string(format_string, date.date());
broken_down.to_string_with_config(config, &fmt)
};
#[cfg(not(feature = "i18n-datetime"))]
let result = broken_down.to_string_with_config(config, format_string);

result.map_err(|e| e.to_string())
broken_down
.to_string_with_config(config, fmt)
.map_err(|e| e.to_string())
}

/// Return the appropriate format string for the given settings.
Expand Down
29 changes: 28 additions & 1 deletion tests/by-util/test_date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 févr février janv janvier mercredi samedi sommes juin décembre Januar Juni Dezember enero junio diciembre gennaio giugno dicembre junho dezembro lundi dimanche Montag Sonntag Samstag sábado febr MEST KST uueuu ueuu
// spell-checker: ignore: AEDT AEST EEST NZDT NZST Kolkata Iseconds févr février janv janvier mercredi samedi sommes juin décembre Januar Juni Dezember enero junio diciembre gennaio giugno dicembre junho dezembro lundi dimanche Montag Sonntag Samstag sábado febr MEST KST uueuu ueuu vasárnap június január distros
// spell-checker: ignore: uppercases

use std::cmp::Ordering;
Expand Down Expand Up @@ -1622,6 +1622,32 @@ fn test_date_locale_en_us_vs_c_difference() {
}
}

#[test]
#[cfg(unix)]
fn test_date_locale_hu_hungarian() {
// Regression test for uutils/coreutils#11240: the GNU modifier fast-path
// ("%-e") used to run before ICU localization, so "%b"/"%A" came out in
// English even under hu_HU.UTF-8. Pin an explicit format string so the
// assertion is deterministic across glibc versions (the default D_T_FMT
// for hu_HU differs between distros).
if !is_locale_available("hu_HU.UTF-8") {
return;
}

let result = new_ucmd!()
.env("LC_ALL", "hu_HU.UTF-8")
.env("TZ", "UTC")
.arg("-d")
.arg("2025-12-14T13:00:00")
.arg("+%Y. %b %-e., %A, %H:%M:%S %Z")
.succeeds();

assert_eq!(
result.stdout_str(),
"2025. dec 14., vasárnap, 13:00:00 UTC\n"
);
}

#[test]
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
fn test_date_locale_fr_french() {
Expand Down Expand Up @@ -2273,6 +2299,7 @@ fn test_locale_month_names() {
("es_ES.UTF-8", "enero", "junio", "diciembre"),
("it_IT.UTF-8", "gennaio", "giugno", "dicembre"),
("pt_BR.UTF-8", "janeiro", "junho", "dezembro"),
("hu_HU.UTF-8", "január", "június", "december"),
("ja_JP.UTF-8", "1月", "6月", "12月"),
("zh_CN.UTF-8", "一月", "六月", "十二月"),
] {
Expand Down
Loading