diff --git a/autotests/dfm-search-tests/tst_chinese_nlp.cpp b/autotests/dfm-search-tests/tst_chinese_nlp.cpp index 2971e72d..d845c36e 100644 --- a/autotests/dfm-search-tests/tst_chinese_nlp.cpp +++ b/autotests/dfm-search-tests/tst_chinese_nlp.cpp @@ -959,6 +959,20 @@ void tst_ChineseNLP::timeCustom_month() m_parser->parse(QStringLiteral("5月份的图片"), intent2); QCOMPARE(intent2.timeConstraint().kind(), TimeConstraintKind::Custom); QCOMPARE(intent2.timeConstraint().customStart().date().month(), 5); + + // "七月" — Chinese numeral month should work too + ParsedIntent intent3; + m_parser->parse(QStringLiteral("七月的文件"), intent3); + QCOMPARE(intent3.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent3.timeConstraint().customStart().date().month(), 7); + QCOMPARE(intent3.timeConstraint().customEnd().date().month(), 7); + + // "十二月" — leading-ten Chinese numeral month should work too + ParsedIntent intent4; + m_parser->parse(QStringLiteral("十二月的文件"), intent4); + QCOMPARE(intent4.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent4.timeConstraint().customStart().date().month(), 12); + QCOMPARE(intent4.timeConstraint().customEnd().date().month(), 12); } void tst_ChineseNLP::timeCustom_yearMonth() @@ -988,6 +1002,22 @@ void tst_ChineseNLP::timeCustom_yearMonth() QCOMPARE(intent3.timeConstraint().customStart().date().year(), 2025); QCOMPARE(intent3.timeConstraint().customStart().date().month(), 12); QCOMPARE(intent3.timeConstraint().customStart().date().day(), 1); + + // "2025年七月" — Chinese numeral month should also be parsed + ParsedIntent intent4; + m_parser->parse(QStringLiteral("2025年七月的文档"), intent4); + QCOMPARE(intent4.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent4.timeConstraint().customStart().date().year(), 2025); + QCOMPARE(intent4.timeConstraint().customStart().date().month(), 7); + QCOMPARE(intent4.timeConstraint().customStart().date().day(), 1); + + // "2025年十一月" — leading-ten Chinese numeral month in year-month form + ParsedIntent intent5; + m_parser->parse(QStringLiteral("2025年十一月的文档"), intent5); + QCOMPARE(intent5.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent5.timeConstraint().customStart().date().year(), 2025); + QCOMPARE(intent5.timeConstraint().customStart().date().month(), 11); + QCOMPARE(intent5.timeConstraint().customStart().date().day(), 1); } void tst_ChineseNLP::timeCustom_yearMonth_separators() @@ -1035,6 +1065,20 @@ void tst_ChineseNLP::timeCustom_dateSpoken() QCOMPARE(intent.timeConstraint().kind(), TimeConstraintKind::Custom); QCOMPARE(intent.timeConstraint().customStart().date().month(), 3); QCOMPARE(intent.timeConstraint().customStart().date().day(), 8); + + // "七月八号" — Chinese numeral month/day should work too + ParsedIntent intent2; + m_parser->parse(QStringLiteral("七月八号的图片"), intent2); + QCOMPARE(intent2.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent2.timeConstraint().customStart().date().month(), 7); + QCOMPARE(intent2.timeConstraint().customStart().date().day(), 8); + + // "十二月十五号" — leading-ten Chinese numeral month/day should work too + ParsedIntent intent3; + m_parser->parse(QStringLiteral("十二月十五号的图片"), intent3); + QCOMPARE(intent3.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent3.timeConstraint().customStart().date().month(), 12); + QCOMPARE(intent3.timeConstraint().customStart().date().day(), 15); } void tst_ChineseNLP::timeCustom_fullDate() @@ -1060,6 +1104,14 @@ void tst_ChineseNLP::timeCustom_fullDate() QCOMPARE(intent2.timeConstraint().customStart().date().year(), 2025); QCOMPARE(intent2.timeConstraint().customStart().date().month(), 12); QCOMPARE(intent2.timeConstraint().customStart().date().day(), 30); + + // "2025年七月三十日" — Chinese numeral month/day should work too + ParsedIntent intent3; + m_parser->parse(QStringLiteral("2025年七月三十日的文档"), intent3); + QCOMPARE(intent3.timeConstraint().kind(), TimeConstraintKind::Custom); + QCOMPARE(intent3.timeConstraint().customStart().date().year(), 2025); + QCOMPARE(intent3.timeConstraint().customStart().date().month(), 7); + QCOMPARE(intent3.timeConstraint().customStart().date().day(), 30); } void tst_ChineseNLP::timeCustom_fullDate_separators() @@ -1912,6 +1964,13 @@ void tst_ChineseNLP::timeDynamic_chineseNumerals() QCOMPARE(intent5.timeConstraint().kind(), TimeConstraintKind::Relative); QCOMPARE(intent5.timeConstraint().relativeValue(), 7); + // "最近十五天" — leading-ten Chinese numeral in dynamic relative time + ParsedIntent intent6; + m_parser->parse(QStringLiteral("最近十五天"), intent6); + QCOMPARE(intent6.timeConstraint().kind(), TimeConstraintKind::Relative); + QCOMPARE(intent6.timeConstraint().relativeValue(), 15); + QCOMPARE(intent6.timeConstraint().relativeUnit(), TimeUnit::Days); + // Mixed: Arabic + Chinese should still work // "最近3天" already tested above } diff --git a/src/dfm-search/dfm-search-lib/semantic/extractors/timeextractor.cpp b/src/dfm-search/dfm-search-lib/semantic/extractors/timeextractor.cpp index f7843fdb..712e01b8 100644 --- a/src/dfm-search/dfm-search-lib/semantic/extractors/timeextractor.cpp +++ b/src/dfm-search/dfm-search-lib/semantic/extractors/timeextractor.cpp @@ -270,10 +270,18 @@ int TimeExtractor::localeAwareToInt(const QString &input, return -1; } - // Two-character pattern: "XY" where Y is the tens unit (e.g., "十五" = 15) + // Leading tens-unit pattern: "十X" (e.g., "十一" = 11, "十五" = 15) + if (input.size() == 2 && input.left(1) == tensUnit) { + int suffix = digitMap.value(input.right(1), -1); + if (suffix >= 0) { + return 10 + suffix; + } + } + + // Two-character pattern: "XY" where Y is the tens unit (e.g., "二十" = 20) if (input.size() == 2 && input.mid(1) == tensUnit) { int prefix = digitMap.value(input.left(1), -1); - if (prefix > 1) { + if (prefix > 0) { return prefix * 10; } // "十" alone = 10 diff --git a/src/dfm-search/dfm-search-lib/semantic/rules/zh_CN/time_rules.json b/src/dfm-search/dfm-search-lib/semantic/rules/zh_CN/time_rules.json index 31e13dec..f56c947e 100644 --- a/src/dfm-search/dfm-search-lib/semantic/rules/zh_CN/time_rules.json +++ b/src/dfm-search/dfm-search-lib/semantic/rules/zh_CN/time_rules.json @@ -132,7 +132,7 @@ }, { "id": "time_exact_month_current_year", - "pattern": "(?\\d{1,2})月份?", + "pattern": "(?(?:\\d{1,2}|[零一二两三四五六七八九十]{1,3}))月份?", "description": "Month this year (e.g. 12月, 5月份)", "enabled": true, "priority": 120, @@ -157,7 +157,7 @@ }, { "id": "time_exact_year_month", - "pattern": "(?\\d{2,4})[年\\./\\-](?\\d{1,2})(?:月份?)?", + "pattern": "(?\\d{2,4})[年\\./\\-](?(?:\\d{1,2}|[零一二两三四五六七八九十]{1,3}))(?:月份?)?", "description": "Exact year-month (e.g. 2025年12月, 2025-12)", "enabled": true, "priority": 140, @@ -182,7 +182,7 @@ }, { "id": "time_exact_date_current_year", - "pattern": "(?\\d{1,2})月(?\\d{1,2})[日号]", + "pattern": "(?(?:\\d{1,2}|[零一二两三四五六七八九十]{1,3}))月(?(?:\\d{1,2}|[零一二两三四五六七八九十]{1,3}))[日号]", "description": "Exact date this year (e.g. 12月5日, 3月8号)", "enabled": true, "priority": 140, @@ -207,7 +207,7 @@ }, { "id": "time_exact_full_date", - "pattern": "(?\\d{2,4})[年\\./\\-](?\\d{1,2})[月\\./\\-](?\\d{1,2})[日号]?", + "pattern": "(?\\d{2,4})[年\\./\\-](?(?:\\d{1,2}|[零一二两三四五六七八九十]{1,3}))[月\\./\\-](?(?:\\d{1,2}|[零一二两三四五六七八九十]{1,3}))[日号]?", "description": "Exact full date (e.g. 2025年12月5日, 2025-12-05)", "enabled": true, "priority": 160,