11use super :: scan;
22use super :: { ParseResult , INVALID , OUT_OF_RANGE } ;
3+ use crate :: CalendarDuration ;
4+
5+ /// Parser for the ISO 8601 duration format with designators.
6+ ///
7+ /// Supported formats:
8+ /// - `Pnn̲Ynn̲Mnn̲DTnn̲Hnn̲Mnn̲S`
9+ /// - `Pnn̲W`
10+ ///
11+ /// Any number-designator pair may be missing when zero, as long as there is at least one pair.
12+ /// The last pair may contain a decimal fraction instead of an integer.
13+ ///
14+ /// - Fractional years will be expressed in months.
15+ /// - Fractional weeks will be expressed in days.
16+ /// - Fractional hours, minutes or seconds will be expressed in minutes, seconds and nanoseconds.
17+ pub ( crate ) fn parse_iso8601_duration ( mut s : & str ) -> ParseResult < ( & str , CalendarDuration ) > {
18+ macro_rules! consume {
19+ ( $e: expr) => { {
20+ $e. map( |( s_, v) | {
21+ s = s_;
22+ v
23+ } )
24+ } } ;
25+ }
26+
27+ s = scan:: char ( s, b'P' ) ?;
28+ let mut duration = CalendarDuration :: new ( ) ;
29+
30+ let mut next = consume ! ( Decimal :: parse( s) ) . ok ( ) ;
31+ if let Some ( val) = next {
32+ if s. as_bytes ( ) . first ( ) == Some ( & b'W' ) {
33+ s = & s[ 1 ..] ;
34+ // Nothing is allowed after a week value
35+ return Ok ( ( s, duration. with_days ( val. mul ( 7 ) ?) ) ) ;
36+ }
37+ if s. as_bytes ( ) . first ( ) == Some ( & b'Y' ) {
38+ s = & s[ 1 ..] ;
39+ duration = duration. with_months ( val. mul ( 12 ) ?) ;
40+ if val. fraction . is_some ( ) {
41+ return Ok ( ( s, duration) ) ;
42+ }
43+ next = consume ! ( Decimal :: parse( s) ) . ok ( ) ;
44+ }
45+ }
46+
47+ if let Some ( val) = next {
48+ if s. as_bytes ( ) . first ( ) == Some ( & b'M' ) {
49+ s = & s[ 1 ..] ;
50+ let months = duration. months ( ) . checked_add ( val. integer ( ) ?) . ok_or ( OUT_OF_RANGE ) ?;
51+ duration = duration. with_months ( months) ;
52+ next = consume ! ( Decimal :: parse( s) ) . ok ( ) ;
53+ }
54+ }
55+
56+ if let Some ( val) = next {
57+ if s. as_bytes ( ) . first ( ) == Some ( & b'D' ) {
58+ s = & s[ 1 ..] ;
59+ duration = duration. with_days ( val. integer ( ) ?) ;
60+ next = None ;
61+ }
62+ }
63+
64+ if next. is_some ( ) {
65+ // We have numbers without a matching designator.
66+ return Err ( INVALID ) ;
67+ }
68+
69+ if s. as_bytes ( ) . first ( ) == Some ( & b'T' ) {
70+ duration = consume ! ( parse_iso8601_duration_time( s, duration) ) ?
71+ }
72+ Ok ( ( s, duration) )
73+ }
74+
75+ /// Parser for the time part of the ISO 8601 duration format with designators.
76+ pub ( crate ) fn parse_iso8601_duration_time (
77+ mut s : & str ,
78+ duration : CalendarDuration ,
79+ ) -> ParseResult < ( & str , CalendarDuration ) > {
80+ macro_rules! consume_or_return {
81+ ( $e: expr, $return: expr) => { {
82+ match $e {
83+ Ok ( ( s_, next) ) => {
84+ s = s_;
85+ next
86+ }
87+ Err ( _) => return $return,
88+ }
89+ } } ;
90+ }
91+ fn set_hms_nano (
92+ duration : CalendarDuration ,
93+ hours : u32 ,
94+ minutes : u32 ,
95+ seconds : u32 ,
96+ nanoseconds : u32 ,
97+ ) -> ParseResult < CalendarDuration > {
98+ let duration = match ( hours, minutes) {
99+ ( 0 , 0 ) => duration. with_seconds ( seconds) ,
100+ _ => duration. with_hms ( hours, minutes, seconds) . ok_or ( OUT_OF_RANGE ) ?,
101+ } ;
102+ Ok ( duration. with_nanos ( nanoseconds) . unwrap ( ) )
103+ }
104+
105+ s = scan:: char ( s, b'T' ) ?;
106+ let mut hours = 0 ;
107+ let mut minutes = 0 ;
108+ let mut incomplete = true ; // at least one component is required
109+
110+ let ( s_, mut next) = Decimal :: parse ( s) ?;
111+ s = s_;
112+ if s. as_bytes ( ) . first ( ) == Some ( & b'H' ) {
113+ s = & s[ 1 ..] ;
114+ incomplete = false ;
115+ match next. integer ( ) {
116+ Ok ( h) => hours = h,
117+ _ => {
118+ let ( secs, nanos) = next. mul_with_nanos ( 3600 ) ?;
119+ let mins = secs / 60 ;
120+ let secs = ( secs % 60 ) as u32 ;
121+ let minutes = u32:: try_from ( mins) . map_err ( |_| OUT_OF_RANGE ) ?;
122+ return Ok ( ( s, set_hms_nano ( duration, 0 , minutes, secs, nanos) ?) ) ;
123+ }
124+ }
125+ next = consume_or_return ! (
126+ Decimal :: parse( s) ,
127+ Ok ( ( s, set_hms_nano( duration, hours, minutes, 0 , 0 ) ?) )
128+ ) ;
129+ }
130+
131+ if s. as_bytes ( ) . first ( ) == Some ( & b'M' ) {
132+ s = & s[ 1 ..] ;
133+ incomplete = false ;
134+ match next. integer ( ) {
135+ Ok ( m) => minutes = m,
136+ _ => {
137+ let ( secs, nanos) = next. mul_with_nanos ( 60 ) ?;
138+ let mins = secs / 60 ;
139+ let secs = ( secs % 60 ) as u32 ;
140+ minutes = u32:: try_from ( mins) . map_err ( |_| OUT_OF_RANGE ) ?;
141+ return Ok ( ( s, set_hms_nano ( duration, hours, minutes, secs, nanos) ?) ) ;
142+ }
143+ }
144+ next = consume_or_return ! (
145+ Decimal :: parse( s) ,
146+ Ok ( ( s, set_hms_nano( duration, hours, minutes, 0 , 0 ) ?) )
147+ ) ;
148+ }
149+
150+ if s. as_bytes ( ) . first ( ) == Some ( & b'S' ) {
151+ s = & s[ 1 ..] ;
152+ let ( secs, nanos) = next. mul_with_nanos ( 1 ) ?;
153+ let secs = u32:: try_from ( secs) . map_err ( |_| OUT_OF_RANGE ) ?;
154+ return Ok ( ( s, set_hms_nano ( duration, hours, minutes, secs, nanos) ?) ) ;
155+ }
156+
157+ if incomplete {
158+ return Err ( INVALID ) ;
159+ }
160+ Ok ( ( s, set_hms_nano ( duration, hours, minutes, 0 , 0 ) ?) )
161+ }
3162
4163/// Helper type for parsing decimals (as in an ISO 8601 duration).
5164#[ derive( Copy , Clone ) ]
@@ -96,7 +255,7 @@ impl Fraction {
96255 let huge = self . 0 * unit + div / 2 ;
97256 let whole = huge / POW10 [ 15 ] ;
98257 let fraction_as_nanos = ( huge % POW10 [ 15 ] ) / div;
99- dbg ! ( whole as i64 , fraction_as_nanos as i64 )
258+ ( whole as i64 , fraction_as_nanos as i64 )
100259 }
101260}
102261
@@ -121,8 +280,9 @@ const POW10: [u64; 16] = [
121280
122281#[ cfg( test) ]
123282mod tests {
124- use super :: Fraction ;
125- use crate :: format:: INVALID ;
283+ use super :: { parse_iso8601_duration, parse_iso8601_duration_time, Fraction } ;
284+ use crate :: format:: { INVALID , OUT_OF_RANGE , TOO_SHORT } ;
285+ use crate :: CalendarDuration ;
126286
127287 #[ test]
128288 fn test_parse_fraction ( ) {
@@ -138,4 +298,128 @@ mod tests {
138298 let ( _, fraction) = Fraction :: parse ( ",5" ) . unwrap ( ) ;
139299 assert_eq ! ( fraction. mul_with_nanos( 1 ) , ( 0 , 500_000_000 ) ) ;
140300 }
301+
302+ #[ test]
303+ fn test_parse_duration_time ( ) {
304+ let parse = parse_iso8601_duration_time;
305+ let d = CalendarDuration :: new ( ) ;
306+
307+ assert_eq ! ( parse( "T12H" , d) , Ok ( ( "" , d. with_hms( 12 , 0 , 0 ) . unwrap( ) ) ) ) ;
308+ assert_eq ! ( parse( "T12.25H" , d) , Ok ( ( "" , d. with_hms( 12 , 15 , 0 ) . unwrap( ) ) ) ) ;
309+ assert_eq ! ( parse( "T12,25H" , d) , Ok ( ( "" , d. with_hms( 12 , 15 , 0 ) . unwrap( ) ) ) ) ;
310+ assert_eq ! ( parse( "T34M" , d) , Ok ( ( "" , d. with_hms( 0 , 34 , 0 ) . unwrap( ) ) ) ) ;
311+ assert_eq ! ( parse( "T34.25M" , d) , Ok ( ( "" , d. with_hms( 0 , 34 , 15 ) . unwrap( ) ) ) ) ;
312+ assert_eq ! ( parse( "T56S" , d) , Ok ( ( "" , d. with_seconds( 56 ) ) ) ) ;
313+ assert_eq ! ( parse( "T0.789S" , d) , Ok ( ( "" , d. with_millis( 789 ) . unwrap( ) ) ) ) ;
314+ assert_eq ! ( parse( "T0,789S" , d) , Ok ( ( "" , d. with_millis( 789 ) . unwrap( ) ) ) ) ;
315+ assert_eq ! ( parse( "T12H34M" , d) , Ok ( ( "" , d. with_hms( 12 , 34 , 0 ) . unwrap( ) ) ) ) ;
316+ assert_eq ! ( parse( "T12H34M60S" , d) , Ok ( ( "" , d. with_hms( 12 , 34 , 60 ) . unwrap( ) ) ) ) ;
317+ assert_eq ! (
318+ parse( "T12H34M56.789S" , d) ,
319+ Ok ( ( "" , d. with_hms( 12 , 34 , 56 ) . unwrap( ) . with_millis( 789 ) . unwrap( ) ) )
320+ ) ;
321+ assert_eq ! ( parse( "T12H56S" , d) , Ok ( ( "" , d. with_hms( 12 , 0 , 56 ) . unwrap( ) ) ) ) ;
322+ assert_eq ! ( parse( "T34M56S" , d) , Ok ( ( "" , d. with_hms( 0 , 34 , 56 ) . unwrap( ) ) ) ) ;
323+
324+ // Data after a fraction is ignored
325+ assert_eq ! ( parse( "T12.5H16M" , d) , Ok ( ( "16M" , d. with_hms( 12 , 30 , 0 ) . unwrap( ) ) ) ) ;
326+ assert_eq ! ( parse( "T12H16.5M30S" , d) , Ok ( ( "30S" , d. with_hms( 12 , 16 , 30 ) . unwrap( ) ) ) ) ;
327+
328+ // Zero values
329+ assert_eq ! ( parse( "T0H" , d) , Ok ( ( "" , d) ) ) ;
330+ assert_eq ! ( parse( "T0M" , d) , Ok ( ( "" , d) ) ) ;
331+ assert_eq ! ( parse( "T0S" , d) , Ok ( ( "" , d) ) ) ;
332+ assert_eq ! ( parse( "T0,0S" , d) , Ok ( ( "" , d) ) ) ;
333+
334+ // Empty or invalid values
335+ assert_eq ! ( parse( "T" , d) , Err ( TOO_SHORT ) ) ;
336+ assert_eq ! ( parse( "TH" , d) , Err ( INVALID ) ) ;
337+ assert_eq ! ( parse( "TM" , d) , Err ( INVALID ) ) ;
338+ assert_eq ! ( parse( "TS" , d) , Err ( INVALID ) ) ;
339+ assert_eq ! ( parse( "T.5S" , d) , Err ( INVALID ) ) ;
340+ assert_eq ! ( parse( "T,5S" , d) , Err ( INVALID ) ) ;
341+
342+ // Date components
343+ assert_eq ! ( parse( "T5W" , d) , Err ( INVALID ) ) ;
344+ assert_eq ! ( parse( "T5Y" , d) , Err ( INVALID ) ) ;
345+ assert_eq ! ( parse( "T5D" , d) , Err ( INVALID ) ) ;
346+
347+ // Max values
348+ assert_eq ! ( parse( "T1118481H" , d) , Ok ( ( "" , d. with_hms( 1118481 , 0 , 0 ) . unwrap( ) ) ) ) ;
349+ assert_eq ! ( parse( "T1118482H" , d) , Err ( OUT_OF_RANGE ) ) ;
350+ assert_eq ! ( parse( "T1118481.05H" , d) , Ok ( ( "" , d. with_hms( 1118481 , 3 , 0 ) . unwrap( ) ) ) ) ;
351+ assert_eq ! ( parse( "T1118481.5H" , d) , Err ( OUT_OF_RANGE ) ) ;
352+ assert_eq ! ( parse( "T67108863M" , d) , Ok ( ( "" , d. with_hms( 0 , u32 :: MAX >> 6 , 0 ) . unwrap( ) ) ) ) ;
353+ assert_eq ! ( parse( "T67108864M" , d) , Err ( OUT_OF_RANGE ) ) ;
354+ assert_eq ! ( parse( "T67108863.25M" , d) , Ok ( ( "" , d. with_hms( 0 , u32 :: MAX >> 6 , 15 ) . unwrap( ) ) ) ) ;
355+ assert_eq ! ( parse( "T4294967295S" , d) , Ok ( ( "" , d. with_seconds( u32 :: MAX ) ) ) ) ;
356+ assert_eq ! ( parse( "T4294967296S" , d) , Err ( OUT_OF_RANGE ) ) ;
357+ assert_eq ! (
358+ parse( "T4294967295.25S" , d) ,
359+ Ok ( ( "" , d. with_seconds( u32 :: MAX ) . with_millis( 250 ) . unwrap( ) ) )
360+ ) ;
361+ assert_eq ! (
362+ parse( "T4294967295.999999999S" , d) ,
363+ Ok ( ( "" , d. with_seconds( u32 :: MAX ) . with_nanos( 999_999_999 ) . unwrap( ) ) )
364+ ) ;
365+ assert_eq ! ( parse( "T4294967295.9999999995S" , d) , Err ( OUT_OF_RANGE ) ) ;
366+ assert_eq ! ( parse( "T12H34M61S" , d) , Err ( OUT_OF_RANGE ) ) ;
367+ }
368+
369+ #[ test]
370+ fn test_parse_duration ( ) {
371+ let d = CalendarDuration :: new ( ) ;
372+ assert_eq ! (
373+ parse_iso8601_duration( "P12Y" ) ,
374+ Ok ( ( "" , d. with_years_and_months( 12 , 0 ) . unwrap( ) ) )
375+ ) ;
376+ assert_eq ! ( parse_iso8601_duration( "P34M" ) , Ok ( ( "" , d. with_months( 34 ) ) ) ) ;
377+ assert_eq ! ( parse_iso8601_duration( "P56D" ) , Ok ( ( "" , d. with_days( 56 ) ) ) ) ;
378+ assert_eq ! ( parse_iso8601_duration( "P78W" ) , Ok ( ( "" , d. with_weeks_and_days( 78 , 0 ) . unwrap( ) ) ) ) ;
379+
380+ // Fractional date values
381+ assert_eq ! (
382+ parse_iso8601_duration( "P1.25Y" ) ,
383+ Ok ( ( "" , d. with_years_and_months( 1 , 3 ) . unwrap( ) ) )
384+ ) ;
385+ assert_eq ! (
386+ parse_iso8601_duration( "P1.99Y" ) ,
387+ Ok ( ( "" , d. with_years_and_months( 2 , 0 ) . unwrap( ) ) )
388+ ) ;
389+ assert_eq ! ( parse_iso8601_duration( "P1.4W" ) , Ok ( ( "" , d. with_days( 10 ) ) ) ) ;
390+ assert_eq ! ( parse_iso8601_duration( "P1.95W" ) , Ok ( ( "" , d. with_days( 14 ) ) ) ) ;
391+ assert_eq ! ( parse_iso8601_duration( "P1.5M" ) , Err ( INVALID ) ) ;
392+ assert_eq ! ( parse_iso8601_duration( "P1.5D" ) , Err ( INVALID ) ) ;
393+
394+ // Data after a fraction is ignored
395+ assert_eq ! (
396+ parse_iso8601_duration( "P1.25Y5D" ) ,
397+ Ok ( ( "5D" , d. with_years_and_months( 1 , 3 ) . unwrap( ) ) )
398+ ) ;
399+ assert_eq ! (
400+ parse_iso8601_duration( "P1.25YT3H" ) ,
401+ Ok ( ( "T3H" , d. with_years_and_months( 1 , 3 ) . unwrap( ) ) )
402+ ) ;
403+
404+ // Zero values
405+ assert_eq ! ( parse_iso8601_duration( "P0Y" ) , Ok ( ( "" , d) ) ) ;
406+ assert_eq ! ( parse_iso8601_duration( "P0M" ) , Ok ( ( "" , d) ) ) ;
407+ assert_eq ! ( parse_iso8601_duration( "P0W" ) , Ok ( ( "" , d) ) ) ;
408+ assert_eq ! ( parse_iso8601_duration( "P0D" ) , Ok ( ( "" , d) ) ) ;
409+ assert_eq ! ( parse_iso8601_duration( "PT0M" ) , Ok ( ( "" , d) ) ) ;
410+ assert_eq ! ( parse_iso8601_duration( "PT0S" ) , Ok ( ( "" , d) ) ) ;
411+
412+ // Invalid designator at a position where another designator can be expected.
413+ assert_eq ! ( parse_iso8601_duration( "P12Y12Y" ) , Err ( INVALID ) ) ;
414+ assert_eq ! ( parse_iso8601_duration( "P12M12M" ) , Err ( INVALID ) ) ;
415+ assert_eq ! ( parse_iso8601_duration( "P12M12Y" ) , Err ( INVALID ) ) ;
416+
417+ // Trailing data
418+ assert_eq ! (
419+ parse_iso8601_duration( "P12W34D" ) ,
420+ Ok ( ( "34D" , d. with_weeks_and_days( 12 , 0 ) . unwrap( ) ) )
421+ ) ;
422+ assert_eq ! ( parse_iso8601_duration( "P12D12D" ) , Ok ( ( "12D" , d. with_days( 12 ) ) ) ) ;
423+ assert_eq ! ( parse_iso8601_duration( "P12D12Y" ) , Ok ( ( "12Y" , d. with_days( 12 ) ) ) ) ;
424+ }
141425}
0 commit comments