whatwg_datetime/components/
global_datetime.rs1use crate::tokens::{TOKEN_SPACE, TOKEN_T};
2use crate::{parse_date_component, parse_time_component, parse_timezone_offset_component};
3use chrono::{DateTime, Duration, NaiveDateTime, TimeZone, Utc};
4
5pub fn parse_global_datetime(s: &str) -> Option<DateTime<Utc>> {
32 let mut position = 0usize;
33 let date = parse_date_component(s, &mut position)?;
34
35 let last_char = s.chars().nth(position);
36 if position > s.len() || !matches!(last_char, Some(TOKEN_T) | Some(TOKEN_SPACE)) {
37 return None;
38 } else {
39 position += 1;
40 }
41
42 let time = parse_time_component(s, &mut position)?;
43 if position > s.len() {
44 return None;
45 }
46
47 let timezone_offset = parse_timezone_offset_component(s, &mut position)?;
48 if position < s.len() {
49 return None;
50 }
51
52 let timezone_offset_as_duration =
53 Duration::minutes(timezone_offset.minute as i64 + timezone_offset.hour as i64 * 60);
54 let naive_datetime = NaiveDateTime::new(
55 date,
56 time.overflowing_sub_signed(timezone_offset_as_duration).0,
57 );
58
59 Some(Utc.from_utc_datetime(&naive_datetime))
60}
61
62#[cfg(test)]
63mod tests {
64 use super::parse_global_datetime;
65 use chrono::{NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
66
67 #[test]
68 fn test_parse_global_datetime_t_hm() {
69 assert_eq!(
70 parse_global_datetime("2004-12-31T12:31"),
71 Some(Utc.from_utc_datetime(&NaiveDateTime::new(
72 NaiveDate::from_ymd_opt(2004, 12, 31).unwrap(),
73 NaiveTime::from_hms_opt(12, 31, 0).unwrap(),
74 )))
75 );
76 }
77
78 #[test]
79 fn test_parse_global_datetime_t_hms() {
80 assert_eq!(
81 parse_global_datetime("2004-12-31T12:31:59"),
82 Some(Utc.from_utc_datetime(&NaiveDateTime::new(
83 NaiveDate::from_ymd_opt(2004, 12, 31).unwrap(),
84 NaiveTime::from_hms_opt(12, 31, 59).unwrap(),
85 )))
86 );
87 }
88
89 #[test]
90 fn test_parse_global_datetime_t_hms_milliseconds() {
91 assert_eq!(
92 parse_global_datetime("2027-11-29T12:31:59.123"),
93 Some(Utc.from_utc_datetime(&NaiveDateTime::new(
94 NaiveDate::from_ymd_opt(2027, 11, 29).unwrap(),
95 NaiveTime::from_hms_milli_opt(12, 31, 59, 123).unwrap(),
96 )))
97 );
98 }
99
100 #[test]
101 fn test_parse_global_datetime_t_hms_z() {
102 assert_eq!(
103 parse_global_datetime("2004-12-31T12:31:59Z"),
104 Some(Utc.from_utc_datetime(&NaiveDateTime::new(
105 NaiveDate::from_ymd_opt(2004, 12, 31).unwrap(),
106 NaiveTime::from_hms_opt(12, 31, 59).unwrap(),
107 )))
108 );
109 }
110
111 #[test]
112 fn test_parse_global_datetime_space_hm() {
113 assert_eq!(
114 parse_global_datetime("2004-12-31 12:31"),
115 Some(Utc.from_utc_datetime(&NaiveDateTime::new(
116 NaiveDate::from_ymd_opt(2004, 12, 31).unwrap(),
117 NaiveTime::from_hms_opt(12, 31, 0).unwrap(),
118 )))
119 );
120 }
121
122 #[test]
123 fn test_parse_global_datetime_space_hms() {
124 assert_eq!(
125 parse_global_datetime("2004-12-31 12:31:59"),
126 Some(Utc.from_utc_datetime(&NaiveDateTime::new(
127 NaiveDate::from_ymd_opt(2004, 12, 31).unwrap(),
128 NaiveTime::from_hms_opt(12, 31, 59).unwrap(),
129 )))
130 );
131 }
132
133 #[test]
134 fn test_parse_global_datetime_space_hms_milliseconds() {
135 assert_eq!(
136 parse_global_datetime("2004-12-31 12:31:59.123"),
137 Some(Utc.from_utc_datetime(&NaiveDateTime::new(
138 NaiveDate::from_ymd_opt(2004, 12, 31).unwrap(),
139 NaiveTime::from_hms_milli_opt(12, 31, 59, 123).unwrap(),
140 )))
141 );
142 }
143
144 #[test]
145 fn test_parse_global_datetime_fails_invalid_date() {
146 assert_eq!(parse_global_datetime("2004/13/31T12:31"), None);
147 }
148
149 #[test]
150 fn test_parse_global_datetime_fails_invalid_delimiter() {
151 assert_eq!(parse_global_datetime("1986-08-14/12-31"), None);
152 }
153
154 #[test]
155 fn test_parse_global_datetime_fails_invalid_time() {
156 assert_eq!(parse_global_datetime("2006-06-05T24:31"), None);
157 }
158
159 #[test]
160 fn test_parse_global_datetime_fails_invalid_time_long_pos() {
161 assert_eq!(parse_global_datetime("2006-06-05T24:31:5999"), None);
162 }
163
164 #[test]
165 fn test_parse_global_datetime_fails_invalid_timezone_offset_1() {
166 assert_eq!(parse_global_datetime("2019-12-31T11:17+24:00"), None);
167 }
168
169 #[test]
170 fn test_parse_global_datetime_fails_invalid_timezone_offset_2() {
171 assert_eq!(parse_global_datetime("1456-02-24T11:17C"), None);
172 }
173}