55
66// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm stringly nohash strtime
77
8+ #[ cfg( unix) ]
89use std:: collections:: HashMap ;
9- use std:: iter;
1010#[ cfg( unix) ]
1111use std:: os:: unix:: fs:: { FileTypeExt , MetadataExt } ;
1212#[ cfg( windows) ]
1313use std:: os:: windows:: fs:: MetadataExt ;
14- use std:: { cell:: LazyCell , cell:: OnceCell , num:: IntErrorKind } ;
1514use std:: {
15+ cell:: { LazyCell , OnceCell } ,
1616 cmp:: Reverse ,
17+ collections:: HashSet ,
1718 ffi:: { OsStr , OsString } ,
1819 fmt:: Write as FmtWrite ,
1920 fs:: { self , DirEntry , FileType , Metadata , ReadDir } ,
20- io:: { BufWriter , ErrorKind , Stdout , Write , stdout} ,
21+ io:: { BufWriter , ErrorKind , IsTerminal , Stdout , Write , stdout} ,
22+ iter,
23+ num:: IntErrorKind ,
24+ ops:: RangeInclusive ,
2125 path:: { Path , PathBuf } ,
2226 time:: { Duration , SystemTime , UNIX_EPOCH } ,
2327} ;
24- use std:: { collections:: HashSet , io:: IsTerminal } ;
2528
2629use ansi_width:: ansi_width;
2730use clap:: {
@@ -32,12 +35,9 @@ use glob::{MatchOptions, Pattern};
3235use lscolors:: LsColors ;
3336use term_grid:: { DEFAULT_SEPARATOR_SIZE , Direction , Filling , Grid , GridOptions , SPACES_IN_TAB } ;
3437use thiserror:: Error ;
38+
3539#[ cfg( unix) ]
3640use uucore:: entries;
37- use uucore:: error:: USimpleError ;
38- use uucore:: format:: human:: { SizeFormat , human_readable} ;
39- use uucore:: fs:: FileInformation ;
40- use uucore:: fsext:: { MetadataTimeField , metadata_get_time} ;
4141#[ cfg( all( unix, not( any( target_os = "android" , target_os = "macos" ) ) ) ) ]
4242use uucore:: fsxattr:: has_acl;
4343#[ cfg( unix) ]
@@ -55,22 +55,25 @@ use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
5555 target_os = "solaris"
5656) ) ]
5757use uucore:: libc:: { dev_t, major, minor} ;
58- use uucore:: line_ending:: LineEnding ;
59- use uucore:: translate;
60-
61- use uucore:: quoting_style:: { QuotingStyle , locale_aware_escape_dir_name, locale_aware_escape_name} ;
62- use uucore:: time:: { FormatSystemTimeFallback , format_system_time} ;
6358use uucore:: {
6459 display:: Quotable ,
65- error:: { UError , UResult , set_exit_code} ,
60+ error:: { UError , UResult , USimpleError , set_exit_code} ,
61+ format:: human:: { SizeFormat , human_readable} ,
6662 format_usage,
63+ fs:: FileInformation ,
6764 fs:: display_permissions,
65+ fsext:: { MetadataTimeField , metadata_get_time} ,
66+ line_ending:: LineEnding ,
6867 os_str_as_bytes_lossy,
68+ parser:: parse_glob,
6969 parser:: parse_size:: parse_size_u64,
7070 parser:: shortcut_value_parser:: ShortcutValueParser ,
71+ quoting_style:: { QuotingStyle , locale_aware_escape_dir_name, locale_aware_escape_name} ,
72+ show, show_error, show_warning,
73+ time:: { FormatSystemTimeFallback , format, format_system_time} ,
74+ translate,
7175 version_cmp:: version_cmp,
7276} ;
73- use uucore:: { parser:: parse_glob, show, show_error, show_warning} ;
7477
7578mod dired;
7679use dired:: { DiredOutput , is_dired_arg_present} ;
@@ -203,8 +206,8 @@ enum LsError {
203206 #[ error( "{}" , translate!( "ls-error-not-listing-already-listed" , "path" => . 0 . to_string_lossy( ) ) ) ]
204207 AlreadyListedError ( PathBuf ) ,
205208
206- #[ error( "{}" , translate!( "ls-error-invalid-time-style" , "style" => . 0 . quote( ) , "values" => format! ( "{:?}" , . 1 ) ) ) ]
207- TimeStyleParseError ( String , Vec < String > ) ,
209+ #[ error( "{}" , translate!( "ls-error-invalid-time-style" , "style" => . 0 . quote( ) ) ) ]
210+ TimeStyleParseError ( String ) ,
208211}
209212
210213impl UError for LsError {
@@ -217,7 +220,7 @@ impl UError for LsError {
217220 Self :: BlockSizeParseError ( _) => 2 ,
218221 Self :: DiredAndZeroAreIncompatible => 2 ,
219222 Self :: AlreadyListedError ( _) => 2 ,
220- Self :: TimeStyleParseError ( _, _ ) => 2 ,
223+ Self :: TimeStyleParseError ( _) => 2 ,
221224 }
222225 }
223226}
@@ -250,53 +253,70 @@ enum Files {
250253}
251254
252255fn parse_time_style ( options : & clap:: ArgMatches ) -> Result < ( String , Option < String > ) , LsError > {
253- const TIME_STYLES : [ ( & str , ( & str , Option < & str > ) ) ; 4 ] = [
254- ( "full-iso" , ( "%Y-%m-%d %H:%M:%S.%f %z" , None ) ) ,
255- ( "long-iso" , ( "%Y-%m-%d %H:%M" , None ) ) ,
256- ( "iso" , ( "%m-%d %H:%M" , Some ( "%Y-%m-%d " ) ) ) ,
257- // TODO: Using correct locale string is not implemented.
258- ( "locale" , ( "%b %e %H:%M" , Some ( "%b %e %Y" ) ) ) ,
259- ] ;
260- // A map from a time-style parameter to a length-2 tuple of formats:
261- // the first one is used for recent dates, the second one for older ones (optional).
262- let time_styles = HashMap :: from ( TIME_STYLES ) ;
263- let possible_time_styles = TIME_STYLES
264- . iter ( )
265- . map ( |( x, _) | * x)
266- . chain ( iter:: once (
267- "+FORMAT (e.g., +%H:%M) for a 'date'-style format" ,
268- ) )
269- . map ( |s| s. to_string ( ) ) ;
256+ // TODO: Using correct locale string is not implemented.
257+ const LOCALE_FORMAT : ( & str , Option < & str > ) = ( "%b %e %H:%M" , Some ( "%b %e %Y" ) ) ;
270258
271259 // Convert time_styles references to owned String/option.
272260 fn ok ( ( recent, older) : ( & str , Option < & str > ) ) -> Result < ( String , Option < String > ) , LsError > {
273261 Ok ( ( recent. to_string ( ) , older. map ( String :: from) ) )
274262 }
275263
276- if let Some ( field) = options. get_one :: < String > ( options:: TIME_STYLE ) {
264+ if let Some ( field) = options
265+ . get_one :: < String > ( options:: TIME_STYLE )
266+ . map ( |s| s. to_owned ( ) )
267+ . or_else ( || std:: env:: var ( "TIME_STYLE" ) . ok ( ) )
268+ {
277269 //If both FULL_TIME and TIME_STYLE are present
278270 //The one added last is dominant
279271 if options. get_flag ( options:: FULL_TIME )
280272 && options. indices_of ( options:: FULL_TIME ) . unwrap ( ) . next_back ( )
281273 > options. indices_of ( options:: TIME_STYLE ) . unwrap ( ) . next_back ( )
282274 {
283- ok ( time_styles [ "full-iso" ] )
275+ ok ( ( format :: FULL_ISO , None ) )
284276 } else {
285- match time_styles. get ( field. as_str ( ) ) {
286- Some ( formats) => ok ( * formats) ,
287- None => match field. chars ( ) . next ( ) . unwrap ( ) {
288- '+' => Ok ( ( field[ 1 ..] . to_string ( ) , None ) ) ,
289- _ => Err ( LsError :: TimeStyleParseError (
290- String :: from ( field) ,
291- possible_time_styles. collect ( ) ,
292- ) ) ,
277+ let field = if let Some ( field) = field. strip_prefix ( "posix-" ) {
278+ // See GNU documentation, set format to "locale" if LC_TIME="POSIX",
279+ // else just strip the prefix and continue (even "posix+FORMAT" is
280+ // supported).
281+ // TODO: This needs to be moved to uucore and handled by icu?
282+ if std:: env:: var ( "LC_TIME" ) . unwrap_or_default ( ) == "POSIX"
283+ || std:: env:: var ( "LC_ALL" ) . unwrap_or_default ( ) == "POSIX"
284+ {
285+ return ok ( LOCALE_FORMAT ) ;
286+ }
287+ field
288+ } else {
289+ & field
290+ } ;
291+
292+ match field {
293+ "full-iso" => ok ( ( format:: FULL_ISO , None ) ) ,
294+ "long-iso" => ok ( ( format:: LONG_ISO , None ) ) ,
295+ // ISO older format needs extra padding.
296+ "iso" => Ok ( (
297+ "%m-%d %H:%M" . to_string ( ) ,
298+ Some ( format:: ISO . to_string ( ) + " " ) ,
299+ ) ) ,
300+ "locale" => ok ( LOCALE_FORMAT ) ,
301+ _ => match field. chars ( ) . next ( ) . unwrap ( ) {
302+ '+' => {
303+ // recent/older formats are (optionally) separated by a newline
304+ let mut it = field[ 1 ..] . split ( '\n' ) ;
305+ let recent = it. next ( ) . unwrap_or_default ( ) ;
306+ let older = it. next ( ) ;
307+ match it. next ( ) {
308+ None => ok ( ( recent, older) ) ,
309+ Some ( _) => Err ( LsError :: TimeStyleParseError ( String :: from ( field) ) ) ,
310+ }
311+ }
312+ _ => Err ( LsError :: TimeStyleParseError ( String :: from ( field) ) ) ,
293313 } ,
294314 }
295315 }
296316 } else if options. get_flag ( options:: FULL_TIME ) {
297- ok ( time_styles [ "full-iso" ] )
317+ ok ( ( format :: FULL_ISO , None ) )
298318 } else {
299- ok ( time_styles [ "locale" ] )
319+ ok ( LOCALE_FORMAT )
300320 }
301321}
302322
@@ -1941,7 +1961,7 @@ struct ListState<'a> {
19411961 uid_cache : HashMap < u32 , String > ,
19421962 #[ cfg( unix) ]
19431963 gid_cache : HashMap < u32 , String > ,
1944- recent_time_threshold : SystemTime ,
1964+ recent_time_range : RangeInclusive < SystemTime > ,
19451965}
19461966
19471967#[ allow( clippy:: cognitive_complexity) ]
@@ -1958,8 +1978,11 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> {
19581978 uid_cache : HashMap :: new ( ) ,
19591979 #[ cfg( unix) ]
19601980 gid_cache : HashMap :: new ( ) ,
1981+ // Time range for which to use the "recent" format. Anything from 0.5 year in the past to now
1982+ // (files with modification time in the future use "old" format).
19611983 // According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average.
1962- recent_time_threshold : SystemTime :: now ( ) - Duration :: new ( 31_556_952 / 2 , 0 ) ,
1984+ recent_time_range : ( SystemTime :: now ( ) - Duration :: new ( 31_556_952 / 2 , 0 ) )
1985+ ..=SystemTime :: now ( ) ,
19631986 } ;
19641987
19651988 for loc in locs {
@@ -2943,7 +2966,7 @@ fn display_date(
29432966 // Use "recent" format if the given date is considered recent (i.e., in the last 6 months),
29442967 // or if no "older" format is available.
29452968 let fmt = match & config. time_format_older {
2946- Some ( time_format_older) if time <= state. recent_time_threshold => time_format_older,
2969+ Some ( time_format_older) if ! state. recent_time_range . contains ( & time ) => time_format_older,
29472970 _ => & config. time_format_recent ,
29482971 } ;
29492972
0 commit comments