Skip to main content

reifydb_value/value/temporal/parse/
date.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 ReifyDB
3
4use crate::{
5	error::{Error, TemporalKind, TypeError},
6	fragment::Fragment,
7	value::Date,
8};
9
10pub fn parse_date(fragment: Fragment) -> Result<Date, Error> {
11	let value = fragment.text();
12	let parts: Vec<&str> = value.split('-').collect();
13
14	if parts.len() != 3 {
15		return Err(TypeError::Temporal {
16			kind: TemporalKind::InvalidDateFormat,
17			message: "invalid date format".into(),
18			fragment,
19		}
20		.into());
21	}
22
23	let year_str = parts[0].trim();
24	let month_str = parts[1].trim();
25	let day_str = parts[2].trim();
26
27	let mut offset = 0;
28	if year_str.is_empty() {
29		let year_frag = fragment.sub_fragment(offset, parts[0].len());
30		return Err(TypeError::Temporal {
31			kind: TemporalKind::EmptyDateComponent,
32			message: "empty date component".into(),
33			fragment: year_frag,
34		}
35		.into());
36	}
37	offset += parts[0].len() + 1;
38
39	if month_str.is_empty() {
40		let month_frag = fragment.sub_fragment(offset, parts[1].len());
41		return Err(TypeError::Temporal {
42			kind: TemporalKind::EmptyDateComponent,
43			message: "empty date component".into(),
44			fragment: month_frag,
45		}
46		.into());
47	}
48	offset += parts[1].len() + 1;
49
50	if day_str.is_empty() {
51		let day_frag = fragment.sub_fragment(offset, parts[2].len());
52		return Err(TypeError::Temporal {
53			kind: TemporalKind::EmptyDateComponent,
54			message: "empty date component".into(),
55			fragment: day_frag,
56		}
57		.into());
58	}
59
60	offset = 0;
61
62	if year_str.len() != 4 {
63		let year_frag = fragment.sub_fragment(offset, parts[0].len());
64		return Err(TypeError::Temporal {
65			kind: TemporalKind::InvalidYear,
66			message: format!("invalid year value '{}'", year_frag.text()),
67			fragment: year_frag,
68		}
69		.into());
70	}
71
72	let year = year_str.parse::<i32>().map_err(|_| {
73		let year_frag = fragment.sub_fragment(offset, parts[0].len());
74		let err: Error = TypeError::Temporal {
75			kind: TemporalKind::InvalidYear,
76			message: format!("invalid year value '{}'", year_frag.text()),
77			fragment: year_frag,
78		}
79		.into();
80		err
81	})?;
82	offset += parts[0].len() + 1;
83
84	if month_str.len() != 2 {
85		let month_frag = fragment.sub_fragment(offset, parts[1].len());
86		return Err(TypeError::Temporal {
87			kind: TemporalKind::InvalidMonth,
88			message: format!("invalid month value '{}'", month_frag.text()),
89			fragment: month_frag,
90		}
91		.into());
92	}
93
94	let month = month_str.parse::<u32>().map_err(|_| {
95		let month_frag = fragment.sub_fragment(offset, parts[1].len());
96		let err: Error = TypeError::Temporal {
97			kind: TemporalKind::InvalidMonth,
98			message: format!("invalid month value '{}'", month_frag.text()),
99			fragment: month_frag,
100		}
101		.into();
102		err
103	})?;
104	offset += parts[1].len() + 1;
105
106	if day_str.len() != 2 {
107		let day_frag = fragment.sub_fragment(offset, parts[2].len());
108		return Err(TypeError::Temporal {
109			kind: TemporalKind::InvalidDay,
110			message: format!("invalid day value '{}'", day_frag.text()),
111			fragment: day_frag,
112		}
113		.into());
114	}
115
116	let day = day_str.parse::<u32>().map_err(|_| {
117		let day_frag = fragment.sub_fragment(offset, parts[2].len());
118		let err: Error = TypeError::Temporal {
119			kind: TemporalKind::InvalidDay,
120			message: format!("invalid day value '{}'", day_frag.text()),
121			fragment: day_frag,
122		}
123		.into();
124		err
125	})?;
126
127	Date::new(year, month, day).ok_or_else(|| {
128		let err: Error = TypeError::Temporal {
129			kind: TemporalKind::InvalidDateValues,
130			message: "invalid date values".into(),
131			fragment,
132		}
133		.into();
134		err
135	})
136}
137
138#[cfg(test)]
139pub mod tests {
140	use super::parse_date;
141	use crate::fragment::Fragment;
142
143	#[test]
144	fn test_basic() {
145		let fragment = Fragment::testing("2024-03-15");
146		let date = parse_date(fragment).unwrap();
147		assert_eq!(date.to_string(), "2024-03-15");
148	}
149
150	#[test]
151	fn test_leap_year() {
152		let fragment = Fragment::testing("2024-02-29");
153		let date = parse_date(fragment).unwrap();
154		assert_eq!(date.to_string(), "2024-02-29");
155	}
156
157	#[test]
158	fn test_boundaries() {
159		let fragment = Fragment::testing("2000-01-01");
160		let date = parse_date(fragment).unwrap();
161		assert_eq!(date.to_string(), "2000-01-01");
162
163		let fragment = Fragment::testing("2024-12-31");
164		let date = parse_date(fragment).unwrap();
165		assert_eq!(date.to_string(), "2024-12-31");
166	}
167
168	#[test]
169	fn test_invalid_format() {
170		let fragment = Fragment::testing("2024-03");
171		let err = parse_date(fragment).unwrap_err();
172		assert_eq!(err.0.code, "TEMPORAL_001");
173	}
174
175	#[test]
176	fn test_invalid_year() {
177		let fragment = Fragment::testing("abcd-03-15");
178		let err = parse_date(fragment).unwrap_err();
179		assert_eq!(err.0.code, "TEMPORAL_005");
180	}
181
182	#[test]
183	fn test_invalid_month() {
184		let fragment = Fragment::testing("2024-invalid-15");
185		let err = parse_date(fragment).unwrap_err();
186		assert_eq!(err.0.code, "TEMPORAL_006");
187	}
188
189	#[test]
190	fn test_invalid_day() {
191		let fragment = Fragment::testing("2024-03-invalid");
192		let err = parse_date(fragment).unwrap_err();
193		assert_eq!(err.0.code, "TEMPORAL_007");
194	}
195
196	#[test]
197	fn test_invalid_date_values() {
198		let fragment = Fragment::testing("2024-13-32");
199		let err = parse_date(fragment).unwrap_err();
200		assert_eq!(err.0.code, "TEMPORAL_012");
201	}
202
203	#[test]
204	fn test_four_digit_year() {
205		// Test 2-digit year
206		let fragment = Fragment::testing("24-03-15");
207		let err = parse_date(fragment).unwrap_err();
208		assert_eq!(err.0.code, "TEMPORAL_005");
209
210		// Test 3-digit year
211		let fragment = Fragment::testing("024-03-15");
212		let err = parse_date(fragment).unwrap_err();
213		assert_eq!(err.0.code, "TEMPORAL_005");
214
215		// Test 5-digit year
216		let fragment = Fragment::testing("20240-03-15");
217		let err = parse_date(fragment).unwrap_err();
218		assert_eq!(err.0.code, "TEMPORAL_005");
219
220		// Test year with leading zeros (still 4 digits, should work)
221		let fragment = Fragment::testing("0024-03-15");
222		let date = parse_date(fragment).unwrap();
223		assert_eq!(date.to_string(), "0024-03-15");
224	}
225
226	#[test]
227	fn test_two_digit_month() {
228		// Test 1-digit month
229		let fragment = Fragment::testing("2024-3-15");
230		let err = parse_date(fragment).unwrap_err();
231		assert_eq!(err.0.code, "TEMPORAL_006");
232
233		// Test 3-digit month
234		let fragment = Fragment::testing("2024-003-15");
235		let err = parse_date(fragment).unwrap_err();
236		assert_eq!(err.0.code, "TEMPORAL_006");
237
238		// Test month with leading zeros (still 2 digits, should work)
239		let fragment = Fragment::testing("2024-03-15");
240		let date = parse_date(fragment).unwrap();
241		assert_eq!(date.to_string(), "2024-03-15");
242
243		// Test month with non-digits
244		let fragment = Fragment::testing("2024-0a-15");
245		let err = parse_date(fragment).unwrap_err();
246		assert_eq!(err.0.code, "TEMPORAL_006");
247
248		// Test month with spaces
249		let fragment = Fragment::testing("2024- 3-15");
250		let err = parse_date(fragment).unwrap_err();
251		assert_eq!(err.0.code, "TEMPORAL_006");
252	}
253
254	#[test]
255	fn test_two_digit_day() {
256		// Test 1-digit day
257		let fragment = Fragment::testing("2024-03-5");
258		let err = parse_date(fragment).unwrap_err();
259		assert_eq!(err.0.code, "TEMPORAL_007");
260
261		// Test 3-digit day
262		let fragment = Fragment::testing("2024-03-015");
263		let err = parse_date(fragment).unwrap_err();
264		assert_eq!(err.0.code, "TEMPORAL_007");
265
266		// Test day with leading zeros (still 2 digits, should work)
267		let fragment = Fragment::testing("2024-03-05");
268		let date = parse_date(fragment).unwrap();
269		assert_eq!(date.to_string(), "2024-03-05");
270
271		// Test day with non-digits
272		let fragment = Fragment::testing("2024-03-1a");
273		let err = parse_date(fragment).unwrap_err();
274		assert_eq!(err.0.code, "TEMPORAL_007");
275
276		// Test day with spaces
277		let fragment = Fragment::testing("2024-03- 5");
278		let err = parse_date(fragment).unwrap_err();
279		assert_eq!(err.0.code, "TEMPORAL_007");
280	}
281}