use std::sync::LazyLock;
use crate::calculation::duration::{Duration, DurationUnit};
use crate::common::word_boundary::{WithWordBoundary, WordBoundaryParser};
use crate::core::chrono::{Extracted, Parser};
use crate::core::components::{meridiem, Component, ParsingComponents};
use crate::core::configuration::Flags;
use crate::core::context::ParsingContext;
use crate::core::regex::{JsRegex, RegexMatch};
use crate::core::utils::js::js_parse_int;
use crate::locales::ja::constants::{ja_string_to_number, to_hankaku, NUMBER};
pub const UPSTREAM: &str = "JPTimeExpressionParser";
pub fn create(_flags: &Flags) -> Box<dyn Parser> {
Box::new(WithWordBoundary::new(JpTimeExpressionParser))
}
fn number_keys() -> String {
NUMBER.iter().map(|(k, _)| *k).collect::<String>()
}
static FIRST_REG_PATTERN: LazyLock<JsRegex> = LazyLock::new(|| {
let n = number_keys();
JsRegex::new(
&format!(
concat!(
r"(?:(午前|午後|A.M.|P.M.|AM|PM))?",
r"(?:[\s,,、]*)",
r"(?:([0-90-9]+|[{n}]+)(?:\s*)(?:時(?!間)|:|:)",
r"(?:\s*)",
r"([0-90-9]+|半|[{n}]+)?(?:\s*)(?:分|:|:)?",
r"(?:\s*)",
r"([0-90-9]+|[{n}]+)?(?:\s*)(?:秒)?)",
r"(?:\s*(A.M.|P.M.|AM?|PM?))?",
),
n = n
),
"i",
)
});
static SECOND_REG_PATTERN: LazyLock<JsRegex> = LazyLock::new(|| {
let n = number_keys();
JsRegex::new(
&format!(
concat!(
r"(?:^\s*(?:から|\-|\–|\-|\~|\〜)\s*)",
r"(?:(午前|午後|A.M.|P.M.|AM|PM))?",
r"(?:[\s,,、]*)",
r"(?:([0-90-9]+|[{n}]+)(?:\s*)(?:時|:|:)",
r"(?:\s*)",
r"([0-90-9]+|半|[{n}]+)?(?:\s*)(?:分|:|:)?",
r"(?:\s*)",
r"([0-90-9]+|[{n}]+)?(?:\s*)(?:秒)?)",
r"(?:\s*(A.M.|P.M.|AM?|PM?))?",
),
n = n
),
"i",
)
});
const AM_PM_HOUR_GROUP_1: usize = 1;
const HOUR_GROUP: usize = 2;
const MINUTE_GROUP: usize = 3;
const SECOND_GROUP: usize = 4;
const AM_PM_HOUR_GROUP_2: usize = 5;
struct JpTimeExpressionParser;
fn am_pm(m: &RegexMatch) -> Option<String> {
m.get(AM_PM_HOUR_GROUP_1)
.or_else(|| m.get(AM_PM_HOUR_GROUP_2))
.map(str::to_string)
}
impl WordBoundaryParser for JpTimeExpressionParser {
fn inner_pattern(&self, _context: &ParsingContext) -> JsRegex {
FIRST_REG_PATTERN.clone()
}
fn inner_extract(&self, context: &ParsingContext, m: &mut RegexMatch) -> Extracted {
if m.index > 0 {
if let Some(prev) = context.text[..m.index].chars().next_back() {
if prev.is_ascii_alphanumeric() || prev == '_' {
return Extracted::None;
}
}
}
let mut result = context.create_parsing_result(m.index, m.text(), None, None);
let start = create_time_components(
context,
m.get(HOUR_GROUP),
m.get(MINUTE_GROUP),
m.get(SECOND_GROUP),
am_pm(m).as_deref(),
);
match start {
Some(start) => result.start = start,
None => {
m.index += m.text().len(); return Extracted::None;
}
}
let rest = context.text.get(result.index + result.text.len()..).unwrap_or("");
let Some(m2) = SECOND_REG_PATTERN.exec(rest) else {
return Extracted::Result(result);
};
result.text.push_str(m2.text());
let Some(mut end) = create_time_components(
context,
m2.get(HOUR_GROUP),
m2.get(MINUTE_GROUP),
m2.get(SECOND_GROUP),
am_pm(&m2).as_deref(),
) else {
return Extracted::None;
};
if !end.is_certain(Component::Meridiem) && result.start.is_certain(Component::Meridiem) {
let start_meridiem = result.start.get_or_zero(Component::Meridiem);
end.imply(Component::Meridiem, start_meridiem);
if start_meridiem == meridiem::PM {
let end_hour = end.get_or_zero(Component::Hour);
if result.start.get_or_zero(Component::Hour) - 12 > end_hour {
end.imply(Component::Meridiem, meridiem::AM);
} else if end_hour < 12 {
end.assign(Component::Hour, end_hour + 12);
}
}
}
if let (Some(e), Some(s)) = (end.date().get_time(), result.start.date().get_time()) {
if e < s {
end.add_duration_as_implied(&mut Duration::from_pairs(&[(DurationUnit::Day, 1.0)]));
}
}
result.end = Some(end);
Extracted::Result(result)
}
}
fn parse_number(s: &str) -> Option<i64> {
js_parse_int(&to_hankaku(s)).or_else(|| ja_string_to_number(s))
}
fn create_time_components(
context: &ParsingContext,
match_hour: Option<&str>,
match_minute: Option<&str>,
match_second: Option<&str>,
match_am_pm: Option<&str>,
) -> Option<ParsingComponents> {
let mut meridiem_value: i64 = -1;
let mut target = context.create_parsing_components(&[]);
let mut hour = parse_number(match_hour.unwrap_or(""))?;
if hour > 24 {
return None;
}
if let Some(mm) = match_minute.filter(|s| !s.is_empty()) {
let minute = if mm == "半" { 30 } else { parse_number(mm)? };
if minute >= 60 {
return None;
}
target.assign(Component::Minute, minute);
}
if let Some(ms) = match_second.filter(|s| !s.is_empty()) {
let second = parse_number(ms)?;
if second >= 60 {
return None;
}
target.assign(Component::Second, second);
}
if let Some(ap) = match_am_pm.filter(|s| !s.is_empty()) {
if hour > 12 {
return None;
}
let first = ap.chars().next().map(|c| c.to_lowercase().collect::<String>()).unwrap_or_default();
if ap == "午前" || first == "a" {
meridiem_value = meridiem::AM;
if hour == 12 {
hour = 0;
}
} else if ap == "午後" || first == "p" {
meridiem_value = meridiem::PM;
if hour != 12 {
hour += 12;
}
}
}
target.assign(Component::Hour, hour);
if meridiem_value >= 0 {
target.assign(Component::Meridiem, meridiem_value);
} else if hour < 12 {
target.imply(Component::Meridiem, meridiem::AM);
} else {
target.imply(Component::Meridiem, meridiem::PM);
}
Some(target)
}