whatwg_datetime/components/
local_datetime.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use crate::tokens::Token;
use crate::{parse_date_component, parse_time_component};
use chrono::NaiveDateTime;

/// Parse a [proleptic-Gregorian date][proleptic-greg] consisting
/// of a date, time, with no time-zone information
///
/// This follows the rules for [parsing a local datetime string][whatwg-html-parse]
/// per [WHATWG HTML Standard ยง 2.3.5.5 Local dates and times][whatwg-html-local-datetime].
///
/// # Examples
/// ```
/// use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
/// use whatwg_datetime::parse_local_datetime;
///
/// // Parse a local datetime string with a date,
/// // a T delimiter, anda  time with fractional seconds
/// assert_eq!(
///     parse_local_datetime("2011-11-18T14:54:39.929"),
///     Some(NaiveDateTime::new(
///         NaiveDate::from_ymd_opt(2011, 11, 18).unwrap(),
///         NaiveTime::from_hms_milli_opt(14, 54, 39, 929).unwrap(),
///     ))
/// );
/// ```
///
/// [proleptic-greg]: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#proleptic-gregorian-date
/// [whatwg-html-local-datetime]: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#local-dates-and-times
/// [whatwg-html-parse]: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#parse-a-local-date-and-time-string
pub fn parse_local_datetime(s: &str) -> Option<NaiveDateTime> {
	let mut position = 0usize;
	let date = parse_date_component(s, &mut position)?;

	let last_char = s.chars().nth(position);
	if position > s.len() || !matches!(last_char, Some(Token::T) | Some(Token::SPACE)) {
		return None;
	} else {
		position += 1;
	}

	let time = parse_time_component(s, &mut position)?;
	if position < s.len() {
		return None;
	}

	Some(NaiveDateTime::new(date, time))
}

#[cfg(test)]
mod tests {
	use super::parse_local_datetime;
	use chrono::{NaiveDate, NaiveDateTime, NaiveTime};

	#[test]
	pub fn test_parse_local_datetime_delimited_t_date_hm() {
		assert_eq!(
			parse_local_datetime("2004-12-31T12:31"),
			Some(NaiveDateTime::new(
				NaiveDate::from_ymd_opt(2004, 12, 31).unwrap(),
				NaiveTime::from_hms_opt(12, 31, 0).unwrap(),
			))
		);
	}

	#[test]
	pub fn test_parse_local_datetime_delimited_t_date_hms() {
		assert_eq!(
			parse_local_datetime("2004-12-31T12:31:59"),
			Some(NaiveDateTime::new(
				NaiveDate::from_ymd_opt(2004, 12, 31).unwrap(),
				NaiveTime::from_hms_opt(12, 31, 59).unwrap(),
			))
		);
	}

	#[test]
	pub fn test_parse_local_datetime_delimited_t_date_hms_milliseconds() {
		assert_eq!(
			parse_local_datetime("2011-11-18T14:54:39.929"),
			Some(NaiveDateTime::new(
				NaiveDate::from_ymd_opt(2011, 11, 18).unwrap(),
				NaiveTime::from_hms_milli_opt(14, 54, 39, 929).unwrap(),
			))
		)
	}

	#[test]
	pub fn test_parse_local_datetime_delimited_space_date_hm() {
		assert_eq!(
			parse_local_datetime("2011-11-18 14:54"),
			Some(NaiveDateTime::new(
				NaiveDate::from_ymd_opt(2011, 11, 18).unwrap(),
				NaiveTime::from_hms_opt(14, 54, 0).unwrap(),
			))
		)
	}

	#[test]
	pub fn test_parse_local_datetime_delimited_space_date_hms() {
		assert_eq!(
			parse_local_datetime("2011-11-18 14:54:39"),
			Some(NaiveDateTime::new(
				NaiveDate::from_ymd_opt(2011, 11, 18).unwrap(),
				NaiveTime::from_hms_opt(14, 54, 39).unwrap(),
			))
		)
	}

	#[test]
	pub fn test_parse_local_datetime_delimited_space_date_hms_milliseconds() {
		assert_eq!(
			parse_local_datetime("2011-11-18 14:54:39.929"),
			Some(NaiveDateTime::new(
				NaiveDate::from_ymd_opt(2011, 11, 18).unwrap(),
				NaiveTime::from_hms_milli_opt(14, 54, 39, 929).unwrap(),
			))
		)
	}

	#[test]
	pub fn test_parse_local_datetime_fails_invalid_delimiter() {
		assert_eq!(parse_local_datetime("2011-11-18W14-54-39"), None);
	}

	#[test]
	pub fn test_parse_local_datetime_fails_invalid_date() {
		assert_eq!(parse_local_datetime("2011/11/18T14:54:39.929"), None);
	}

	#[test]
	pub fn test_parse_local_datetime_fails_invalid_time() {
		assert_eq!(parse_local_datetime("2011-11-18T14/54/39"), None);
	}
}