1use super::js_date::JsDate;
4use super::reference::ReferenceWithTimezone;
5use crate::calculation::duration::{add_duration, Duration, DurationUnit};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
9pub enum Component {
10 Year,
11 Month,
12 Day,
13 Weekday,
14 Hour,
15 Minute,
16 Second,
17 Millisecond,
18 Meridiem,
19 TimezoneOffset,
20}
21
22impl Component {
23 pub const ALL: [Component; 10] = [
24 Component::Year,
25 Component::Month,
26 Component::Day,
27 Component::Weekday,
28 Component::Hour,
29 Component::Minute,
30 Component::Second,
31 Component::Millisecond,
32 Component::Meridiem,
33 Component::TimezoneOffset,
34 ];
35
36 pub fn name(self) -> &'static str {
38 match self {
39 Component::Year => "year",
40 Component::Month => "month",
41 Component::Day => "day",
42 Component::Weekday => "weekday",
43 Component::Hour => "hour",
44 Component::Minute => "minute",
45 Component::Second => "second",
46 Component::Millisecond => "millisecond",
47 Component::Meridiem => "meridiem",
48 Component::TimezoneOffset => "timezoneOffset",
49 }
50 }
51
52 pub fn from_name(name: &str) -> Option<Component> {
53 Component::ALL.into_iter().find(|c| c.name() == name)
54 }
55}
56
57pub mod meridiem {
59 pub const AM: i64 = 0;
60 pub const PM: i64 = 1;
61}
62
63pub mod weekday {
65 pub const SUNDAY: i64 = 0;
66 pub const MONDAY: i64 = 1;
67 pub const TUESDAY: i64 = 2;
68 pub const WEDNESDAY: i64 = 3;
69 pub const THURSDAY: i64 = 4;
70 pub const FRIDAY: i64 = 5;
71 pub const SATURDAY: i64 = 6;
72}
73
74#[derive(Clone, Debug, Default, PartialEq, Eq)]
76pub struct ComponentMap(Vec<(Component, i64)>);
77
78impl ComponentMap {
79 pub fn new() -> ComponentMap {
80 ComponentMap(Vec::new())
81 }
82
83 pub fn from_pairs(pairs: &[(Component, i64)]) -> ComponentMap {
84 let mut map = ComponentMap::new();
85 for &(c, v) in pairs {
86 map.set(c, v);
87 }
88 map
89 }
90
91 pub fn get(&self, c: Component) -> Option<i64> {
92 self.0.iter().find(|(k, _)| *k == c).map(|(_, v)| *v)
93 }
94
95 pub fn contains(&self, c: Component) -> bool {
96 self.0.iter().any(|(k, _)| *k == c)
97 }
98
99 pub fn set(&mut self, c: Component, v: i64) {
101 match self.0.iter_mut().find(|(k, _)| *k == c) {
102 Some(slot) => slot.1 = v,
103 None => self.0.push((c, v)),
104 }
105 }
106
107 pub fn remove(&mut self, c: Component) {
108 self.0.retain(|(k, _)| *k != c);
109 }
110
111 pub fn keys(&self) -> impl Iterator<Item = Component> + '_ {
112 self.0.iter().map(|(k, _)| *k)
113 }
114
115 pub fn iter(&self) -> impl Iterator<Item = (Component, i64)> + '_ {
116 self.0.iter().copied()
117 }
118
119 pub fn is_empty(&self) -> bool {
120 self.0.is_empty()
121 }
122}
123
124#[derive(Clone, Debug, PartialEq)]
129pub struct ParsingComponents {
130 reference: ReferenceWithTimezone,
131 known: ComponentMap,
132 implied: ComponentMap,
133 tags: Vec<String>,
134}
135
136impl ParsingComponents {
137 pub fn from_parts(
140 reference: ReferenceWithTimezone,
141 known: ComponentMap,
142 implied: ComponentMap,
143 tags: Vec<String>,
144 ) -> ParsingComponents {
145 ParsingComponents {
146 reference,
147 known,
148 implied,
149 tags,
150 }
151 }
152
153 pub fn new(reference: ReferenceWithTimezone, known: &[(Component, i64)]) -> ParsingComponents {
155 let mut c = ParsingComponents {
156 reference,
157 known: ComponentMap::from_pairs(known),
158 implied: ComponentMap::new(),
159 tags: Vec::new(),
160 };
161 let date = reference.get_date_with_adjusted_timezone();
162 c.imply(Component::Day, date.get_date());
163 c.imply(Component::Month, date.get_month() + 1);
164 c.imply(Component::Year, date.get_full_year());
165 c.imply(Component::Hour, 12);
166 c.imply(Component::Minute, 0);
167 c.imply(Component::Second, 0);
168 c.imply(Component::Millisecond, 0);
169 c
170 }
171
172 pub fn create_relative_from_reference(
175 reference: ReferenceWithTimezone,
176 duration: &mut Duration,
177 ) -> ParsingComponents {
178 let date = add_duration(reference.get_date_with_adjusted_timezone(), duration);
179
180 let mut c = ParsingComponents::new(reference, &[]);
181 c.add_tag("result/relativeDate");
182 let has = |u| duration.has(u);
183 if has(DurationUnit::Hour)
184 || has(DurationUnit::Minute)
185 || has(DurationUnit::Second)
186 || has(DurationUnit::Millisecond)
187 {
188 c.add_tag("result/relativeDateAndTime");
189 crate::core::utils::dates::assign_similar_time(&mut c, date);
190 crate::core::utils::dates::assign_similar_date(&mut c, date);
191 c.assign(Component::TimezoneOffset, reference.get_timezone_offset());
192 } else {
193 crate::core::utils::dates::imply_similar_time(&mut c, date);
194 c.imply(Component::TimezoneOffset, reference.get_timezone_offset());
195 if has(DurationUnit::Day) {
196 c.assign(Component::Day, date.get_date());
197 c.assign(Component::Month, date.get_month() + 1);
198 c.assign(Component::Year, date.get_full_year());
199 c.assign(Component::Weekday, date.get_day());
200 } else if has(DurationUnit::Week) {
201 c.assign(Component::Day, date.get_date());
202 c.assign(Component::Month, date.get_month() + 1);
203 c.assign(Component::Year, date.get_full_year());
204 c.imply(Component::Weekday, date.get_day());
205 } else {
206 c.imply(Component::Day, date.get_date());
207 if has(DurationUnit::Month) {
208 c.assign(Component::Month, date.get_month() + 1);
209 c.assign(Component::Year, date.get_full_year());
210 } else {
211 c.imply(Component::Month, date.get_month() + 1);
212 if has(DurationUnit::Year) {
213 c.assign(Component::Year, date.get_full_year());
214 } else {
215 c.imply(Component::Year, date.get_full_year());
216 }
217 }
218 }
219 }
220 c
221 }
222
223 pub fn reference(&self) -> ReferenceWithTimezone {
224 self.reference
225 }
226
227 pub fn get(&self, c: Component) -> Option<i64> {
229 self.known.get(c).or_else(|| self.implied.get(c))
230 }
231
232 pub fn get_or_zero(&self, c: Component) -> i64 {
234 self.get(c).unwrap_or(0)
235 }
236
237 pub fn is_certain(&self, c: Component) -> bool {
238 self.known.contains(c)
239 }
240
241 pub fn get_certain_components(&self) -> Vec<Component> {
242 self.known.keys().collect()
243 }
244
245 pub fn known_values(&self) -> &ComponentMap {
246 &self.known
247 }
248
249 pub fn implied_values(&self) -> &ComponentMap {
250 &self.implied
251 }
252
253 pub fn imply(&mut self, c: Component, v: i64) -> &mut Self {
255 if !self.known.contains(c) {
256 self.implied.set(c, v);
257 }
258 self
259 }
260
261 pub fn assign(&mut self, c: Component, v: i64) -> &mut Self {
263 self.known.set(c, v);
264 self.implied.remove(c);
265 self
266 }
267
268 pub fn add_duration_as_implied(&mut self, duration: &mut Duration) -> &mut Self {
270 let current = self.date_without_timezone_adjustment();
271 let date = add_duration(current, duration);
272 let has = |u| duration.has(u);
273 if has(DurationUnit::Day) || has(DurationUnit::Week) || has(DurationUnit::Month) || has(DurationUnit::Year) {
274 self.delete(&[Component::Day, Component::Weekday, Component::Month, Component::Year]);
275 self.imply(Component::Day, date.get_date());
276 self.imply(Component::Weekday, date.get_day());
277 self.imply(Component::Month, date.get_month() + 1);
278 self.imply(Component::Year, date.get_full_year());
279 }
280 if has(DurationUnit::Second) || has(DurationUnit::Minute) || has(DurationUnit::Hour) {
281 self.delete(&[Component::Second, Component::Minute, Component::Hour]);
282 self.imply(Component::Second, date.get_seconds());
283 self.imply(Component::Minute, date.get_minutes());
284 self.imply(Component::Hour, date.get_hours());
285 }
286 self
287 }
288
289 pub fn delete(&mut self, components: &[Component]) {
290 for &c in components {
291 self.known.remove(c);
292 self.implied.remove(c);
293 }
294 }
295
296 pub fn is_only_date(&self) -> bool {
297 !self.is_certain(Component::Hour) && !self.is_certain(Component::Minute) && !self.is_certain(Component::Second)
298 }
299
300 pub fn is_only_time(&self) -> bool {
301 !self.is_certain(Component::Weekday)
302 && !self.is_certain(Component::Day)
303 && !self.is_certain(Component::Month)
304 && !self.is_certain(Component::Year)
305 }
306
307 pub fn is_only_weekday_component(&self) -> bool {
308 self.is_certain(Component::Weekday) && !self.is_certain(Component::Day) && !self.is_certain(Component::Month)
309 }
310
311 pub fn is_date_with_unknown_year(&self) -> bool {
312 self.is_certain(Component::Month) && !self.is_certain(Component::Year)
313 }
314
315 fn utc_from_fields(&self) -> JsDate {
316 let g = |c| self.get_or_zero(c);
317 let mut date = JsDate::utc(
318 g(Component::Year),
319 g(Component::Month) - 1,
320 g(Component::Day),
321 g(Component::Hour),
322 g(Component::Minute),
323 g(Component::Second),
324 g(Component::Millisecond),
325 );
326 date.set_full_year(g(Component::Year));
327 date
328 }
329
330 pub fn is_valid_date(&self) -> bool {
332 let date = self.utc_from_fields();
333 if !date.is_valid() {
334 return false;
335 }
336 let g = |c| self.get_or_zero(c);
337 if date.get_full_year() != g(Component::Year) {
338 return false;
339 }
340 if date.get_month() != g(Component::Month) - 1 {
341 return false;
342 }
343 if date.get_date() != g(Component::Day) {
344 return false;
345 }
346 if self.get(Component::Hour).is_some_and(|h| date.get_hours() != h) {
347 return false;
348 }
349 if self.get(Component::Minute).is_some_and(|m| date.get_minutes() != m) {
350 return false;
351 }
352 true
353 }
354
355 pub fn date(&self) -> JsDate {
357 let offset = self.get(Component::TimezoneOffset).or(self.reference.timezone_offset);
358 match offset {
359 None => self.date_without_timezone_adjustment(),
360 Some(offset) => match self.utc_from_fields().get_time() {
361 Some(t) => JsDate::from_millis(t - offset * 60_000),
362 None => JsDate::INVALID,
363 },
364 }
365 }
366
367 pub fn date_without_timezone_adjustment(&self) -> JsDate {
369 let g = |c| self.get_or_zero(c);
370 let mut date = JsDate::local(
371 g(Component::Year),
372 g(Component::Month) - 1,
373 g(Component::Day),
374 g(Component::Hour),
375 g(Component::Minute),
376 g(Component::Second),
377 g(Component::Millisecond),
378 );
379 date.set_full_year(g(Component::Year));
380 date
381 }
382
383 pub fn add_tag(&mut self, tag: &str) -> &mut Self {
384 if !self.tags.iter().any(|t| t == tag) {
385 self.tags.push(tag.to_string());
386 }
387 self
388 }
389
390 pub fn add_tags<'a>(&mut self, tags: impl IntoIterator<Item = &'a str>) -> &mut Self {
391 for tag in tags {
392 self.add_tag(tag);
393 }
394 self
395 }
396
397 pub fn js_clone(&self) -> ParsingComponents {
401 ParsingComponents {
402 reference: self.reference,
403 known: self.known.clone(),
404 implied: self.implied.clone(),
405 tags: Vec::new(),
406 }
407 }
408
409 pub fn tags(&self) -> &[String] {
410 &self.tags
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417
418 fn reference() -> ReferenceWithTimezone {
419 ReferenceWithTimezone::new(JsDate::parse_iso("2012-08-10T09:30:00.000Z").unwrap(), None)
420 }
421
422 #[test]
423 fn js_clone_keeps_values_and_drops_tags_like_upstream() {
424 let mut c = ParsingComponents::new(reference(), &[(Component::Day, 21), (Component::Month, 7)]);
425 c.imply(Component::Year, 2038);
426 c.add_tag("parser/ENMonthNameMiddleEndianParser");
427 let js = c.js_clone();
428 assert_eq!(js.get(Component::Day), Some(21));
429 assert_eq!(js.get(Component::Year), Some(2038));
430 assert!(js.is_certain(Component::Month));
431 assert!(!js.is_certain(Component::Year));
432 assert!(js.tags().is_empty(), "upstream clone() doesn't copy tags");
433 assert_eq!(c.clone().tags(), c.tags(), "Rust Clone does");
434 }
435
436 #[test]
437 fn new_implies_reference_day_and_noon() {
438 let c = ParsingComponents::new(reference(), &[(Component::Weekday, 1)]);
439 assert_eq!(c.get(Component::Day), Some(10));
440 assert_eq!(c.get(Component::Hour), Some(12));
441 assert!(c.is_certain(Component::Weekday));
442 assert!(!c.is_certain(Component::Day));
443 assert_eq!(c.get(Component::TimezoneOffset), None);
444 assert_eq!(c.date().to_iso_string().unwrap(), "2012-08-10T12:00:00.000Z");
445 }
446
447 #[test]
448 fn assign_and_imply() {
449 let mut c = ParsingComponents::new(reference(), &[]);
450 c.assign(Component::Hour, 5).imply(Component::Hour, 7);
451 assert_eq!(c.get(Component::Hour), Some(5));
452 c.assign(Component::TimezoneOffset, 60);
453 assert_eq!(c.date().to_iso_string().unwrap(), "2012-08-10T04:00:00.000Z");
454 }
455
456 #[test]
457 fn add_duration_as_implied_crosses_month() {
458 let mut c = ParsingComponents::new(reference(), &[]);
459 c.add_duration_as_implied(&mut Duration::from_pairs(&[(DurationUnit::Day, 25.0)]));
460 assert_eq!(
461 (
462 c.get(Component::Month),
463 c.get(Component::Day),
464 c.get(Component::Weekday)
465 ),
466 (Some(9), Some(4), Some(2))
467 );
468 assert!(!c.is_certain(Component::Day));
469 }
470
471 #[test]
472 fn validity() {
473 let c = ParsingComponents::new(reference(), &[(Component::Month, 2), (Component::Day, 30)]);
474 assert!(!c.is_valid_date());
475 let c = ParsingComponents::new(reference(), &[(Component::Month, 2), (Component::Day, 29)]);
476 assert!(c.is_valid_date()); }
478}