forked from extern/nushell
Add custom datetime format through strftime
strings (#9500)
- improves usability of datetime's in displayed text - # Description Creates a config point for specifying long / short date time formats. Defaults to humanized as we have today. Provides for adding strftime formats into config.nu such as: ```nu datetime_format: { normal: "%Y-%m-%d %H:%M:%S" table: "%Y-%m-%d" } ``` Example: ```bash > $env.config.datetime_format ┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ normal ┃ %a, %d %b %Y %H:%M:%S %z ┃ ┃ table ┃ %m/%d/%y %I:%M:%S%p ┃ ┗━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━┛ > let a = (date now) > echo $a Thu, 22 Jun 2023 10:21:23 -0700 > echo [$a] ┏━━━┳━━━━━━━━━━━━━━━━━━━━━┓ ┃ 0 ┃ 06/22/23 10:21:23AM ┃ ┗━━━┻━━━━━━━━━━━━━━━━━━━━━┛ ``` # User-Facing Changes Any place converting a datetime to a user displayed value should be impacted. # Tests + Formatting - `cargo fmt --all -- --check` Done - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err` Done - `cargo test --workspace` Done - `cargo run -- crates/nu-std/tests/run.nu` Not done - doesn't seem to work ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr ``` - Done --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> Co-authored-by: Antoine Stevan <44101798+amtoine@users.noreply.github.com>
This commit is contained in:
parent
e16c1b7c88
commit
0c888486c9
1
.gitignore
vendored
1
.gitignore
vendored
@ -42,6 +42,7 @@ tarpaulin-report.html
|
||||
*.rsproj
|
||||
*.rsproj.user
|
||||
*.sln
|
||||
*.code-workspace
|
||||
|
||||
# direnv
|
||||
.direnv/
|
||||
|
@ -16,7 +16,7 @@ bench = false
|
||||
nu-utils = { path = "../nu-utils", version = "0.81.1" }
|
||||
|
||||
byte-unit = "4.0"
|
||||
chrono = { version = "0.4", features = [ "serde", "std", ], default-features = false }
|
||||
chrono = { version = "0.4", features = [ "serde", "std", "unstable-locales" ], default-features = false }
|
||||
chrono-humanize = "0.2"
|
||||
fancy-regex = "0.11"
|
||||
indexmap = { version = "1.7" }
|
||||
|
@ -106,6 +106,8 @@ pub struct Config {
|
||||
pub cursor_shape_vi_insert: NuCursorShape,
|
||||
pub cursor_shape_vi_normal: NuCursorShape,
|
||||
pub cursor_shape_emacs: NuCursorShape,
|
||||
pub datetime_normal_format: Option<String>,
|
||||
pub datetime_table_format: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -150,6 +152,8 @@ impl Default for Config {
|
||||
cursor_shape_vi_insert: NuCursorShape::Block,
|
||||
cursor_shape_vi_normal: NuCursorShape::UnderScore,
|
||||
cursor_shape_emacs: NuCursorShape::Line,
|
||||
datetime_normal_format: None,
|
||||
datetime_table_format: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1215,6 +1219,50 @@ impl Value {
|
||||
});
|
||||
}
|
||||
},
|
||||
"datetime_format" => {
|
||||
if let Value::Record { cols, vals, span } = &mut vals[index] {
|
||||
for index in (0..cols.len()).rev() {
|
||||
let value = &vals[index];
|
||||
let key2 = cols[index].as_str();
|
||||
match key2 {
|
||||
"normal" => {
|
||||
if let Ok(v) = value.as_string() {
|
||||
config.datetime_normal_format = Some(v);
|
||||
} else {
|
||||
invalid!(Some(*span), "should be a string");
|
||||
}
|
||||
}
|
||||
"table" => {
|
||||
if let Ok(v) = value.as_string() {
|
||||
config.datetime_table_format = Some(v);
|
||||
} else {
|
||||
invalid!(Some(*span), "should be a string");
|
||||
}
|
||||
}
|
||||
x => {
|
||||
invalid_key!(
|
||||
cols,
|
||||
vals,
|
||||
index,
|
||||
value.span().ok(),
|
||||
"$env.config.{key}.{x} is an unknown config setting"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
invalid!(vals[index].span().ok(), "should be a record");
|
||||
// Reconstruct
|
||||
vals[index] = Value::record(
|
||||
vec!["metric".into(), "format".into()],
|
||||
vec![
|
||||
Value::boolean(config.filesize_metric, *span),
|
||||
Value::string(config.filesize_format.clone(), *span),
|
||||
],
|
||||
*span,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Catch all
|
||||
x => {
|
||||
invalid_key!(
|
||||
|
@ -12,7 +12,7 @@ use crate::engine::EngineState;
|
||||
use crate::ShellError;
|
||||
use crate::{did_you_mean, BlockId, Config, Span, Spanned, Type, VarId};
|
||||
use byte_unit::ByteUnit;
|
||||
use chrono::{DateTime, Duration, FixedOffset};
|
||||
use chrono::{DateTime, Duration, FixedOffset, Locale, TimeZone};
|
||||
use chrono_humanize::HumanTime;
|
||||
pub use custom_value::CustomValue;
|
||||
use fancy_regex::Regex;
|
||||
@ -20,10 +20,12 @@ pub use from_value::FromValue;
|
||||
use indexmap::map::IndexMap;
|
||||
pub use lazy_record::LazyRecord;
|
||||
use nu_utils::get_system_locale;
|
||||
use nu_utils::locale::get_system_locale_string;
|
||||
use num_format::ToFormattedString;
|
||||
pub use range::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
@ -534,7 +536,12 @@ impl Value {
|
||||
Value::Float { val, .. } => val.to_string(),
|
||||
Value::Filesize { val, .. } => format_filesize_from_conf(*val, config),
|
||||
Value::Duration { val, .. } => format_duration(*val),
|
||||
Value::Date { val, .. } => format!("{} ({})", val.to_rfc2822(), HumanTime::from(*val)),
|
||||
Value::Date { val, .. } => match &config.datetime_normal_format {
|
||||
Some(format) => self.format_datetime(val, format),
|
||||
None => {
|
||||
format!("{} ({})", val.to_rfc2822(), HumanTime::from(*val))
|
||||
}
|
||||
},
|
||||
Value::Range { val, .. } => {
|
||||
format!(
|
||||
"{}..{}",
|
||||
@ -586,7 +593,10 @@ impl Value {
|
||||
Value::Float { val, .. } => val.to_string(),
|
||||
Value::Filesize { val, .. } => format_filesize_from_conf(*val, config),
|
||||
Value::Duration { val, .. } => format_duration(*val),
|
||||
Value::Date { val, .. } => HumanTime::from(*val).to_string(),
|
||||
Value::Date { val, .. } => match &config.datetime_table_format {
|
||||
Some(format) => self.format_datetime(val, format),
|
||||
None => HumanTime::from(*val).to_string(),
|
||||
},
|
||||
Value::Range { val, .. } => {
|
||||
format!(
|
||||
"{}..{}",
|
||||
@ -630,6 +640,26 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_datetime<Tz: TimeZone>(&self, date_time: &DateTime<Tz>, formatter: &str) -> String
|
||||
where
|
||||
Tz::Offset: Display,
|
||||
{
|
||||
let mut formatter_buf = String::new();
|
||||
let locale: Locale = 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);
|
||||
let format = date_time.format_localized(formatter, locale);
|
||||
|
||||
match formatter_buf.write_fmt(format_args!("{format}")) {
|
||||
Ok(_) => (),
|
||||
Err(_) => formatter_buf = format!("Invalid format string {}", formatter),
|
||||
}
|
||||
formatter_buf
|
||||
}
|
||||
|
||||
/// Convert Value into a debug string
|
||||
pub fn debug_value(&self) -> String {
|
||||
format!("{self:#?}")
|
||||
@ -799,7 +829,7 @@ impl Value {
|
||||
} else {
|
||||
return Err(ShellError::AccessBeyondEndOfStream {
|
||||
span: *origin_span
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Value::CustomValue { val, .. } => {
|
||||
|
@ -207,6 +207,15 @@ let-env config = {
|
||||
}
|
||||
}
|
||||
|
||||
# datetime_format determines what a datetime rendered in the shell would look like.
|
||||
# Behavior without this configuration point will be to "humanize" the datetime display,
|
||||
# showing something like "a day ago."
|
||||
|
||||
datetime_format: {
|
||||
normal: '%a, %d %b %Y %H:%M:%S %z' # shows up in displays of variables or other datetime's outside of tables
|
||||
# table: '%m/%d/%y %I:%M:%S%p' # generally shows up in tabular outputs such as ls. commenting this out will change it to the default human readable datetime format
|
||||
}
|
||||
|
||||
explore: {
|
||||
help_banner: true
|
||||
exit_esc: true
|
||||
|
Loading…
Reference in New Issue
Block a user