use crate::{
error::{Error, TemporalKind, TypeError},
fragment::Fragment,
value::Date,
};
pub fn parse_date(fragment: Fragment) -> Result<Date, Error> {
let value = fragment.text();
let parts: Vec<&str> = value.split('-').collect();
if parts.len() != 3 {
return Err(TypeError::Temporal {
kind: TemporalKind::InvalidDateFormat,
message: "invalid date format".into(),
fragment,
}
.into());
}
let year_str = parts[0].trim();
let month_str = parts[1].trim();
let day_str = parts[2].trim();
let mut offset = 0;
if year_str.is_empty() {
let year_frag = fragment.sub_fragment(offset, parts[0].len());
return Err(TypeError::Temporal {
kind: TemporalKind::EmptyDateComponent,
message: "empty date component".into(),
fragment: year_frag,
}
.into());
}
offset += parts[0].len() + 1;
if month_str.is_empty() {
let month_frag = fragment.sub_fragment(offset, parts[1].len());
return Err(TypeError::Temporal {
kind: TemporalKind::EmptyDateComponent,
message: "empty date component".into(),
fragment: month_frag,
}
.into());
}
offset += parts[1].len() + 1;
if day_str.is_empty() {
let day_frag = fragment.sub_fragment(offset, parts[2].len());
return Err(TypeError::Temporal {
kind: TemporalKind::EmptyDateComponent,
message: "empty date component".into(),
fragment: day_frag,
}
.into());
}
offset = 0;
if year_str.len() != 4 {
let year_frag = fragment.sub_fragment(offset, parts[0].len());
return Err(TypeError::Temporal {
kind: TemporalKind::InvalidYear,
message: format!("invalid year value '{}'", year_frag.text()),
fragment: year_frag,
}
.into());
}
let year = year_str.parse::<i32>().map_err(|_| {
let year_frag = fragment.sub_fragment(offset, parts[0].len());
let err: Error = TypeError::Temporal {
kind: TemporalKind::InvalidYear,
message: format!("invalid year value '{}'", year_frag.text()),
fragment: year_frag,
}
.into();
err
})?;
offset += parts[0].len() + 1;
if month_str.len() != 2 {
let month_frag = fragment.sub_fragment(offset, parts[1].len());
return Err(TypeError::Temporal {
kind: TemporalKind::InvalidMonth,
message: format!("invalid month value '{}'", month_frag.text()),
fragment: month_frag,
}
.into());
}
let month = month_str.parse::<u32>().map_err(|_| {
let month_frag = fragment.sub_fragment(offset, parts[1].len());
let err: Error = TypeError::Temporal {
kind: TemporalKind::InvalidMonth,
message: format!("invalid month value '{}'", month_frag.text()),
fragment: month_frag,
}
.into();
err
})?;
offset += parts[1].len() + 1;
if day_str.len() != 2 {
let day_frag = fragment.sub_fragment(offset, parts[2].len());
return Err(TypeError::Temporal {
kind: TemporalKind::InvalidDay,
message: format!("invalid day value '{}'", day_frag.text()),
fragment: day_frag,
}
.into());
}
let day = day_str.parse::<u32>().map_err(|_| {
let day_frag = fragment.sub_fragment(offset, parts[2].len());
let err: Error = TypeError::Temporal {
kind: TemporalKind::InvalidDay,
message: format!("invalid day value '{}'", day_frag.text()),
fragment: day_frag,
}
.into();
err
})?;
Date::new(year, month, day).ok_or_else(|| {
let err: Error = TypeError::Temporal {
kind: TemporalKind::InvalidDateValues,
message: "invalid date values".into(),
fragment,
}
.into();
err
})
}
#[cfg(test)]
pub mod tests {
use super::parse_date;
use crate::fragment::Fragment;
#[test]
fn test_basic() {
let fragment = Fragment::testing("2024-03-15");
let date = parse_date(fragment).unwrap();
assert_eq!(date.to_string(), "2024-03-15");
}
#[test]
fn test_leap_year() {
let fragment = Fragment::testing("2024-02-29");
let date = parse_date(fragment).unwrap();
assert_eq!(date.to_string(), "2024-02-29");
}
#[test]
fn test_boundaries() {
let fragment = Fragment::testing("2000-01-01");
let date = parse_date(fragment).unwrap();
assert_eq!(date.to_string(), "2000-01-01");
let fragment = Fragment::testing("2024-12-31");
let date = parse_date(fragment).unwrap();
assert_eq!(date.to_string(), "2024-12-31");
}
#[test]
fn test_invalid_format() {
let fragment = Fragment::testing("2024-03");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_001");
}
#[test]
fn test_invalid_year() {
let fragment = Fragment::testing("abcd-03-15");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_005");
}
#[test]
fn test_invalid_month() {
let fragment = Fragment::testing("2024-invalid-15");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_006");
}
#[test]
fn test_invalid_day() {
let fragment = Fragment::testing("2024-03-invalid");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_007");
}
#[test]
fn test_invalid_date_values() {
let fragment = Fragment::testing("2024-13-32");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_012");
}
#[test]
fn test_four_digit_year() {
let fragment = Fragment::testing("24-03-15");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_005");
let fragment = Fragment::testing("024-03-15");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_005");
let fragment = Fragment::testing("20240-03-15");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_005");
let fragment = Fragment::testing("0024-03-15");
let date = parse_date(fragment).unwrap();
assert_eq!(date.to_string(), "0024-03-15");
}
#[test]
fn test_two_digit_month() {
let fragment = Fragment::testing("2024-3-15");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_006");
let fragment = Fragment::testing("2024-003-15");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_006");
let fragment = Fragment::testing("2024-03-15");
let date = parse_date(fragment).unwrap();
assert_eq!(date.to_string(), "2024-03-15");
let fragment = Fragment::testing("2024-0a-15");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_006");
let fragment = Fragment::testing("2024- 3-15");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_006");
}
#[test]
fn test_two_digit_day() {
let fragment = Fragment::testing("2024-03-5");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_007");
let fragment = Fragment::testing("2024-03-015");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_007");
let fragment = Fragment::testing("2024-03-05");
let date = parse_date(fragment).unwrap();
assert_eq!(date.to_string(), "2024-03-05");
let fragment = Fragment::testing("2024-03-1a");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_007");
let fragment = Fragment::testing("2024-03- 5");
let err = parse_date(fragment).unwrap_err();
assert_eq!(err.0.code, "TEMPORAL_007");
}
}