Skip to content

Commit 68cbd1a

Browse files
committed
refactor(parser): simplify date string parsing logic
Replace regex-based parsing with direct string manipulation for better readability and maintainability. The new implementation uses helper functions to parse date and time components separately.
1 parent a54cd79 commit 68cbd1a

File tree

1 file changed

+46
-43
lines changed

1 file changed

+46
-43
lines changed

rrule/src/parser/regex.rs

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
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

75
use 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)]
1028
pub(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-
4348
impl 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.
9091
pub(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

Comments
 (0)