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}")