Skip to main content

reifydb_value/value/temporal/parse/
datetime.rs

1// SPDX-License-Identifier: MIT
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		let err: Error = TypeError::Temporal {
41			kind: TemporalKind::DateTimeOutOfRange,
42			message: "datetime out of representable range".into(),
43			fragment,
44		}
45		.into();
46		err
47	})
48}
49
50#[cfg(test)]
51pub mod tests {
52	use super::parse_datetime;
53	use crate::fragment::Fragment;
54
55	#[test]
56	fn test_basic() {
57		let fragment = Fragment::testing("2024-03-15T14:30:00");
58		let datetime = parse_datetime(fragment).unwrap();
59		assert_eq!(datetime.to_string(), "2024-03-15T14:30:00.000000000Z");
60	}
61
62	#[test]
63	fn test_with_timezone_z() {
64		let fragment = Fragment::testing("2024-03-15T14:30:00Z");
65		let datetime = parse_datetime(fragment).unwrap();
66		assert_eq!(datetime.to_string(), "2024-03-15T14:30:00.000000000Z");
67	}
68
69	#[test]
70	fn test_with_milliseconds() {
71		let fragment = Fragment::testing("2024-03-15T14:30:00.123Z");
72		let datetime = parse_datetime(fragment).unwrap();
73		assert_eq!(datetime.to_string(), "2024-03-15T14:30:00.123000000Z");
74	}
75
76	#[test]
77	fn test_with_microseconds() {
78		let fragment = Fragment::testing("2024-03-15T14:30:00.123456Z");
79		let datetime = parse_datetime(fragment).unwrap();
80		assert_eq!(datetime.to_string(), "2024-03-15T14:30:00.123456000Z");
81	}
82
83	#[test]
84	fn test_with_nanoseconds() {
85		let fragment = Fragment::testing("2024-03-15T14:30:00.123456789Z");
86		let datetime = parse_datetime(fragment).unwrap();
87		assert_eq!(datetime.to_string(), "2024-03-15T14:30:00.123456789Z");
88	}
89
90	#[test]
91	fn test_leap_year() {
92		let fragment = Fragment::testing("2024-02-29T00:00:00");
93		let datetime = parse_datetime(fragment).unwrap();
94		assert_eq!(datetime.to_string(), "2024-02-29T00:00:00.000000000Z");
95	}
96
97	#[test]
98	fn test_boundaries() {
99		let fragment = Fragment::testing("2000-01-01T00:00:00");
100		let datetime = parse_datetime(fragment).unwrap();
101		assert_eq!(datetime.to_string(), "2000-01-01T00:00:00.000000000Z");
102
103		let fragment = Fragment::testing("2024-12-31T23:59:59");
104		let datetime = parse_datetime(fragment).unwrap();
105		assert_eq!(datetime.to_string(), "2024-12-31T23:59:59.000000000Z");
106	}
107
108	#[test]
109	fn test_invalid_format() {
110		let fragment = Fragment::testing("2024-03-15");
111		let err = parse_datetime(fragment).unwrap_err();
112		assert_eq!(err.0.code, "TEMPORAL_002");
113	}
114
115	#[test]
116	fn test_invalid_date_format() {
117		let fragment = Fragment::testing("2024-03T14:30:00");
118		let err = parse_datetime(fragment).unwrap_err();
119		assert_eq!(err.0.code, "TEMPORAL_001");
120	}
121
122	#[test]
123	fn test_invalid_time_format() {
124		let fragment = Fragment::testing("2024-03-15T14:30");
125		let err = parse_datetime(fragment).unwrap_err();
126		assert_eq!(err.0.code, "TEMPORAL_003");
127	}
128
129	#[test]
130	fn test_invalid_year() {
131		let fragment = Fragment::testing("invalid-03-15T14:30:00");
132		let err = parse_datetime(fragment).unwrap_err();
133		assert_eq!(err.0.code, "TEMPORAL_005");
134	}
135
136	#[test]
137	fn test_invalid_date_values() {
138		let fragment = Fragment::testing("2024-13-32T23:30:40");
139		let err = parse_datetime(fragment).unwrap_err();
140		assert_eq!(err.0.code, "TEMPORAL_012");
141	}
142
143	#[test]
144	fn test_invalid_time_value() {
145		let fragment = Fragment::testing("2024-09-09T30:70:80");
146		let err = parse_datetime(fragment).unwrap_err();
147		assert_eq!(err.0.code, "TEMPORAL_013");
148	}
149
150	#[test]
151	fn test_invalid_fractional_seconds() {
152		let fragment = Fragment::testing("2024-03-15T14:30:00.123.456");
153		let err = parse_datetime(fragment).unwrap_err();
154		assert_eq!(err.0.code, "TEMPORAL_011");
155	}
156}