3333//! - `%^B`: Month name in uppercase (JUNE)
3434//! - `%+4C`: Century with sign, padded to 4 characters (+019)
3535
36- use fluent:: FluentArgs ;
3736use jiff:: Zoned ;
3837use jiff:: fmt:: strtime:: { BrokenDownTime , Config , PosixCustom } ;
3938use regex:: Regex ;
4039use std:: fmt;
4140use std:: sync:: OnceLock ;
42- use uucore:: locale :: get_message_with_args ;
41+ use uucore:: translate ;
4342
4443/// Error type for format modifier operations
4544#[ derive( Debug ) ]
4645pub enum FormatError {
4746 /// Error from the underlying jiff library
4847 JiffError ( jiff:: Error ) ,
4948 /// Field width calculation overflowed or required allocation failed
50- FieldWidthTooLarge { width : String , specifier : String } ,
49+ FieldWidthTooLarge { width : usize , specifier : String } ,
5150}
5251
5352impl fmt:: Display for FormatError {
5453 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
5554 match self {
5655 Self :: JiffError ( e) => write ! ( f, "{e}" ) ,
57- Self :: FieldWidthTooLarge { width, specifier } => {
58- let mut args = FluentArgs :: new ( ) ;
59- args. set ( "width" , width. clone ( ) ) ;
60- args. set ( "specifier" , specifier. clone ( ) ) ;
61- write ! (
62- f,
63- "{}" ,
64- get_message_with_args( "date-error-format-modifier-width-too-large" , args)
56+ Self :: FieldWidthTooLarge { width, specifier } => write ! (
57+ f,
58+ "{}" ,
59+ translate!(
60+ "date-error-format-modifier-width-too-large" ,
61+ "width" => width,
62+ "specifier" => specifier
6563 )
66- }
64+ ) ,
6765 }
6866 }
6967}
@@ -155,16 +153,7 @@ fn format_with_modifiers(
155153 // Check if this specifier has modifiers
156154 if !flags. is_empty ( ) || !width_str. is_empty ( ) {
157155 // Apply modifiers to the formatted value
158- let width = if width_str. is_empty ( ) {
159- 0
160- } else {
161- width_str
162- . parse ( )
163- . map_err ( |_| FormatError :: FieldWidthTooLarge {
164- width : width_str. to_string ( ) ,
165- specifier : spec. to_string ( ) ,
166- } ) ?
167- } ;
156+ let width: usize = width_str. parse ( ) . unwrap_or ( 0 ) ;
168157 let explicit_width = !width_str. is_empty ( ) ;
169158 let modified = apply_modifiers ( & formatted, flags, width, spec, explicit_width) ?;
170159 result. push_str ( & modified) ;
@@ -406,38 +395,14 @@ fn apply_modifiers(
406395 // Zero padding: sign first, then zeros (e.g., "-0022")
407396 let sign = result. chars ( ) . next ( ) . unwrap ( ) ;
408397 let rest = & result[ 1 ..] ;
409- let target_len = result. len ( ) . checked_add ( padding) . ok_or_else ( || {
410- FormatError :: FieldWidthTooLarge {
411- width : width. to_string ( ) ,
412- specifier : specifier. to_string ( ) ,
413- }
414- } ) ?;
415- let mut padded = String :: new ( ) ;
416- padded
417- . try_reserve ( target_len)
418- . map_err ( |_| FormatError :: FieldWidthTooLarge {
419- width : width. to_string ( ) ,
420- specifier : specifier. to_string ( ) ,
421- } ) ?;
398+ let mut padded = try_alloc_padded ( result. len ( ) , padding, effective_width, specifier) ?;
422399 padded. push ( sign) ;
423400 padded. extend ( std:: iter:: repeat_n ( '0' , padding) ) ;
424401 padded. push_str ( rest) ;
425402 result = padded;
426403 } else {
427404 // Default: pad on the left (e.g., " -22" or " 1999")
428- let target_len = result. len ( ) . checked_add ( padding) . ok_or_else ( || {
429- FormatError :: FieldWidthTooLarge {
430- width : width. to_string ( ) ,
431- specifier : specifier. to_string ( ) ,
432- }
433- } ) ?;
434- let mut padded = String :: new ( ) ;
435- padded
436- . try_reserve ( target_len)
437- . map_err ( |_| FormatError :: FieldWidthTooLarge {
438- width : width. to_string ( ) ,
439- specifier : specifier. to_string ( ) ,
440- } ) ?;
405+ let mut padded = try_alloc_padded ( result. len ( ) , padding, effective_width, specifier) ?;
441406 padded. extend ( std:: iter:: repeat_n ( pad_char, padding) ) ;
442407 padded. push_str ( & result) ;
443408 result = padded;
@@ -447,6 +412,30 @@ fn apply_modifiers(
447412 Ok ( result)
448413}
449414
415+ /// Allocate a `String` with enough capacity for `current_len + padding`,
416+ /// returning `FieldWidthTooLarge` on arithmetic overflow or allocation failure.
417+ fn try_alloc_padded (
418+ current_len : usize ,
419+ padding : usize ,
420+ width : usize ,
421+ specifier : & str ,
422+ ) -> Result < String , FormatError > {
423+ let target_len =
424+ current_len
425+ . checked_add ( padding)
426+ . ok_or_else ( || FormatError :: FieldWidthTooLarge {
427+ width,
428+ specifier : specifier. to_string ( ) ,
429+ } ) ?;
430+ let mut s = String :: new ( ) ;
431+ s. try_reserve ( target_len)
432+ . map_err ( |_| FormatError :: FieldWidthTooLarge {
433+ width,
434+ specifier : specifier. to_string ( ) ,
435+ } ) ?;
436+ Ok ( s)
437+ }
438+
450439#[ cfg( test) ]
451440mod tests {
452441 use super :: * ;
@@ -734,6 +723,16 @@ mod tests {
734723 }
735724 }
736725
726+ #[ test]
727+ fn test_apply_modifiers_width_too_large ( ) {
728+ let err = apply_modifiers ( "x" , "" , usize:: MAX , "c" , true ) . unwrap_err ( ) ;
729+ assert ! ( matches!(
730+ err,
731+ FormatError :: FieldWidthTooLarge { width, specifier }
732+ if width == usize :: MAX && specifier == "c"
733+ ) ) ;
734+ }
735+
737736 #[ test]
738737 fn test_underscore_flag_without_width ( ) {
739738 // %_m should pad month to default width 2 with spaces
@@ -743,7 +742,8 @@ mod tests {
743742 // %_H should pad hour to default width 2 with spaces
744743 assert_eq ! ( apply_modifiers( "5" , "_" , 0 , "H" , false ) . unwrap( ) , " 5" ) ;
745744 // %_Y should pad year to default width 4 with spaces
746- assert_eq ! ( apply_modifiers( "1999" , "_" , 0 , "Y" , false ) . unwrap( ) , "1999" ) ; // already at default width
745+ assert_eq ! ( apply_modifiers( "1999" , "_" , 0 , "Y" , false ) . unwrap( ) , "1999" ) ;
746+ // already at default width
747747 }
748748
749749 #[ test]
@@ -793,14 +793,4 @@ mod tests {
793793 "GNU: %_C should produce '19', not ' 19' (default width is 2, not 4)"
794794 ) ;
795795 }
796-
797- #[ test]
798- fn test_apply_modifiers_width_too_large ( ) {
799- let err = apply_modifiers ( "x" , "" , usize:: MAX , "c" , true ) . unwrap_err ( ) ;
800- assert ! ( matches!(
801- err,
802- FormatError :: FieldWidthTooLarge { width, specifier }
803- if width == usize :: MAX . to_string( ) && specifier == "c"
804- ) ) ;
805- }
806796}
0 commit comments