1- //! Utility functions around the regexes we use for parsing rrule strings.
1+ //! Utility functions we use for parsing rrule strings.
22
3- use std:: { str:: FromStr , sync:: OnceLock } ;
4-
5- use regex:: { Captures , Regex } ;
3+ use std:: str:: FromStr ;
64
75use super :: { content_line:: PropertyName , ParseError } ;
86
7+ fn parse_yyyymmdd ( val : & str ) -> Option < ( i32 , u32 , u32 ) > {
8+ if val. len ( ) != 8 {
9+ return None ;
10+ }
11+ let year = val[ 0 ..4 ] . parse ( ) . ok ( ) ?;
12+ let month = val[ 4 ..6 ] . parse ( ) . ok ( ) ?;
13+ let day = val[ 6 ..8 ] . parse ( ) . ok ( ) ?;
14+ Some ( ( year, month, day) )
15+ }
16+
17+ fn parse_hhmmss ( val : & str ) -> Option < ( u32 , u32 , u32 ) > {
18+ if val. len ( ) != 6 {
19+ return None ;
20+ }
21+ let hour = val[ 0 ..2 ] . parse ( ) . ok ( ) ?;
22+ let min = val[ 2 ..4 ] . parse ( ) . ok ( ) ?;
23+ let sec = val[ 4 ..6 ] . parse ( ) . ok ( ) ?;
24+ Some ( ( hour, min, sec) )
25+ }
26+
927#[ derive( Debug , PartialEq ) ]
1028pub ( crate ) struct ParsedDateString {
1129 pub year : i32 ,
@@ -27,53 +45,36 @@ pub(crate) struct ParsedDateStringTime {
2745 pub sec : u32 ,
2846}
2947
30- fn get_datetime_captures < T : FromStr > (
31- captures : & Captures ,
32- idx : usize ,
33- val : & str ,
34- ) -> Result < T , ParseError > {
35- captures
36- . get ( idx)
37- . ok_or_else ( || ParseError :: InvalidDateTimeFormat ( val. into ( ) ) ) ?
38- . as_str ( )
39- . parse ( )
40- . map_err ( |_| ParseError :: InvalidDateTimeFormat ( val. into ( ) ) )
41- }
42-
4348impl ParsedDateString {
4449 /// Parses a date string with format `YYYYMMDD(THHMMSSZ)` where the part in parentheses
4550 /// is optional. It returns [`ParsedDateString`].
4651 pub ( crate ) fn from_ical_datetime ( val : & str ) -> Result < Self , ParseError > {
47- static DATESTR_RE : OnceLock < Regex > = OnceLock :: new ( ) ;
48-
49- let captures = DATESTR_RE
50- . get_or_init ( || {
51- Regex :: new (
52- r"(?m)^([0-9]{4})([0-9]{2})([0-9]{2})(T([0-9]{2})([0-9]{2})([0-9]{2})(Z?))?$" ,
53- )
54- . expect ( "DATESTR_RE must compile" )
55- } )
56- . captures ( val)
52+ let ( date_part, time_part) = if val. len ( ) >= 8 {
53+ val. split_at ( 8 )
54+ } else {
55+ return Err ( ParseError :: InvalidDateTimeFormat ( val. into ( ) ) ) ;
56+ } ;
57+
58+ let ( year, month, day) = parse_yyyymmdd ( date_part)
5759 . ok_or_else ( || ParseError :: InvalidDateTimeFormat ( val. into ( ) ) ) ?;
5860
59- let year = get_datetime_captures ( & captures, 1 , val) ?;
60- let month = get_datetime_captures ( & captures, 2 , val) ?;
61- let day = get_datetime_captures ( & captures, 3 , val) ?;
61+ let mut zulu_timezone_set = false ;
62+ let time = if !time_part. is_empty ( ) {
63+ let Some ( time_part) = time_part. strip_prefix ( 'T' ) else {
64+ return Err ( ParseError :: InvalidDateTimeFormat ( val. into ( ) ) ) ;
65+ } ;
66+ let time_part = time_part
67+ . strip_suffix ( 'Z' )
68+ . inspect ( |_| zulu_timezone_set = true )
69+ . unwrap_or ( time_part) ;
6270
63- // Check if time part is captured
64- let time = if captures. get ( 4 ) . is_some ( ) {
65- let hour = get_datetime_captures ( & captures, 5 , val) ?;
66- let min = get_datetime_captures ( & captures, 6 , val) ?;
67- let sec = get_datetime_captures ( & captures, 7 , val) ?;
71+ let ( hour, min, sec) = parse_hhmmss ( time_part)
72+ . ok_or_else ( || ParseError :: InvalidDateTimeFormat ( val. into ( ) ) ) ?;
6873 Some ( ParsedDateStringTime { hour, min, sec } )
6974 } else {
7075 None
7176 } ;
7277
73- let zulu_timezone_set = match captures. get ( 8 ) {
74- Some ( part) => part. as_str ( ) == "Z" ,
75- None => false ,
76- } ;
7778 let flags = ParsedDateStringFlags { zulu_timezone_set } ;
7879
7980 Ok ( Self {
@@ -89,10 +90,12 @@ impl ParsedDateString {
8990/// Get the line property name, the `RRULE:`, `EXRULE:` etc part.
9091pub ( crate ) fn get_property_name ( val : & str ) -> Result < Option < PropertyName > , ParseError > {
9192 val. chars ( )
92- . position ( |c| c == ':' || c == ';' )
93- . map ( |pos| & val[ 0 ..pos] )
94- . filter ( |candidate| candidate. chars ( ) . all ( |c| c. is_ascii_alphabetic ( ) ) )
95- . map_or ( Ok ( None ) , |candidate| PropertyName :: from_str ( candidate) . map ( Some ) )
93+ . position ( |c| c == ':' || c == ';' )
94+ . map ( |pos| & val[ 0 ..pos] )
95+ . filter ( |candidate| candidate. chars ( ) . all ( |c| c. is_ascii_alphabetic ( ) ) )
96+ . map_or ( Ok ( None ) , |candidate| {
97+ PropertyName :: from_str ( candidate) . map ( Some )
98+ } )
9699}
97100
98101#[ cfg( test) ]
0 commit comments