diff --git a/src/options/view.rs b/src/options/view.rs index 94889cad46dceb57806228a9a2b6af7b25c369c4..b905c179c57f79d2bcfb8e3141abf8eff862cca2 100644 --- a/src/options/view.rs +++ b/src/options/view.rs @@ -413,7 +413,10 @@ mod test { use super::*; use std::ffi::OsString; use options::flags; - use options::parser::Flag; + use options::parser::{Flag, Arg}; + + use options::test::parse_for_test; + use options::test::Strictnesses::*; pub fn os(input: &'static str) -> OsString { let mut os = OsString::new(); @@ -421,36 +424,77 @@ mod test { os } + static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, &flags::TIME_STYLE, + &flags::TIME, &flags::MODIFIED, &flags::CREATED, &flags::ACCESSED, + &flags::COLOR, &flags::COLOUR ]; + macro_rules! test { ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => $result:expr) => { #[test] fn $name() { - use options::parser::Arg; - use options::test::parse_for_test; - use options::test::Strictnesses::*; - - static TEST_ARGS: &[&Arg] = &[ &flags::BINARY, &flags::BYTES, - &flags::TIME, &flags::MODIFIED, &flags::CREATED, &flags::ACCESSED, - &flags::COLOR, &flags::COLOUR ]; - for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) { assert_eq!(result, $result); } } }; + + ($name:ident: $type:ident <- $inputs:expr; $stricts:expr => like $pat:pat) => { + #[test] + fn $name() { + for result in parse_for_test($inputs.as_ref(), TEST_ARGS, $stricts, |mf| $type::deduce(mf)) { + println!("Testing {:?}", result); + match result { + $pat => assert!(true), + _ => assert!(false), + } + } + } + }; } mod size_formats { use super::*; + // Default behaviour test!(empty: SizeFormat <- []; Both => Ok(SizeFormat::DecimalBytes)); + + // Individual flags test!(binary: SizeFormat <- ["--binary"]; Both => Ok(SizeFormat::BinaryBytes)); test!(bytes: SizeFormat <- ["--bytes"]; Both => Ok(SizeFormat::JustBytes)); + + // Errors test!(both: SizeFormat <- ["--binary", "--bytes"]; Both => Err(Misfire::Conflict(&flags::BINARY, &flags::BYTES))); } + mod time_formats { + use super::*; + + // These tests use pattern matching because TimeFormat doesn’t + // implement PartialEq. + + // Default behaviour + test!(empty: TimeFormat <- []; Both => like Ok(TimeFormat::DefaultFormat(_))); + + // Individual settings + test!(default: TimeFormat <- ["--time-style=default"]; Both => like Ok(TimeFormat::DefaultFormat(_))); + test!(iso: TimeFormat <- ["--time-style", "iso"]; Both => like Ok(TimeFormat::ISOFormat(_))); + test!(long_iso: TimeFormat <- ["--time-style=long-iso"]; Both => like Ok(TimeFormat::LongISO)); + test!(full_iso: TimeFormat <- ["--time-style", "full-iso"]; Both => like Ok(TimeFormat::FullISO)); + + // Overriding + test!(actually: TimeFormat <- ["--time-style=default", "--time-style", "iso"]; Last => like Ok(TimeFormat::ISOFormat(_))); + test!(actual_2: TimeFormat <- ["--time-style=default", "--time-style", "iso"]; Complain => like Err(Misfire::Duplicate(Flag::Long("time-style"), Flag::Long("time-style")))); + + test!(nevermind: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"]; Last => like Ok(TimeFormat::FullISO)); + test!(nevermore: TimeFormat <- ["--time-style", "long-iso", "--time-style=full-iso"]; Complain => like Err(Misfire::Duplicate(Flag::Long("time-style"), Flag::Long("time-style")))); + + // Errors + test!(daily: TimeFormat <- ["--time-style=24-hour"]; Both => like Err(Misfire::BadArgument(_, _, _))); + } + + mod time_types { use super::*; diff --git a/src/output/time.rs b/src/output/time.rs index 4f6d621cb5b4cf66b5de53af23a85fc23d696875..4439e9756f965d87d7c2da02c8bb118505ef6480 100644 --- a/src/output/time.rs +++ b/src/output/time.rs @@ -1,3 +1,5 @@ +//! Timestamp formatting. + use datetime::{LocalDateTime, TimeZone, DatePiece, TimePiece}; use datetime::fmt::DateFormat; use locale; @@ -6,13 +8,48 @@ use std::cmp; use fs::fields::Time; +/// Every timestamp in exa needs to be rendered by a **time format**. +/// Formatting times is tricky, because how a timestamp is rendered can +/// depend on one or more of the following: +/// +/// - The user’s locale, for printing the month name as “Feb”, or as “fév”, +/// or as “2月”; +/// - The current year, because certain formats will be less precise when +/// dealing with dates far in the past; +/// - The formatting style that the user asked for on the command-line. +/// +/// Because not all formatting styles need the same data, they all have their +/// own enum variants. It’s not worth looking the locale up if the formatter +/// prints month names as numbers. +/// +/// Currently exa does not support *custom* styles, where the user enters a +/// format string in an environment variable or something. Just these four. +#[derive(Debug)] pub enum TimeFormat { + + /// The **default format** uses the user’s locale to print month names, + /// and specifies the timestamp down to the minute for recent times, and + /// day for older times. DefaultFormat(DefaultFormat), + + /// Use the **ISO format**, which specifies the timestamp down to the + /// minute for recent times, and day for older times. It uses a number + /// for the month so it doesn’t need a locale. ISOFormat(ISOFormat), + + /// Use the **long ISO format**, which specifies the timestamp down to the + /// minute using only numbers, without needing the locale or year. LongISO, + + /// Use the **full ISO format**, which specifies the timestamp down to the + /// millisecond and includes its offset down to the minute. This too uses + /// only numbers so doesn’t require any special consideration. FullISO, } +// There are two different formatting functions because local and zoned +// timestamps are separate types. + impl TimeFormat { pub fn format_local(&self, time: Time) -> String { match *self {