reifydb-value 0.6.0

Core type system and value representations for ReifyDB
Documentation
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 ReifyDB

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() {
		// Test 2-digit year
		let fragment = Fragment::testing("24-03-15");
		let err = parse_date(fragment).unwrap_err();
		assert_eq!(err.0.code, "TEMPORAL_005");

		// Test 3-digit year
		let fragment = Fragment::testing("024-03-15");
		let err = parse_date(fragment).unwrap_err();
		assert_eq!(err.0.code, "TEMPORAL_005");

		// Test 5-digit year
		let fragment = Fragment::testing("20240-03-15");
		let err = parse_date(fragment).unwrap_err();
		assert_eq!(err.0.code, "TEMPORAL_005");

		// Test year with leading zeros (still 4 digits, should work)
		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() {
		// Test 1-digit month
		let fragment = Fragment::testing("2024-3-15");
		let err = parse_date(fragment).unwrap_err();
		assert_eq!(err.0.code, "TEMPORAL_006");

		// Test 3-digit month
		let fragment = Fragment::testing("2024-003-15");
		let err = parse_date(fragment).unwrap_err();
		assert_eq!(err.0.code, "TEMPORAL_006");

		// Test month with leading zeros (still 2 digits, should work)
		let fragment = Fragment::testing("2024-03-15");
		let date = parse_date(fragment).unwrap();
		assert_eq!(date.to_string(), "2024-03-15");

		// Test month with non-digits
		let fragment = Fragment::testing("2024-0a-15");
		let err = parse_date(fragment).unwrap_err();
		assert_eq!(err.0.code, "TEMPORAL_006");

		// Test month with spaces
		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() {
		// Test 1-digit day
		let fragment = Fragment::testing("2024-03-5");
		let err = parse_date(fragment).unwrap_err();
		assert_eq!(err.0.code, "TEMPORAL_007");

		// Test 3-digit day
		let fragment = Fragment::testing("2024-03-015");
		let err = parse_date(fragment).unwrap_err();
		assert_eq!(err.0.code, "TEMPORAL_007");

		// Test day with leading zeros (still 2 digits, should work)
		let fragment = Fragment::testing("2024-03-05");
		let date = parse_date(fragment).unwrap();
		assert_eq!(date.to_string(), "2024-03-05");

		// Test day with non-digits
		let fragment = Fragment::testing("2024-03-1a");
		let err = parse_date(fragment).unwrap_err();
		assert_eq!(err.0.code, "TEMPORAL_007");

		// Test day with spaces
		let fragment = Fragment::testing("2024-03- 5");
		let err = parse_date(fragment).unwrap_err();
		assert_eq!(err.0.code, "TEMPORAL_007");
	}
}