diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index fd93bf4ba..ffaba6964 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -7,6 +7,8 @@ use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, }; +use crate::generate_strftime_list; + struct Arguments { timezone: Option>, offset: Option>, @@ -175,7 +177,7 @@ fn operate( if options.column_paths.is_empty() && !list_flag { action(&v, &zone_options, &format_options, head) } else if list_flag { - generate_strfttime_list(head) + generate_strftime_list(head, true) } else { let mut ret = v; for path in &options.column_paths { @@ -196,917 +198,6 @@ fn operate( ) } -fn generate_strfttime_list(head: Span) -> Value { - let column_names = vec![ - "Specification".into(), - "Example".into(), - "Description".into(), - ]; - let records = vec![ - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%Y".into(), - span: head, - }, - Value::String { - val: "2001".into(), - span: head, - }, - Value::String { - val: "The full proleptic Gregorian year, zero-padded to 4 digits".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%C".into(), - span: head, - }, - Value::String { - val: "20".into(), - span: head, - }, - Value::String { - val: "The proleptic Gregorian year divided by 100, zero-padded to 2 digits. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%y".into(), - span: head, - }, - Value::String { - val: "01".into(), - span: head, - }, - Value::String { - val: "The proleptic Gregorian year modulo 100, zero-padded to 2 digits. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%m".into(), - span: head, - }, - Value::String { - val: "07".into(), - span: head, - }, - Value::String { - val: "Month number (01--12), zero-padded to 2 digits.".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%b".into(), - span: head, - }, - Value::String { - val: "Jul".into(), - span: head, - }, - Value::String { - val: "Abbreviated month name. Always 3 letters".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%B".into(), - span: head, - }, - Value::String { - val: "July".into(), - span: head, - }, - Value::String { - val: "Full month name. Also accepts corresponding abbreviation in parsing" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%h".into(), - span: head, - }, - Value::String { - val: "Jul".into(), - span: head, - }, - Value::String { - val: "Same to %b".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%d".into(), - span: head, - }, - Value::String { - val: "08".into(), - span: head, - }, - Value::String { - val: "Day number (01--31), zero-padded to 2 digits".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%e".into(), - span: head, - }, - Value::String { - val: "8".into(), - span: head, - }, - Value::String { - val: "Same to %d but space-padded. Same to %_d".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%a".into(), - span: head, - }, - Value::String { - val: "Sun".into(), - span: head, - }, - Value::String { - val: "Abbreviated weekday name. Always 3 letters".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%A".into(), - span: head, - }, - Value::String { - val: "Sunday".into(), - span: head, - }, - Value::String { - val: "Full weekday name. Also accepts corresponding abbreviation in parsing" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%w".into(), - span: head, - }, - Value::String { - val: "0".into(), - span: head, - }, - Value::String { - val: "Sunday = 0, Monday = 1, ..., Saturday = 6".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%u".into(), - span: head, - }, - Value::String { - val: "7".into(), - span: head, - }, - Value::String { - val: "Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601) -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%U".into(), - span: head, - }, - Value::String { - val: "28".into(), - span: head, - }, - Value::String { - val: "Week number starting with Sunday (00--53), zero-padded to 2 digits. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%W".into(), - span: head, - }, - Value::String { - val: "27".into(), - span: head, - }, - Value::String { - val: "Same to %U, but week 1 starts with the first Monday in that year instead" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%G".into(), - span: head, - }, - Value::String { - val: "2001".into(), - span: head, - }, - Value::String { - val: "Same to %Y but uses the year number in ISO 8601 week date. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%g".into(), - span: head, - }, - Value::String { - val: "01".into(), - span: head, - }, - Value::String { - val: "Same to %y but uses the year number in ISO 8601 week date. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%V".into(), - span: head, - }, - Value::String { - val: "27".into(), - span: head, - }, - Value::String { - val: "Same to %U but uses the week number in ISO 8601 week date (01--53). -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%j".into(), - span: head, - }, - Value::String { - val: "189".into(), - span: head, - }, - Value::String { - val: "Day of the year (001--366), zero-padded to 3 digits".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%D".into(), - span: head, - }, - Value::String { - val: "07/08/01".into(), - span: head, - }, - Value::String { - val: "Month-day-year format. Same to %m/%d/%y".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%x".into(), - span: head, - }, - Value::String { - val: "07/08/01".into(), - span: head, - }, - Value::String { - val: "Same to %D".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%F".into(), - span: head, - }, - Value::String { - val: "2001-07-08".into(), - span: head, - }, - Value::String { - val: "Year-month-day format (ISO 8601). Same to %Y-%m-%d".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%v".into(), - span: head, - }, - Value::String { - val: "8-Jul-2001".into(), - span: head, - }, - Value::String { - val: "Day-month-year format. Same to %e-%b-%Y".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%H".into(), - span: head, - }, - Value::String { - val: "00".into(), - span: head, - }, - Value::String { - val: "Hour number (00--23), zero-padded to 2 digits".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%k".into(), - span: head, - }, - Value::String { - val: "0".into(), - span: head, - }, - Value::String { - val: "Same to %H but space-padded. Same to %_H".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%I".into(), - span: head, - }, - Value::String { - val: "12".into(), - span: head, - }, - Value::String { - val: "Hour number in 12-hour clocks (01--12), zero-padded to 2 digits".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%l".into(), - span: head, - }, - Value::String { - val: "12".into(), - span: head, - }, - Value::String { - val: "Same to %I but space-padded. Same to %_I".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%P".into(), - span: head, - }, - Value::String { - val: "am".into(), - span: head, - }, - Value::String { - val: "am or pm in 12-hour clocks".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%p".into(), - span: head, - }, - Value::String { - val: "AM".into(), - span: head, - }, - Value::String { - val: "AM or PM in 12-hour clocks".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%M".into(), - span: head, - }, - Value::String { - val: "34".into(), - span: head, - }, - Value::String { - val: "Minute number (00--59), zero-padded to 2 digits".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%S".into(), - span: head, - }, - Value::String { - val: "60".into(), - span: head, - }, - Value::String { - val: "Second number (00--60), zero-padded to 2 digits. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%f".into(), - span: head, - }, - Value::String { - val: "026490000".into(), - span: head, - }, - Value::String { - val: "The fractional seconds (in nanoseconds) since last whole second. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%.".into(), - span: head, - }, - Value::String { - val: ".026490".into(), - span: head, - }, - Value::String { - val: "Similar to .%f but left-aligned. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%.".into(), - span: head, - }, - Value::String { - val: ".026".into(), - span: head, - }, - Value::String { - val: "Similar to .%f but left-aligned but fixed to a length of 3. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%.".into(), - span: head, - }, - Value::String { - val: ".026490".into(), - span: head, - }, - Value::String { - val: "Similar to .%f but left-aligned but fixed to a length of 6. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%.".into(), - span: head, - }, - Value::String { - val: ".026490000".into(), - span: head, - }, - Value::String { - val: "Similar to .%f but left-aligned but fixed to a length of 9. -" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%R".into(), - span: head, - }, - Value::String { - val: "00:34".into(), - span: head, - }, - Value::String { - val: "Hour-minute format. Same to %H:%M".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%T".into(), - span: head, - }, - Value::String { - val: "00:34:60".into(), - span: head, - }, - Value::String { - val: "Hour-minute-second format. Same to %H:%M:%S".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%X".into(), - span: head, - }, - Value::String { - val: "00:34:60".into(), - span: head, - }, - Value::String { - val: "Same to %T".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%r".into(), - span: head, - }, - Value::String { - val: "12:34:60".into(), - span: head, - }, - Value::String { - val: "AM Hour-minute-second format in 12-hour clocks. Same to %I:%M:%S %p" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%Z".into(), - span: head, - }, - Value::String { - val: "ACST".into(), - span: head, - }, - Value::String { - val: "Formatting only: Local time zone name".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%z".into(), - span: head, - }, - Value::String { - val: "+0930".into(), - span: head, - }, - Value::String { - val: "Offset from the local time to UTC (with UTC being +0000)".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%:".into(), - span: head, - }, - Value::String { - val: "+09:30".into(), - span: head, - }, - Value::String { - val: "Same to %z but with a colon".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%c".into(), - span: head, - }, - Value::String { - val: "Sun".into(), - span: head, - }, - Value::String { - val: - "Jul 8 00:34:60 2001 ctime date & time format. Same to %a %b %e %T %Y sans" - .into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%s".into(), - span: head, - }, - Value::String { - val: "994518299".into(), - span: head, - }, - Value::String { - val: "UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC.".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%t".into(), - span: head, - }, - Value::String { - val: "".into(), - span: head, - }, - Value::String { - val: "Literal tab (\\t)".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names.clone(), - vals: vec![ - Value::String { - val: "%n".into(), - span: head, - }, - Value::String { - val: "".into(), - span: head, - }, - Value::String { - val: "Literal newline (\\n)".into(), - span: head, - }, - ], - span: head, - }, - Value::Record { - cols: column_names, - vals: vec![ - Value::String { - val: "%%".into(), - span: head, - }, - Value::String { - val: "".into(), - span: head, - }, - Value::String { - val: "percent sign".into(), - span: head, - }, - ], - span: head, - }, - ]; - - Value::List { - vals: records, - span: head, - } -} - fn action( input: &Value, timezone: &Option>, diff --git a/crates/nu-command/src/date/format.rs b/crates/nu-command/src/date/format.rs index b4c78f508..4d9bb7dfc 100644 --- a/crates/nu-command/src/date/format.rs +++ b/crates/nu-command/src/date/format.rs @@ -3,7 +3,7 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, Signature, Span, Spanned, SyntaxShape, Value, + Category, Example, PipelineData, Signature, Span, SyntaxShape, Value, }; use super::utils::{parse_date_from_string, unsupported_input_error}; @@ -18,7 +18,8 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("date format") - .required( + .switch("list", "lists strftime cheatsheet", Some('l')) + .optional( "format string", SyntaxShape::String, "the desired date format", @@ -27,7 +28,7 @@ impl Command for SubCommand { } fn usage(&self) -> &str { - "Format a given date using the given format string." + "Format a given date using a format string." } fn run( @@ -38,9 +39,20 @@ impl Command for SubCommand { input: PipelineData, ) -> Result { let head = call.head; - let formatter: Spanned = call.req(engine_state, stack, 0)?; + if call.has_flag("list") { + return Ok(PipelineData::Value( + generate_strftime_list(head, false), + None, + )); + } + + let format = call.opt::(engine_state, stack, 0)?; + input.map( - move |value| format_helper(value, &formatter, head), + move |value| match &format { + Some(format) => format_helper(value, format.as_str(), head), + None => format_helper_rfc2822(value, head), + }, engine_state.ctrlc.clone(), ) } @@ -48,17 +60,25 @@ impl Command for SubCommand { fn examples(&self) -> Vec { vec![ Example { - description: "Format a given date using the given format string.", + description: "Format a given date using the default format (RFC 2822).", + example: r#""2021-10-22 20:00:12 +01:00" | date format"#, + result: Some(Value::String { + val: "Fri, 22 Oct 2021 20:00:12 +0100".to_string(), + span: Span::test_data(), + }), + }, + Example { + description: "Format a given date using a given format string.", example: "date format '%Y-%m-%d'", result: None, }, Example { - description: "Format a given date using the given format string.", + description: "Format a given date using a given format string.", example: r#"date format "%Y-%m-%d %H:%M:%S""#, result: None, }, Example { - description: "Format a given date using the given format string.", + description: "Format a given date using a given format string.", example: r#""2021-10-22 20:00:12 +01:00" | date format "%Y-%m-%d""#, result: None, }, @@ -66,10 +86,10 @@ impl Command for SubCommand { } } -fn format_helper(value: Value, formatter: &Spanned, span: Span) -> Value { +fn format_helper(value: Value, formatter: &str, span: Span) -> Value { match value { Value::Date { val, span: _ } => Value::String { - val: val.format(formatter.item.as_str()).to_string(), + val: val.format(formatter).to_string(), span, }, Value::String { @@ -79,7 +99,7 @@ fn format_helper(value: Value, formatter: &Spanned, span: Span) -> Value let dt = parse_date_from_string(val, val_span); match dt { Ok(x) => Value::String { - val: x.format(formatter.item.as_str()).to_string(), + val: x.format(formatter).to_string(), span, }, Err(e) => e, @@ -88,10 +108,7 @@ fn format_helper(value: Value, formatter: &Spanned, span: Span) -> Value Value::Nothing { span: _ } => { let dt = Local::now(); Value::String { - val: dt - .with_timezone(dt.offset()) - .format(formatter.item.as_str()) - .to_string(), + val: dt.with_timezone(dt.offset()).format(formatter).to_string(), span, } } @@ -99,6 +116,334 @@ fn format_helper(value: Value, formatter: &Spanned, span: Span) -> Value } } +fn format_helper_rfc2822(value: Value, span: Span) -> Value { + match value { + Value::Date { val, span: _ } => Value::String { + val: val.to_rfc2822(), + span, + }, + Value::String { + val, + span: val_span, + } => { + let dt = parse_date_from_string(val, val_span); + match dt { + Ok(x) => Value::String { + val: x.to_rfc2822(), + span, + }, + Err(e) => e, + } + } + Value::Nothing { span: _ } => { + let dt = Local::now(); + Value::String { + val: dt.with_timezone(dt.offset()).to_rfc2822(), + span, + } + } + _ => unsupported_input_error(span), + } +} + +/// Generates a table containing available datetime format specifiers +/// +/// # Arguments +/// * `head` - use the call's head +/// * `show_parse_only_formats` - whether parse-only format specifiers (that can't be outputted) should be shown. Should only be used for `into datetime`, not `date format` +pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool) -> Value { + let column_names = vec![ + "Specification".into(), + "Example".into(), + "Description".into(), + ]; + let now = Local::now(); + + struct FormatSpecification<'a> { + spec: &'a str, + description: &'a str, + } + + let specifications = vec![ + FormatSpecification { + spec: "%Y", + description: "The full proleptic Gregorian year, zero-padded to 4 digits.", + }, + FormatSpecification { + spec: "%C", + description: "The proleptic Gregorian year divided by 100, zero-padded to 2 digits.", + }, + FormatSpecification { + spec: "%Y", + description: "The full proleptic Gregorian year, zero-padded to 4 digits.", + }, + FormatSpecification { + spec: "%C", + description: "The proleptic Gregorian year divided by 100, zero-padded to 2 digits.", + }, + FormatSpecification { + spec: "%y", + description: "The proleptic Gregorian year modulo 100, zero-padded to 2 digits.", + }, + FormatSpecification { + spec: "%m", + description: "Month number (01--12), zero-padded to 2 digits.", + }, + FormatSpecification { + spec: "%b", + description: "Abbreviated month name. Always 3 letters.", + }, + FormatSpecification { + spec: "%B", + description: "Full month name. Also accepts corresponding abbreviation in parsing.", + }, + FormatSpecification { + spec: "%h", + description: "Same as %b.", + }, + FormatSpecification { + spec: "%d", + description: "Day number (01--31), zero-padded to 2 digits.", + }, + FormatSpecification { + spec: "%e", + description: "Same as %d but space-padded. Same as %_d.", + }, + FormatSpecification { + spec: "%a", + description: "Abbreviated weekday name. Always 3 letters.", + }, + FormatSpecification { + spec: "%A", + description: "Full weekday name. Also accepts corresponding abbreviation in parsing.", + }, + FormatSpecification { + spec: "%w", + description: "Sunday = 0, Monday = 1, ..., Saturday = 6.", + }, + FormatSpecification { + spec: "%u", + description: "Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601)", + }, + FormatSpecification { + spec: "%U", + description: "Week number starting with Sunday (00--53), zero-padded to 2 digits.", + }, + FormatSpecification { + spec: "%W", + description: + "Same as %U, but week 1 starts with the first Monday in that year instead.", + }, + FormatSpecification { + spec: "%G", + description: "Same as %Y but uses the year number in ISO 8601 week date.", + }, + FormatSpecification { + spec: "%g", + description: "Same as %y but uses the year number in ISO 8601 week date.", + }, + FormatSpecification { + spec: "%V", + description: "Same as %U but uses the week number in ISO 8601 week date (01--53).", + }, + FormatSpecification { + spec: "%j", + description: "Day of the year (001--366), zero-padded to 3 digits.", + }, + FormatSpecification { + spec: "%D", + description: "Month-day-year format. Same as %m/%d/%y.", + }, + FormatSpecification { + spec: "%x", + description: "Locale's date representation (e.g., 12/31/99).", + }, + FormatSpecification { + spec: "%F", + description: "Year-month-day format (ISO 8601). Same as %Y-%m-%d.", + }, + FormatSpecification { + spec: "%v", + description: "Day-month-year format. Same as %e-%b-%Y.", + }, + FormatSpecification { + spec: "%H", + description: "Hour number (00--23), zero-padded to 2 digits.", + }, + FormatSpecification { + spec: "%k", + description: "Same as %H but space-padded. Same as %_H.", + }, + FormatSpecification { + spec: "%I", + description: "Hour number in 12-hour clocks (01--12), zero-padded to 2 digits.", + }, + FormatSpecification { + spec: "%l", + description: "Same as %I but space-padded. Same as %_I.", + }, + FormatSpecification { + spec: "%P", + description: "am or pm in 12-hour clocks.", + }, + FormatSpecification { + spec: "%p", + description: "AM or PM in 12-hour clocks.", + }, + FormatSpecification { + spec: "%M", + description: "Minute number (00--59), zero-padded to 2 digits.", + }, + FormatSpecification { + spec: "%S", + description: "Second number (00--60), zero-padded to 2 digits.", + }, + FormatSpecification { + spec: "%f", + description: "The fractional seconds (in nanoseconds) since last whole second.", + }, + FormatSpecification { + spec: "%.f", + description: "Similar to .%f but left-aligned. These all consume the leading dot.", + }, + FormatSpecification { + spec: "%.3f", + description: "Similar to .%f but left-aligned but fixed to a length of 3.", + }, + FormatSpecification { + spec: "%.6f", + description: "Similar to .%f but left-aligned but fixed to a length of 6.", + }, + FormatSpecification { + spec: "%.9f", + description: "Similar to .%f but left-aligned but fixed to a length of 9.", + }, + FormatSpecification { + spec: "%3f", + description: "Similar to %.3f but without the leading dot.", + }, + FormatSpecification { + spec: "%6f", + description: "Similar to %.6f but without the leading dot.", + }, + FormatSpecification { + spec: "%9f", + description: "Similar to %.9f but without the leading dot.", + }, + FormatSpecification { + spec: "%R", + description: "Hour-minute format. Same as %H:%M.", + }, + FormatSpecification { + spec: "%T", + description: "Hour-minute-second format. Same as %H:%M:%S.", + }, + FormatSpecification { + spec: "%X", + description: "Locale's time representation (e.g., 23:13:48).", + }, + FormatSpecification { + spec: "%r", + description: "Hour-minute-second format in 12-hour clocks. Same as %I:%M:%S %p.", + }, + FormatSpecification { + spec: "%Z", + description: + "Local time zone name. Skips all non-whitespace characters during parsing.", + }, + FormatSpecification { + spec: "%z", + description: "Offset from the local time to UTC (with UTC being +0000).", + }, + FormatSpecification { + spec: "%:z", + description: "Same as %z but with a colon.", + }, + FormatSpecification { + spec: "%c", + description: "Locale's date and time (e.g., Thu Mar 3 23:05:25 2005).", + }, + FormatSpecification { + spec: "%+", + description: "ISO 8601 / RFC 3339 date & time format.", + }, + FormatSpecification { + spec: "%s", + description: "UNIX timestamp, the number of seconds since 1970-01-01", + }, + FormatSpecification { + spec: "%t", + description: "Literal tab (\\t).", + }, + FormatSpecification { + spec: "%n", + description: "Literal newline (\\n).", + }, + FormatSpecification { + spec: "%%", + description: "Literal percent sign.", + }, + ]; + + let mut records = specifications + .iter() + .map(|s| Value::Record { + cols: column_names.clone(), + vals: vec![ + Value::String { + val: s.spec.to_string(), + span: head, + }, + Value::String { + val: now.format(s.spec).to_string(), + span: head, + }, + Value::String { + val: s.description.to_string(), + span: head, + }, + ], + span: head, + }) + .collect::>(); + + if show_parse_only_formats { + // now.format("%#z") will panic since it is parse-only + // so here we emulate how it will look: + let example = now + .format("%:z") // e.g. +09:30 + .to_string() + .get(0..3) // +09:30 -> +09 + .unwrap_or("") + .to_string(); + + records.push(Value::Record { + cols: column_names, + vals: vec![ + Value::String { + val: "%#z".to_string(), + span: head, + }, + Value::String { + val: example, + span: head, + }, + Value::String { + val: "Parsing only: Same as %z but allows minutes to be missing or present." + .to_string(), + span: head, + }, + ], + span: head, + }); + } + + Value::List { + vals: records, + span: head, + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/nu-command/src/date/mod.rs b/crates/nu-command/src/date/mod.rs index 6d0e3727e..d2b6e8f31 100644 --- a/crates/nu-command/src/date/mod.rs +++ b/crates/nu-command/src/date/mod.rs @@ -9,6 +9,7 @@ mod to_timezone; mod utils; pub use date_::Date; +pub(crate) use format::generate_strftime_list; pub use format::SubCommand as DateFormat; pub use humanize::SubCommand as DateHumanize; pub use list_timezone::SubCommand as DateListTimezones;