From 123bf2d73694dd63d29a77e9c7578421b7739b67 Mon Sep 17 00:00:00 2001 From: David Matos Date: Tue, 20 Feb 2024 18:08:49 +0100 Subject: [PATCH] fix format date based on users locale (#11908) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Hi, Fixes #10838, where before the `date` would be formatted incorrectly, and was not picking `LC_TIME` for time formatting, but it picked the first locale returned by the `sys-locale` crate instead. Now it will format time based on `LC_TIME`. For example, ``` // my locale `nl_NL.UTF-8` ❯ date now | format date '%x %X' 20-02-24 17:17:12 $env.LC_TIME = "en_US.UTF-8" ❯ date now | format date '%x %X' 02/20/2024 05:16:28 PM ``` Note that I also changed the `default_env.nu` as otherwise the Time will show AM/PM twice. Also reason for the `chrono` update is because this relies on a fix to upstream repo, which i initially submitted an [issue](https://github.com/chronotope/chrono/issues/1349#event-11765363286) # User-Facing Changes # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - [X] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - [X] `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - [X] `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - [X] `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` # After Submitting --- Cargo.lock | 8 +++---- crates/nu-command/Cargo.toml | 2 +- crates/nu-command/src/strings/format/date.rs | 20 ++++++++-------- .../nu-command/tests/commands/date/format.rs | 23 +++++++++++++++++++ crates/nu-protocol/Cargo.toml | 2 +- crates/nu-protocol/src/value/mod.rs | 18 ++++++++------- .../nu-utils/src/sample_config/default_env.nu | 2 +- 7 files changed, 51 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd5eb470a9..163938624e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,9 +657,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", @@ -4503,9 +4503,9 @@ dependencies = [ [[package]] name = "pure-rust-locales" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed02a829e62dc2715ceb8afb4f80e298148e1345749ceb369540fe0eb3368432" +checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" [[package]] name = "pwd" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 901e4e5023..3773149f7c 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -33,7 +33,7 @@ base64 = "0.21" byteorder = "1.5" bytesize = "1.3" calamine = "0.24.0" -chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false } +chrono = { version = "0.4.34", features = ["std", "unstable-locales"], default-features = false } chrono-humanize = "0.2.3" chrono-tz = "0.8" crossterm = "0.27" diff --git a/crates/nu-command/src/strings/format/date.rs b/crates/nu-command/src/strings/format/date.rs index 1555ee3c99..3a00b97184 100644 --- a/crates/nu-command/src/strings/format/date.rs +++ b/crates/nu-command/src/strings/format/date.rs @@ -7,7 +7,7 @@ use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, }; -use nu_utils::locale::get_system_locale_string; +use nu_utils::locale::{get_system_locale_string, LOCALE_OVERRIDE_ENV_VAR}; use std::fmt::{Display, Write}; use crate::{generate_strftime_list, parse_date_from_string}; @@ -118,21 +118,23 @@ where Tz::Offset: Display, { let mut formatter_buf = String::new(); - // These are already in locale format, so we don't need to localize them - let format = if ["%x", "%X", "%r"] - .iter() - .any(|item| formatter.contains(item)) + // Format using locale LC_TIME + let locale = if let Ok(l) = + std::env::var(LOCALE_OVERRIDE_ENV_VAR).or_else(|_| std::env::var("LC_TIME")) { - date_time.format(formatter) + let locale_str = l.split('.').next().unwrap_or("en_US"); + locale_str.try_into().unwrap_or(Locale::en_US) } else { - let locale: Locale = get_system_locale_string() + // LC_ALL > LC_CTYPE > LANG + // Not locale present, default to en_US + get_system_locale_string() .map(|l| l.replace('-', "_")) // `chrono::Locale` needs something like `xx_xx`, rather than `xx-xx` .unwrap_or_else(|| String::from("en_US")) .as_str() .try_into() - .unwrap_or(Locale::en_US); - date_time.format_localized(formatter, locale) + .unwrap_or(Locale::en_US) }; + let format = date_time.format_localized(formatter, locale); match formatter_buf.write_fmt(format_args!("{format}")) { Ok(_) => Value::string(formatter_buf, span), diff --git a/crates/nu-command/tests/commands/date/format.rs b/crates/nu-command/tests/commands/date/format.rs index b7698c2629..cd49ea78ac 100644 --- a/crates/nu-command/tests/commands/date/format.rs +++ b/crates/nu-command/tests/commands/date/format.rs @@ -80,3 +80,26 @@ fn locale_format_respect_different_locale() { ); assert!(actual.out.contains("ven. 22 oct. 2021 20:00:12 +01:00")); } + +#[test] +fn locale_with_different_format_specifiers() { + let actual = nu!( + locale: "en_US", + pipeline( + r#" + "Thu, 26 Oct 2023 22:52:14 +0200" | format date "%x %X" + "# + ) + ); + assert!(actual.out.contains("10/26/2023 10:52:14 PM")); + + let actual = nu!( + locale: "nl_NL", + pipeline( + r#" + "Thu, 26 Oct 2023 22:52:14 +0200" | format date "%x %X" + "# + ) + ); + assert!(actual.out.contains("26-10-23 22:52:14")); +} diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 0f2676e48f..8db6154f66 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -18,7 +18,7 @@ nu-path = { path = "../nu-path", version = "0.90.2" } nu-system = { path = "../nu-system", version = "0.90.2" } byte-unit = { version = "5.1", features = [ "serde" ] } -chrono = { version = "0.4", features = [ "serde", "std", "unstable-locales" ], default-features = false } +chrono = { version = "0.4.34", features = [ "serde", "std", "unstable-locales" ], default-features = false } chrono-humanize = "0.2" fancy-regex = "0.13" indexmap = "2.2" diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 7f2c569262..03c4748fbd 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -20,6 +20,7 @@ pub use custom_value::CustomValue; use fancy_regex::Regex; pub use from_value::FromValue; pub use lazy_record::LazyRecord; +use nu_utils::locale::LOCALE_OVERRIDE_ENV_VAR; use nu_utils::{ contains_emoji, get_system_locale, locale::get_system_locale_string, IgnoreCaseExt, }; @@ -29,6 +30,7 @@ pub use range::*; pub use record::Record; use serde::{Deserialize, Serialize}; use std::fmt::Write; + use std::{ borrow::Cow, fmt::{Display, Formatter, Result as FmtResult}, @@ -844,21 +846,21 @@ impl Value { Tz::Offset: Display, { let mut formatter_buf = String::new(); - // These are already in locale format, so we don't need to localize them - let format = if ["%x", "%X", "%r"] - .iter() - .any(|item| formatter.contains(item)) + let locale = if let Ok(l) = + std::env::var(LOCALE_OVERRIDE_ENV_VAR).or_else(|_| std::env::var("LC_TIME")) { - date_time.format(formatter) + let locale_str = l.split('.').next().unwrap_or("en_US"); + locale_str.try_into().unwrap_or(Locale::en_US) } else { - let locale: Locale = get_system_locale_string() + // LC_ALL > LC_CTYPE > LANG else en_US + get_system_locale_string() .map(|l| l.replace('-', "_")) // `chrono::Locale` needs something like `xx_xx`, rather than `xx-xx` .unwrap_or_else(|| String::from("en_US")) .as_str() .try_into() - .unwrap_or(Locale::en_US); - date_time.format_localized(formatter, locale) + .unwrap_or(Locale::en_US) }; + let format = date_time.format_localized(formatter, locale); match formatter_buf.write_fmt(format_args!("{format}")) { Ok(_) => (), diff --git a/crates/nu-utils/src/sample_config/default_env.nu b/crates/nu-utils/src/sample_config/default_env.nu index 0133db4d0e..8dec580f1b 100644 --- a/crates/nu-utils/src/sample_config/default_env.nu +++ b/crates/nu-utils/src/sample_config/default_env.nu @@ -21,7 +21,7 @@ def create_right_prompt [] { let time_segment = ([ (ansi reset) (ansi magenta) - (date now | format date '%x %X %p') # try to respect user's locale + (date now | format date '%x %X') # try to respect user's locale ] | str join | str replace --regex --all "([/:])" $"(ansi green)${1}(ansi magenta)" | str replace --regex --all "([AP]M)" $"(ansi magenta_underline)${1}")