Fixed parsing of opening hours with a discount. Fixes #4

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-11 17:09:44 +02:00
co-authored by Claude Opus 5.5
parent 15ee091a0a
commit 01d48ac0e4
+39 -6
View File
@@ -309,7 +309,19 @@ impl TryFrom<&str> for DayHour {
return Err(AppError::HourParseError)
}
let times = value.split("-")
// Optional discount in parentheses after the interval: "08:00 - 12:00 (10)"
let (interval, discount) = match value.trim().strip_suffix(")") {
Some(rest) => {
let (interval, discount) = rest.rsplit_once("(").ok_or(AppError::HourParseError)?;
if discount.is_empty() || !discount.chars().all(|c| c.is_ascii_digit()) {
return Err(AppError::HourParseError)
}
(interval, Some(discount.parse::<i32>().map_err(|_| AppError::HourParseError)?))
}
None => (value, None)
};
let times = interval.split("-")
.map(|t| t.trim()).collect::<Vec<_>>();
if times.len() != 2 {
@@ -326,7 +338,7 @@ impl TryFrom<&str> for DayHour {
Ok(DayHour {
from: from.unwrap(),
to: to.unwrap(),
discount: None
discount
})
}
}
@@ -892,6 +904,24 @@ mod tests {
}
}
#[test]
fn day_hour_parses_discount() {
let expected = DayHour::new(time(8, 0), time(12, 30), Some(10));
assert_eq!(DayHour::try_from("08:00 - 12:30 (10)"), Ok(expected.clone()));
assert_eq!(DayHour::try_from("08:00-12:30(10)"), Ok(expected));
assert_eq!(DayHour::try_from("08:00 - 12:30 (0)"), Ok(DayHour::new(time(8, 0), time(12, 30), Some(0))));
}
#[test]
fn day_hour_rejects_malformed_discount() {
for input in ["08:00 - 12:00 ()", "08:00 - 12:00 (x)", "08:00 - 12:00 (-5)", "08:00 - 12:00 (1.5)", "08:00 - 12:00 (10",
"08:00 - 12:00 10)", "(10) 08:00 - 12:00", "08:00 (10) - 12:00", "08:00 - 12:00 (10) (20)", "(10)",
"08:00 - 12:00 (99999999999)"] {
assert_eq!(DayHour::try_from(input), Err(AppError::HourParseError), "input: {:?}", input);
}
}
#[test]
fn day_hours_empty_string_is_closed_day() {
let hours = DayHours::try_new("").unwrap();
@@ -930,7 +960,8 @@ mod tests {
fn day_hours_display_round_trip() {
let hours = DayHours(vec![
DayHour::new(time(8, 0), time(12, 0), None),
DayHour::new(time(13, 0), time(17, 0), None)
DayHour::new(time(13, 0), time(17, 0), Some(10)),
DayHour::new(time(18, 0), time(20, 0), Some(0))
]);
let parsed = DayHours::try_new(&hours.to_string()).unwrap();
@@ -950,9 +981,11 @@ mod tests {
#[test]
fn week_hours_accepts_valid_hours() {
for input in ["", "08:00 - 12:00", "08:00-12:00", "08:00 - 12:00, 13:00 - 17:00"] {
for input in ["", "08:00 - 12:00", "08:00-12:00", "08:00 - 12:00, 13:00 - 17:00",
"08:00 - 12:00 (10)", "08:00 - 12:00 (10), 13:00 - 17:00", "08:00 - 12:00, 13:00 - 17:00(20)"] {
let hours = WeekHours { day: Weekday::Mon, hours: input.to_string() };
assert!(hours.validate().is_ok(), "input: {:?}", input);
assert!(DayHours::try_new(input).is_ok(), "input: {:?}", input);
}
}
@@ -978,7 +1011,7 @@ mod tests {
#[test]
fn week_hours_validation_agrees_with_parser() {
for input in ["", "08:00 - 12:00", "08:00-12:00", "08:00 - 12:00,13:00 - 17:00", "08:00 - 12:00 , 13:00 - 17:00",
"08:00 - 12:00, nonsense", "08:00 - 12:00,", "25:00 - 26:00", "08:00 - 12:00 garbage"] {
"08:00 - 12:00 (10)", "08:00 - 12:00 (10) , 13:00 - 17:00 (5)", "08:00 - 12:00 (99999999999)", "08:00 - 12:00, nonsense", "08:00 - 12:00,", "25:00 - 26:00", "08:00 - 12:00 garbage"] {
let hours = WeekHours { day: Weekday::Mon, hours: input.to_string() };
if hours.validate().is_ok() {
assert!(DayHours::try_new(input).is_ok(), "input: {:?}", input);
@@ -1003,7 +1036,7 @@ mod tests {
#[test]
fn week_hours_rejects_to_before_from() {
for input in ["12:00 - 08:00", "08:00 - 08:00", "08:00 - 12:00, 17:00 - 13:00"] {
for input in ["12:00 - 08:00", "08:00 - 08:00", "08:00 - 12:00, 17:00 - 13:00", "12:00 - 08:00 (10)"] {
let hours = WeekHours { day: Weekday::Mon, hours: input.to_string() };
assert!(has_field_error(&hours, "__all__"), "input: {:?}", input);
}