@@ -16,6 +16,21 @@ use std::time::{SystemTime, UNIX_EPOCH};
1616use crate :: error:: { UResult , USimpleError } ;
1717use crate :: show_error;
1818
19+ #[ cfg( feature = "i18n-datetime" ) ]
20+ pub use crate :: i18n:: datetime:: NamePadding ;
21+
22+ /// Controls whether locale name lookups return raw or padded names.
23+ ///
24+ /// Without the `i18n-datetime` feature the parameter is accepted but ignored.
25+ #[ cfg( not( feature = "i18n-datetime" ) ) ]
26+ #[ derive( Clone , Copy ) ]
27+ pub enum NamePadding {
28+ /// Raw names with no trailing padding — for `date` and similar utilities.
29+ Raw ,
30+ /// Names padded to uniform display width — for columnar output like `ls`.
31+ Padded ,
32+ }
33+
1934/// Format the given date according to this time format style.
2035fn format_zoned < W : Write > ( out : & mut W , zoned : Zoned , fmt : & str ) -> UResult < ( ) > {
2136 let tm = BrokenDownTime :: from ( & zoned) ;
@@ -49,41 +64,79 @@ pub enum FormatSystemTimeFallback {
4964 Float , // Just print seconds+nanoseconds since epoch (`stat`)
5065}
5166
67+ /// Write the seconds-since-epoch fallback used when a `SystemTime` is out of
68+ /// the range representable by `jiff::Zoned`.
69+ fn write_fallback_seconds < W : Write > (
70+ out : & mut W ,
71+ time : SystemTime ,
72+ mode : FormatSystemTimeFallback ,
73+ ) -> UResult < ( ) > {
74+ // TODO: The range allowed by jiff is different from what GNU accepts,
75+ // but it still far enough in the future/past to be unlikely to matter:
76+ // jiff: Year between -9999 to 9999 (UTC) [-377705023201..=253402207200]
77+ // GNU: Year fits in signed 32 bits (timezone dependent)
78+ let ( mut secs, mut nsecs) = system_time_to_sec ( time) ;
79+ match mode {
80+ FormatSystemTimeFallback :: Integer => out. write_all ( secs. to_string ( ) . as_bytes ( ) ) ?,
81+ FormatSystemTimeFallback :: IntegerError => {
82+ let str = secs. to_string ( ) ;
83+ show_error ! ( "time '{str}' is out of range" ) ;
84+ out. write_all ( str. as_bytes ( ) ) ?;
85+ }
86+ FormatSystemTimeFallback :: Float => {
87+ if secs < 0 && nsecs != 0 {
88+ secs -= 1 ;
89+ nsecs = 1_000_000_000 - nsecs;
90+ }
91+ out. write_fmt ( format_args ! ( "{secs}.{nsecs:09}" ) ) ?;
92+ }
93+ }
94+ Ok ( ( ) )
95+ }
96+
5297/// Format a `SystemTime` according to given fmt, and append to vector out.
5398pub fn format_system_time < W : Write > (
5499 out : & mut W ,
55100 time : SystemTime ,
56101 fmt : & str ,
57102 mode : FormatSystemTimeFallback ,
58103) -> UResult < ( ) > {
59- let zoned: Result < Zoned , _ > = time. try_into ( ) ;
60- if let Ok ( zoned) = zoned {
61- format_zoned ( out, zoned, fmt)
62- } else {
104+ match time. try_into ( ) {
105+ Ok ( zoned) => format_zoned ( out, zoned, fmt) ,
63106 // Assume that if we cannot build a Zoned element, the timestamp is
64107 // out of reasonable range, just print it then.
65- // TODO: The range allowed by jiff is different from what GNU accepts,
66- // but it still far enough in the future/past to be unlikely to matter:
67- // jiff: Year between -9999 to 9999 (UTC) [-377705023201..=253402207200]
68- // GNU: Year fits in signed 32 bits (timezone dependent)
69- let ( mut secs, mut nsecs) = system_time_to_sec ( time) ;
70- match mode {
71- FormatSystemTimeFallback :: Integer => out. write_all ( secs. to_string ( ) . as_bytes ( ) ) ?,
72- FormatSystemTimeFallback :: IntegerError => {
73- let str = secs. to_string ( ) ;
74- show_error ! ( "time '{str}' is out of range" ) ;
75- out. write_all ( str. as_bytes ( ) ) ?;
76- }
77- FormatSystemTimeFallback :: Float => {
78- if secs < 0 && nsecs != 0 {
79- secs -= 1 ;
80- nsecs = 1_000_000_000 - nsecs;
81- }
82- out. write_fmt ( format_args ! ( "{secs}.{nsecs:09}" ) ) ?;
108+ Err ( _) => write_fallback_seconds ( out, time, mode) ,
109+ }
110+ }
111+
112+ /// Like [`format_system_time`], but when built with the `i18n-datetime`
113+ /// feature and a non-C `LC_TIME` locale is active, rewrites locale-dependent
114+ /// strftime directives (`%b`, `%B`, `%a`, `%A`, and `%Y`/`%m`/`%d`/`%e` for
115+ /// non-Gregorian calendars) to their localized values before formatting.
116+ /// For Gregorian locales, `%Y`/`%m`/`%d`/`%e` are unaffected (e.g. `en_US`
117+ /// still renders 2025 as `2025`).
118+ ///
119+ /// With the feature disabled or a C/POSIX locale, this is identical to
120+ /// `format_system_time`.
121+ pub fn format_system_time_locale_aware < W : Write > (
122+ out : & mut W ,
123+ time : SystemTime ,
124+ fmt : & str ,
125+ mode : FormatSystemTimeFallback ,
126+ padding : NamePadding ,
127+ ) -> UResult < ( ) > {
128+ #[ cfg( feature = "i18n-datetime" ) ]
129+ {
130+ use crate :: i18n:: datetime:: { localize_format_string, should_use_icu_locale} ;
131+ if should_use_icu_locale ( ) {
132+ if let Ok ( zoned) = <SystemTime as TryInto < Zoned > >:: try_into ( time) {
133+ let localized = localize_format_string ( fmt, zoned. date ( ) ) ;
134+ return format_zoned ( out, zoned, & localized) ;
83135 }
136+ // Out-of-range: fall through to the plain fallback below.
84137 }
85- Ok ( ( ) )
86138 }
139+ format_system_time ( out, time, fmt, mode)
87140}
88141
89142#[ cfg( test) ]
0 commit comments