1- use crate :: platform:: { prelude:: * , Duration } ;
1+ use crate :: {
2+ platform:: { prelude:: * , Duration } ,
3+ util:: ascii_char:: AsciiChar ,
4+ } ;
25use core:: {
3- num:: { ParseFloatError , ParseIntError } ,
6+ num:: ParseIntError ,
47 ops:: { Add , AddAssign , Neg , Sub , SubAssign } ,
58 str:: FromStr ,
69} ;
@@ -73,15 +76,17 @@ impl TimeSpan {
7376pub enum ParseError {
7477 /// An empty string is not a valid Time Span.
7578 Empty ,
76- /// The seconds need to be a finite number.
77- Finite ,
78- /// Couldn't parse the seconds.
79- Seconds {
79+ /// The time is too large to be represented.
80+ Overflow ,
81+ /// The fractional part contains characters that are not digits.
82+ FractionDigits ,
83+ /// Couldn't parse the fractional part.
84+ Fraction {
8085 /// The underlying error.
81- source : ParseFloatError ,
86+ source : ParseIntError ,
8287 } ,
83- /// Couldn't parse the minutes or hours.
84- MinutesOrHours {
88+ /// Couldn't parse the seconds, minutes, or hours.
89+ Time {
8590 /// The underlying error.
8691 source : ParseIntError ,
8792 } ,
@@ -94,27 +99,50 @@ impl FromStr for TimeSpan {
9499 // It's faster to use `strip_prefix` with char literals if it's an ASCII
95100 // char, otherwise prefer using string literals.
96101 #[ allow( clippy:: single_char_pattern) ]
97- let factor =
102+ let negate =
98103 if let Some ( remainder) = text. strip_prefix ( '-' ) . or_else ( || text. strip_prefix ( "−" ) ) {
99104 text = remainder;
100- - 1.0
105+ true
101106 } else {
102- 1.0
107+ false
103108 } ;
104109
105- let mut pieces = text. split ( ':' ) ;
110+ let ( seconds_text, nanos) =
111+ if let Some ( ( seconds, mut nanos) ) = AsciiChar :: DOT . split_once ( text) {
112+ if nanos. len ( ) > 9 {
113+ nanos = nanos. get ( ..9 ) . context ( FractionDigits ) ?;
114+ }
115+ (
116+ seconds,
117+ nanos. parse :: < u32 > ( ) . context ( Fraction ) ? * 10_u32 . pow ( 9 - nanos. len ( ) as u32 ) ,
118+ )
119+ } else {
120+ ( text, 0u32 )
121+ } ;
106122
107- let last = pieces. next_back ( ) . context ( Empty ) ?;
108- let seconds = last. parse :: < f64 > ( ) . context ( Seconds ) ?;
123+ ensure ! ( !seconds_text. is_empty( ) , Empty ) ;
124+
125+ let mut seconds = 0u64 ;
126+
127+ for split in AsciiChar :: COLON . split_iter ( seconds_text) {
128+ seconds = seconds
129+ . checked_mul ( 60 )
130+ . context ( Overflow ) ?
131+ . checked_add ( split. parse :: < u64 > ( ) . context ( Time ) ?)
132+ . context ( Overflow ) ?;
133+ }
109134
110- ensure ! ( seconds. is_finite( ) && seconds >= 0.0 , Finite ) ;
135+ let ( mut seconds, mut nanos) = (
136+ i64:: try_from ( seconds) . ok ( ) . context ( Overflow ) ?,
137+ i32:: try_from ( nanos) . ok ( ) . context ( Overflow ) ?,
138+ ) ;
111139
112- let mut minutes = 0.0 ;
113- for split in pieces {
114- minutes = minutes * 60.0 + split . parse :: < u32 > ( ) . context ( MinutesOrHours ) ? as f64 ;
140+ if negate {
141+ seconds = -seconds ;
142+ nanos = -nanos ;
115143 }
116144
117- Ok ( TimeSpan :: from_seconds ( factor * ( seconds + minutes * 60.0 ) ) )
145+ Ok ( Duration :: new ( seconds, nanos ) . into ( ) )
118146 }
119147}
120148
@@ -205,19 +233,35 @@ mod tests {
205233
206234 #[ test]
207235 fn parsing ( ) {
208- "-12:37:30.12" . parse :: < TimeSpan > ( ) . unwrap ( ) ;
209- "-37:30.12" . parse :: < TimeSpan > ( ) . unwrap ( ) ;
210- "-30.12" . parse :: < TimeSpan > ( ) . unwrap ( ) ;
211- "-10:30" . parse :: < TimeSpan > ( ) . unwrap ( ) ;
212- "-30" . parse :: < TimeSpan > ( ) . unwrap ( ) ;
213- "-100" . parse :: < TimeSpan > ( ) . unwrap ( ) ;
214- "--30" . parse :: < TimeSpan > ( ) . unwrap_err ( ) ;
215- "-" . parse :: < TimeSpan > ( ) . unwrap_err ( ) ;
216- "" . parse :: < TimeSpan > ( ) . unwrap_err ( ) ;
217- "-10:-30" . parse :: < TimeSpan > ( ) . unwrap_err ( ) ;
218- "10:-30" . parse :: < TimeSpan > ( ) . unwrap_err ( ) ;
219- "10.5:30.5" . parse :: < TimeSpan > ( ) . unwrap_err ( ) ;
220- "NaN" . parse :: < TimeSpan > ( ) . unwrap_err ( ) ;
221- "Inf" . parse :: < TimeSpan > ( ) . unwrap_err ( ) ;
236+ TimeSpan :: from_str ( "-12:37:30.12" ) . unwrap ( ) ;
237+ TimeSpan :: from_str ( "-37:30.12" ) . unwrap ( ) ;
238+ TimeSpan :: from_str ( "-30.12" ) . unwrap ( ) ;
239+ TimeSpan :: from_str ( "-10:30" ) . unwrap ( ) ;
240+ TimeSpan :: from_str ( "-30" ) . unwrap ( ) ;
241+ TimeSpan :: from_str ( "-100" ) . unwrap ( ) ;
242+ TimeSpan :: from_str ( "--30" ) . unwrap_err ( ) ;
243+ TimeSpan :: from_str ( "-" ) . unwrap_err ( ) ;
244+ TimeSpan :: from_str ( "" ) . unwrap_err ( ) ;
245+ TimeSpan :: from_str ( "-10:-30" ) . unwrap_err ( ) ;
246+ TimeSpan :: from_str ( "10:-30" ) . unwrap_err ( ) ;
247+ TimeSpan :: from_str ( "10.5:30.5" ) . unwrap_err ( ) ;
248+ TimeSpan :: from_str ( "NaN" ) . unwrap_err ( ) ;
249+ TimeSpan :: from_str ( "Inf" ) . unwrap_err ( ) ;
250+ assert ! ( matches!(
251+ TimeSpan :: from_str( "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1" ) ,
252+ Err ( ParseError :: Overflow ) ,
253+ ) ) ;
254+ assert_eq ! (
255+ TimeSpan :: from_str( "10.123456789" )
256+ . unwrap( )
257+ . to_seconds_and_subsec_nanoseconds( ) ,
258+ ( 10 , 123456789 )
259+ ) ;
260+ assert_eq ! (
261+ TimeSpan :: from_str( "10.0987654321987654321" )
262+ . unwrap( )
263+ . to_seconds_and_subsec_nanoseconds( ) ,
264+ ( 10 , 98765432 )
265+ ) ;
222266 }
223267}
0 commit comments