whichtime_sys/parsers/en/
casual_time.rs1use crate::components::Component;
5use crate::context::ParsingContext;
6use crate::dictionaries::en::{get_casual_time, get_relative_modifier};
7use crate::dictionaries::{CasualTimeType, RelativeModifier};
8use crate::error::Result;
9use crate::parsers::Parser;
10use crate::results::ParsedResult;
11use crate::scanner::TokenType;
12use crate::types::Meridiem;
13use chrono::{Datelike, Duration, Timelike};
14use regex::Regex;
15use std::sync::LazyLock;
16
17static PATTERN: LazyLock<Regex> = LazyLock::new(|| {
19 Regex::new(r"(?i)\b(?:(this|last|next|past|previous)\s+)?(noon|midday|midnight|morning|afternoon|evening|night)\b").unwrap()
20});
21
22pub struct CasualTimeParser;
24
25impl Parser for CasualTimeParser {
26 fn name(&self) -> &'static str {
27 "CasualTimeParser"
28 }
29
30 fn should_apply(&self, context: &ParsingContext) -> bool {
31 context.has_token_type(TokenType::CasualTime)
32 }
33
34 fn parse(&self, context: &ParsingContext) -> Result<Vec<ParsedResult>> {
35 let mut results = Vec::new();
36
37 for mat in PATTERN.find_iter(context.text) {
38 let matched_text = mat.as_str();
39 let index = mat.start();
40
41 let Some(caps) = PATTERN.captures(matched_text) else {
42 continue;
43 };
44
45 let modifier_str = caps.get(1).map(|m| m.as_str().to_lowercase());
46 let time_word = caps
47 .get(2)
48 .map(|m| m.as_str().to_lowercase())
49 .unwrap_or_default();
50
51 let Some(time_type) = get_casual_time(&time_word) else {
52 continue;
53 };
54
55 let modifier = modifier_str.as_deref().and_then(get_relative_modifier);
56
57 let mut components = context.create_components();
58 let ref_date = context.reference.instant;
59
60 let target_date = match modifier {
62 Some(RelativeModifier::Last) => {
63 if ref_date.hour() <= 6 && matches!(time_type, CasualTimeType::Night) {
65 ref_date
66 } else {
67 ref_date - Duration::days(1)
68 }
69 }
70 Some(RelativeModifier::Next) => ref_date + Duration::days(1),
71 Some(RelativeModifier::This) | None => ref_date,
72 };
73
74 components.assign(Component::Year, target_date.year());
76 components.assign(Component::Month, target_date.month() as i32);
77 components.assign(Component::Day, target_date.day() as i32);
78
79 match time_type {
81 CasualTimeType::Noon => {
82 components.assign(Component::Hour, 12);
83 components.assign(Component::Minute, 0);
84 components.assign(Component::Second, 0);
85 components.assign(Component::Meridiem, Meridiem::PM as i32);
86 }
87 CasualTimeType::Midnight => {
88 if matches!(modifier, Some(RelativeModifier::Last)) {
91 components.assign(Component::Day, target_date.day() as i32);
93 } else if modifier.is_none() {
94 let next_day = ref_date + Duration::days(1);
96 components.assign(Component::Year, next_day.year());
97 components.assign(Component::Month, next_day.month() as i32);
98 components.assign(Component::Day, next_day.day() as i32);
99 }
100 components.assign(Component::Hour, 0);
101 components.assign(Component::Minute, 0);
102 components.assign(Component::Second, 0);
103 }
104 CasualTimeType::Morning => {
105 components.imply(Component::Hour, 6);
106 components.imply(Component::Minute, 0);
107 components.assign(Component::Meridiem, Meridiem::AM as i32);
108 }
109 CasualTimeType::Afternoon => {
110 components.imply(Component::Hour, 15);
111 components.imply(Component::Minute, 0);
112 components.assign(Component::Meridiem, Meridiem::PM as i32);
113 }
114 CasualTimeType::Evening => {
115 components.imply(Component::Hour, 20);
116 components.imply(Component::Minute, 0);
117 components.assign(Component::Meridiem, Meridiem::PM as i32);
118 }
119 CasualTimeType::Night => {
120 components.imply(Component::Hour, 0);
121 components.imply(Component::Minute, 0);
122 components.assign(Component::Meridiem, Meridiem::AM as i32);
123 }
124 }
125
126 results.push(context.create_result(
127 index,
128 index + matched_text.len(),
129 components,
130 None,
131 ));
132 }
133
134 Ok(results)
135 }
136}