@@ -64,8 +64,98 @@ cfg_langinfo! {
6464 } )
6565 }
6666
67- /// Retrieves the date/time format string from the system locale
68- fn get_locale_format_string( ) -> Option <String > {
67+ /// Replaces %c, %x, %X with their locale-specific format strings.
68+ ///
69+ /// If a flag like `^` is present (e.g., `%^c`), it is distributed to the
70+ /// sub-specifiers within the locale string.
71+ pub fn expand_locale_format( format: & str ) -> std:: borrow:: Cow <' _, str > {
72+ let mut result = String :: with_capacity( format. len( ) ) ;
73+ let mut chars = format. chars( ) . peekable( ) ;
74+ let mut modified = false ;
75+
76+ while let Some ( c) = chars. next( ) {
77+ if c != '%' {
78+ result. push( c) ;
79+ continue ;
80+ }
81+
82+ // Capture flags
83+ let mut flags = Vec :: new( ) ;
84+ while let Some ( & peek) = chars. peek( ) {
85+ match peek {
86+ '_' | '-' | '0' | '^' | '#' => {
87+ flags. push( peek) ;
88+ chars. next( ) ;
89+ } ,
90+ _ => break ,
91+ }
92+ }
93+
94+ match chars. peek( ) {
95+ Some ( & spec @ ( 'c' | 'x' | 'X' ) ) => {
96+ chars. next( ) ;
97+
98+ let item = match spec {
99+ 'c' => libc:: D_T_FMT ,
100+ 'x' => libc:: D_FMT ,
101+ 'X' => libc:: T_FMT ,
102+ _ => unreachable!( ) ,
103+ } ;
104+
105+ if let Some ( s) = get_langinfo( item) {
106+ // If the user requested uppercase (%^c), distribute that flag
107+ // to the expanded specifiers.
108+ let replacement = if flags. contains( & '^' ) {
109+ distribute_flag( & s, '^' )
110+ } else {
111+ s
112+ } ;
113+ result. push_str( & replacement) ;
114+ modified = true ;
115+ } else {
116+ // Reconstruct original sequence if lookup fails
117+ result. push( '%' ) ;
118+ result. extend( flags) ;
119+ result. push( spec) ;
120+ }
121+ } ,
122+ Some ( _) | None => {
123+ // Not a locale specifier, or end of string.
124+ // Push captured flags and let loop handle the next char.
125+ result. push( '%' ) ;
126+ result. extend( flags) ;
127+ }
128+ }
129+ }
130+
131+ if modified {
132+ std:: borrow:: Cow :: Owned ( result)
133+ } else {
134+ std:: borrow:: Cow :: Borrowed ( format)
135+ }
136+ }
137+
138+ fn distribute_flag( fmt: & str , flag: char ) -> String {
139+ let mut res = String :: with_capacity( fmt. len( ) * 2 ) ;
140+ let mut chars = fmt. chars( ) . peekable( ) ;
141+ while let Some ( c) = chars. next( ) {
142+ res. push( c) ;
143+ if c == '%' {
144+ if let Some ( & n) = chars. peek( ) {
145+ if n == '%' {
146+ chars. next( ) ;
147+ res. push( '%' ) ;
148+ } else {
149+ res. push( flag) ;
150+ }
151+ }
152+ }
153+ }
154+ res
155+ }
156+
157+ /// Retrieves the date/time format string from the system locale (D_T_FMT, D_FMT, T_FMT)
158+ pub fn get_langinfo( item: libc:: nl_item) -> Option <String > {
69159 // In tests, acquire mutex to prevent race conditions with setlocale()
70160 // which is process-global and not thread-safe
71161 #[ cfg( test) ]
@@ -76,12 +166,12 @@ cfg_langinfo! {
76166 libc:: setlocale( libc:: LC_TIME , c"" . as_ptr( ) ) ;
77167
78168 // Get the date/time format string
79- let d_t_fmt_ptr = libc:: nl_langinfo( libc :: D_T_FMT ) ;
80- if d_t_fmt_ptr . is_null( ) {
169+ let fmt_ptr = libc:: nl_langinfo( item ) ;
170+ if fmt_ptr . is_null( ) {
81171 return None ;
82172 }
83173
84- let format = CStr :: from_ptr( d_t_fmt_ptr ) . to_str( ) . ok( ) ?;
174+ let format = CStr :: from_ptr( fmt_ptr ) . to_str( ) . ok( ) ?;
85175 if format. is_empty( ) {
86176 return None ;
87177 }
@@ -90,6 +180,11 @@ cfg_langinfo! {
90180 }
91181 }
92182
183+ /// Retrieves the date/time format string from the system locale
184+ fn get_locale_format_string( ) -> Option <String > {
185+ get_langinfo( libc:: D_T_FMT )
186+ }
187+
93188 /// Ensures the format string includes timezone (%Z)
94189 fn ensure_timezone_in_format( format: & str ) -> String {
95190 if format. contains( "%Z" ) {
@@ -123,6 +218,18 @@ pub fn get_locale_default_format() -> &'static str {
123218 "%a %b %e %X %Z %Y"
124219}
125220
221+ #[ cfg( not( any(
222+ target_os = "linux" ,
223+ target_vendor = "apple" ,
224+ target_os = "freebsd" ,
225+ target_os = "netbsd" ,
226+ target_os = "openbsd" ,
227+ target_os = "dragonfly"
228+ ) ) ) ]
229+ pub fn expand_locale_format ( format : & str ) -> std:: borrow:: Cow < ' _ , str > {
230+ std:: borrow:: Cow :: Borrowed ( format)
231+ }
232+
126233#[ cfg( test) ]
127234mod tests {
128235 cfg_langinfo ! {
0 commit comments