Skip to main content

reifydb_value/value/temporal/parse/
datetime.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use super::{date::parse_date, time::parse_time};
5use crate::{
6	error::{Error, TemporalKind, TypeError},
7	fragment::Fragment,
8	value::DateTime,
9};
10
11pub fn parse_datetime(fragment: Fragment) -> Result<DateTime, Error> {
12	let parts: Vec<&str> = fragment.text().split('T').collect();
13	if parts.len() != 2 {
14		return Err(TypeError::Temporal {
15			kind: TemporalKind::InvalidDateTimeFormat,
16			message: "invalid datetime format".into(),
17			fragment,
18		}
19		.into());
20	}
21
22	let date_offset = 0;
23	let date_fragment = fragment.sub_fragment(date_offset, parts[0].len());
24	let time_offset = parts[0].len() + 1;
25	let time_fragment = fragment.sub_fragment(time_offset, parts[1].len());
26
27	let date = parse_date(date_fragment)?;
28	let time = parse_time(time_fragment)?;
29
30	DateTime::new(
31		date.year(),
32		date.month(),
33		date.day(),
34		time.hour(),
35		time.minute(),
36		time.second(),
37		time.nanosecond(),
38	)
39	.ok_or_else(|| {
40		Error::from(TypeError::Temporal {
41			kind: TemporalKind::DateTimeOutOfRange,
42			message: "datetime out of representable range".into(),
43			fragment,
44		})
45	})
46}
47
48#[cfg(test)]
49pub mod tests {
50	use super::parse_datetime;
51	use crate::fragment::Fragment;
52
53	#[test]
54	fn test_basic() {
55		let fragment = Fragment::testing("2024-03-15T14:30:00");
56		let datetime = parse_datetime(fragment).unwrap();
57		assert_eq!(datetime.to_string(), "2024-03-15T14:30:00.000000000Z");
58	}
59
60	#[test]
61	fn test_with_timezone_z() {
62		let fragment = Fragment::testing("2024-03-15T14:30:00Z");
63		let datetime = parse_datetime(fragment).unwrap();
64		assert_eq!(datetime.to_string(), "2024-03-15T14:30:00.000000000Z");
65	}
66
67	#[test]
68	fn test_with_milliseconds() {
69		let fragment = Fragment::testing("2024-03-15T14:30:00.123Z");
70		let datetime = parse_datetime(fragment).unwrap();
71		assert_eq!(datetime.to_string(), "2024-03-15T14:30:00.123000000Z");
72	}
73
74	#[test]
75	fn test_with_microseconds() {
76		let fragment = Fragment::testing("2024-03-15T14:30:00.123456Z");
77		let datetime = parse_datetime(fragment).unwrap();
78		assert_eq!(datetime.to_string(), "2024-03-15T14:30:00.123456000Z");
79	}
80
81	#[test]
82	fn test_with_nanoseconds() {
83		let fragment = Fragment::testing("2024-03-15T14:30:00.123456789Z");
84		let datetime = parse_datetime(fragment).unwrap();
85		assert_eq!(datetime.to_string(), "2024-03-15T14:30:00.123456789Z");
86	}
87
88	#[test]
89	fn test_leap_year() {
90		let fragment = Fragment::testing("2024-02-29T00:00:00");
91		let datetime = parse_datetime(fragment).unwrap();
92		assert_eq!(datetime.to_string(), "2024-02-29T00:00:00.000000000Z");
93	}
94
95	#[test]
96	fn test_boundaries() {
97		let fragment = Fragment::testing("2000-01-01T00:00:00");
98		let datetime = parse_datetime(fragment).unwrap();
99		assert_eq!(datetime.to_string(), "2000-01-01T00:00:00.000000000Z");
100
101		let fragment = Fragment::testing("2024-12-31T23:59:59");
102		let datetime = parse_datetime(fragment).unwrap();
103		assert_eq!(datetime.to_string(), "2024-12-31T23:59:59.000000000Z");
104	}
105
106	#[test]
107	fn test_invalid_format() {
108		let fragment = Fragment::testing("2024-03-15");
109		let err = parse_datetime(fragment).unwrap_err();
110		assert_eq!(err.0.code, "TEMPORAL_002");
111	}
112
113	#[test]
114	fn test_invalid_date_format() {
115		let fragment = Fragment::testing("2024-03T14:30:00");
116		let err = parse_datetime(fragment).unwrap_err();
117		assert_eq!(err.0.code, "TEMPORAL_001");
118	}
119
120	#[test]
121	fn test_invalid_time_format() {
122		let fragment = Fragment::testing("2024-03-15T14:30");
123		let err = parse_datetime(fragment).unwrap_err();
124		assert_eq!(err.0.code, "TEMPORAL_003");
125	}
126
127	#[test]
128	fn test_invalid_year() {
129		let fragment = Fragment::testing("invalid-03-15T14:30:00");
130		let err = parse_datetime(fragment).unwrap_err();
131		assert_eq!(err.0.code, "TEMPORAL_005");
132	}
133
134	#[test]
135	fn test_invalid_date_values() {
136		let fragment = Fragment::testing("2024-13-32T23:30:40");
137		let err = parse_datetime(fragment).unwrap_err();
138		assert_eq!(err.0.code, "TEMPORAL_012");
139	}
140
141	#[test]
142	fn test_invalid_time_value() {
143		let fragment = Fragment::testing("2024-09-09T30:70:80");
144		let err = parse_datetime(fragment).unwrap_err();
145		assert_eq!(err.0.code, "TEMPORAL_013");
146	}
147
148	#[test]
149	fn test_invalid_fractional_seconds() {
150		let fragment = Fragment::testing("2024-03-15T14:30:00.123.456");
151		let err = parse_datetime(fragment).unwrap_err();
152		assert_eq!(err.0.code, "TEMPORAL_011");
153	}
154}