耶2
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,11 @@
|
||||
[project]
|
||||
name = "ptimeparse"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
description = "一个用于解析中文的时间表达的库"
|
||||
authors = [
|
||||
{name = "passthem", email = "Passthem183@gmail.com"}
|
||||
]
|
||||
authors = [{ name = "passthem", email = "Passthem183@gmail.com" }]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry]
|
||||
@ -24,6 +21,4 @@ url = "https://gitea.service.jazzwhom.top/api/packages/Passthem/pypi"
|
||||
priority = "supplemental"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest (>=8.4.2,<9.0.0)"
|
||||
]
|
||||
dev = ["pytest (>=8.4.2,<9.0.0)"]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import datetime
|
||||
from datetime import datetime as dt
|
||||
|
||||
import pytest
|
||||
|
||||
@ -6,192 +6,351 @@ from ptimeparse import Parser
|
||||
from ptimeparse.err import MultipleSpecificationException, TokenUnhandledException
|
||||
|
||||
|
||||
def test_chinese_number():
|
||||
# --- 测试中文数字解析 ---
|
||||
@pytest.mark.parametrize(
|
||||
"input_str, expected_rest, expected_num",
|
||||
[
|
||||
("零", "", 0),
|
||||
("零喵", "喵", 0),
|
||||
("一喵", "喵", 1),
|
||||
("十喵", "喵", 10),
|
||||
("三千万喵", "喵", 3000_0000),
|
||||
("三千三百万喵", "喵", 3300_0000),
|
||||
("三千零三万喵", "喵", 3003_0000),
|
||||
("三千零三十万喵", "喵", 3030_0000),
|
||||
("五千四百零三万喵", "喵", 5403_0000),
|
||||
("五百万喵", "喵", 500_0000),
|
||||
("五万五千喵", "喵", 5_5000),
|
||||
("五万零五百喵", "喵", 5_0500),
|
||||
("五亿喵", "喵", 5_0000_0000),
|
||||
("五百亿喵", "喵", 500_0000_0000),
|
||||
("五百亿零五十喵", "喵", 500_0000_0050),
|
||||
("五百亿五十万喵", "喵", 500_0050_0000),
|
||||
],
|
||||
)
|
||||
def test_digest_chinese_number(input_str, expected_rest, expected_num):
|
||||
parser = Parser()
|
||||
|
||||
assert parser.digest_chinese_number("零") == ('', 0)
|
||||
assert parser.digest_chinese_number("零喵") == ('喵', 0)
|
||||
assert parser.digest_chinese_number("一喵") == ('喵', 1)
|
||||
assert parser.digest_chinese_number("十喵") == ('喵', 10)
|
||||
assert parser.digest_chinese_number("三千万喵") == ('喵', 3000_0000)
|
||||
assert parser.digest_chinese_number("三千三百万喵") == ('喵', 3300_0000)
|
||||
assert parser.digest_chinese_number("三千零三万喵") == ('喵', 3003_0000)
|
||||
assert parser.digest_chinese_number("三千零三十万喵") == ('喵', 3030_0000)
|
||||
assert parser.digest_chinese_number("五千四百零三万喵") == ('喵', 5403_0000)
|
||||
assert parser.digest_chinese_number("五百万喵") == ('喵', 500_0000)
|
||||
assert parser.digest_chinese_number("五万五千喵") == ('喵', 5_5000)
|
||||
assert parser.digest_chinese_number("五万零五百喵") == ('喵', 5_0500)
|
||||
assert parser.digest_chinese_number("五亿喵") == ('喵', 5_0000_0000)
|
||||
assert parser.digest_chinese_number("五百亿喵") == ('喵', 500_0000_0000)
|
||||
assert parser.digest_chinese_number("五百亿零五十喵") == ('喵', 500_0000_0050)
|
||||
assert parser.digest_chinese_number("五百亿五十万喵") == ('喵', 500_0050_0000)
|
||||
rest, num = parser.digest_chinese_number(input_str)
|
||||
# 使用 f-string 包含上下文信息
|
||||
assert rest == expected_rest, f"Input: {input_str}, Expected Rest: {expected_rest}, Actual Rest: {rest}"
|
||||
assert num == expected_num, f"Input: {input_str}, Expected Num: {expected_num}, Actual Num: {num}"
|
||||
|
||||
|
||||
def test_hour_specification_pm():
|
||||
parser = Parser(now=datetime.datetime(2025, 10, 9, 16, 34, 1, 114))
|
||||
# --- 测试时间解析(PM 上下文)---
|
||||
@pytest.mark.parametrize(
|
||||
"text, expected",
|
||||
[
|
||||
# 基础点表达(自动转为 PM if 1-12 且上下文为下午)
|
||||
("五点", dt(2025, 10, 9, 17, 0)),
|
||||
("5点", dt(2025, 10, 9, 17, 0)),
|
||||
("5 点", dt(2025, 10, 9, 17, 0)),
|
||||
("六点", dt(2025, 10, 9, 18, 0)),
|
||||
("六点整", dt(2025, 10, 9, 18, 0)),
|
||||
("六点钟", dt(2025, 10, 9, 18, 0)),
|
||||
("四点", dt(2025, 10, 9, 16, 0)),
|
||||
|
||||
assert parser.parse("五点") == datetime.datetime(2025, 10, 9, 17, 0, 0, 0)
|
||||
assert parser.parse("5点") == datetime.datetime(2025, 10, 9, 17, 0, 0, 0)
|
||||
assert parser.parse("5 点") == datetime.datetime(2025, 10, 9, 17, 0, 0, 0)
|
||||
# 显式 "时" 表示 24 小时制
|
||||
("10 时", dt(2025, 10, 9, 10, 0)),
|
||||
("10 时整", dt(2025, 10, 9, 10, 0)),
|
||||
("13点", dt(2025, 10, 9, 13, 0)),
|
||||
("15点", dt(2025, 10, 9, 15, 0)),
|
||||
("13 时", dt(2025, 10, 9, 13, 0)),
|
||||
("15 时", dt(2025, 10, 9, 15, 0)),
|
||||
|
||||
assert parser.parse("六点") == datetime.datetime(2025, 10, 9, 18, 0, 0, 0)
|
||||
assert parser.parse("六点整") == datetime.datetime(2025, 10, 9, 18, 0, 0, 0)
|
||||
assert parser.parse("六点钟") == datetime.datetime(2025, 10, 9, 18, 0, 0, 0)
|
||||
# 显式上午/下午
|
||||
("上午十点", dt(2025, 10, 9, 10, 0)),
|
||||
("早晨十点", dt(2025, 10, 9, 10, 0)),
|
||||
("早上十点", dt(2025, 10, 9, 10, 0)),
|
||||
("早十", dt(2025, 10, 9, 10, 0)),
|
||||
("早八", dt(2025, 10, 9, 8, 0)),
|
||||
("晚六", dt(2025, 10, 9, 18, 0)),
|
||||
("下午三点", dt(2025, 10, 9, 15, 0)),
|
||||
("晚上八点", dt(2025, 10, 9, 20, 0)),
|
||||
("中午十二点", dt(2025, 10, 9, 12, 0)),
|
||||
("凌晨零点", dt(2025, 10, 9, 0, 0)),
|
||||
|
||||
assert parser.parse("10 时") == datetime.datetime(2025, 10, 9, 10, 0, 0, 0)
|
||||
assert parser.parse("10 时整") == datetime.datetime(2025, 10, 9, 10, 0, 0, 0)
|
||||
assert parser.parse("10点") == datetime.datetime(2025, 10, 9, 22, 0, 0, 0)
|
||||
assert parser.parse("10点整") == datetime.datetime(2025, 10, 9, 22, 0, 0, 0)
|
||||
# 特殊:晚上十二点 → 次日 00:00
|
||||
("晚上十二点", dt(2025, 10, 10, 0, 0)),
|
||||
|
||||
assert parser.parse("13点") == datetime.datetime(2025, 10, 9, 13, 0, 0, 0)
|
||||
assert parser.parse("15点") == datetime.datetime(2025, 10, 9, 15, 0, 0, 0)
|
||||
assert parser.parse("13 时") == datetime.datetime(2025, 10, 9, 13, 0, 0, 0)
|
||||
assert parser.parse("15 时") == datetime.datetime(2025, 10, 9, 15, 0, 0, 0)
|
||||
|
||||
assert parser.parse("四点") == datetime.datetime(2025, 10, 9, 16, 0, 0, 0)
|
||||
|
||||
assert parser.parse("上午十点") == datetime.datetime(2025, 10, 9, 10, 0, 0, 0)
|
||||
assert parser.parse("早晨十点") == datetime.datetime(2025, 10, 9, 10, 0, 0, 0)
|
||||
assert parser.parse("早上十点") == datetime.datetime(2025, 10, 9, 10, 0, 0, 0)
|
||||
assert parser.parse("早十") == datetime.datetime(2025, 10, 9, 10, 0, 0, 0)
|
||||
assert parser.parse("早八") == datetime.datetime(2025, 10, 9, 8, 0, 0, 0)
|
||||
assert parser.parse("晚六") == datetime.datetime(2025, 10, 9, 18, 0, 0, 0)
|
||||
assert parser.parse("下午三点") == datetime.datetime(2025, 10, 9, 15, 0, 0, 0)
|
||||
|
||||
assert parser.parse("晚上十二点") == datetime.datetime(2025, 10, 10, 0, 0, 0, 0)
|
||||
assert parser.parse("晚上八点") == datetime.datetime(2025, 10, 9, 20, 0, 0, 0)
|
||||
assert parser.parse("凌晨零点") == datetime.datetime(2025, 10, 9, 0, 0, 0, 0)
|
||||
assert parser.parse("中午十二点") == datetime.datetime(2025, 10, 9, 12, 0, 0, 0)
|
||||
|
||||
def test_hour_specification_am():
|
||||
parser = Parser(now=datetime.datetime(2025, 10, 9, 2, 34, 1, 114))
|
||||
|
||||
assert parser.parse("五点") == datetime.datetime(2025, 10, 9, 5, 0, 0, 0)
|
||||
assert parser.parse("5点") == datetime.datetime(2025, 10, 9, 5, 0, 0, 0)
|
||||
assert parser.parse("5 点") == datetime.datetime(2025, 10, 9, 5, 0, 0, 0)
|
||||
|
||||
assert parser.parse("六点") == datetime.datetime(2025, 10, 9, 6, 0, 0, 0)
|
||||
assert parser.parse("六点整") == datetime.datetime(2025, 10, 9, 6, 0, 0, 0)
|
||||
assert parser.parse("六点钟") == datetime.datetime(2025, 10, 9, 6, 0, 0, 0)
|
||||
|
||||
assert parser.parse("10 时") == datetime.datetime(2025, 10, 9, 10, 0, 0, 0)
|
||||
assert parser.parse("10 时整") == datetime.datetime(2025, 10, 9, 10, 0, 0, 0)
|
||||
assert parser.parse("10点") == datetime.datetime(2025, 10, 9, 10, 0, 0, 0)
|
||||
assert parser.parse("10点整") == datetime.datetime(2025, 10, 9, 10, 0, 0, 0)
|
||||
|
||||
assert parser.parse("四点") == datetime.datetime(2025, 10, 9, 4, 0, 0, 0)
|
||||
assert parser.parse("一点钟") == datetime.datetime(2025, 10, 9, 13, 0, 0, 0)
|
||||
# 注意:10点(无修饰)在 PM 上下文中 → 22:00
|
||||
("10点", dt(2025, 10, 9, 22, 0)),
|
||||
("10点整", dt(2025, 10, 9, 22, 0)),
|
||||
],
|
||||
)
|
||||
def test_parse_hour_pm_context(text, expected):
|
||||
"""在下午(16:34)上下文中解析时间"""
|
||||
NOW = dt(2025, 10, 9, 16, 34, 1, 114)
|
||||
parser = Parser(now=NOW)
|
||||
actual = parser.parse(text)
|
||||
# 移除微秒,以便比较
|
||||
expected_clean = expected.replace(microsecond=0)
|
||||
actual_clean = actual.replace(microsecond=0)
|
||||
# 使用 f-string 包含上下文信息:输入文本、now 上下文、计算值、期望值
|
||||
assert actual_clean == expected_clean, (
|
||||
f"Failed on Text: '{text}'. "
|
||||
f"Context (now): {NOW}. "
|
||||
f"Expected: {expected_clean}. "
|
||||
f"Actual: {actual_clean}."
|
||||
)
|
||||
|
||||
|
||||
def test_hour_with_minute():
|
||||
parser = Parser(now=datetime.datetime(2025, 10, 9, 16, 34, 1, 114))
|
||||
|
||||
assert parser.parse("六点半") == datetime.datetime(2025, 10, 9, 18, 30, 0, 0)
|
||||
assert parser.parse("六点半钟") == datetime.datetime(2025, 10, 9, 18, 30, 0, 0)
|
||||
assert parser.parse("六点一刻") == datetime.datetime(2025, 10, 9, 18, 15, 0, 0)
|
||||
assert parser.parse("六点过一刻") == datetime.datetime(2025, 10, 9, 18, 15, 0, 0)
|
||||
# --- 测试时间解析(AM 上下文)---
|
||||
@pytest.mark.parametrize(
|
||||
"text, expected",
|
||||
[
|
||||
("五点", dt(2025, 10, 9, 5, 0)),
|
||||
("5点", dt(2025, 10, 9, 5, 0)),
|
||||
("5 点", dt(2025, 10, 9, 5, 0)),
|
||||
("六点", dt(2025, 10, 9, 6, 0)),
|
||||
("六点整", dt(2025, 10, 9, 6, 0)),
|
||||
("六点钟", dt(2025, 10, 9, 6, 0)),
|
||||
("10 时", dt(2025, 10, 9, 10, 0)),
|
||||
("10 时整", dt(2025, 10, 9, 10, 0)),
|
||||
("10点", dt(2025, 10, 9, 10, 0)), # AM 上下文中 10点 → 10:00
|
||||
("10点整", dt(2025, 10, 9, 10, 0)),
|
||||
("四点", dt(2025, 10, 9, 4, 0)),
|
||||
# 一点钟在 AM 上下文?但 13点是合理的(可能表示下午1点)
|
||||
("一点钟", dt(2025, 10, 9, 13, 0)),
|
||||
("晚上十二点", dt(2025, 10, 10, 0, 0)),
|
||||
],
|
||||
)
|
||||
def test_parse_hour_am_context(text, expected):
|
||||
"""在凌晨(02:34)上下文中解析时间"""
|
||||
NOW = dt(2025, 10, 9, 2, 34, 1, 114)
|
||||
parser = Parser(now=NOW)
|
||||
actual = parser.parse(text)
|
||||
expected_clean = expected.replace(microsecond=0)
|
||||
actual_clean = actual.replace(microsecond=0)
|
||||
assert actual_clean == expected_clean, (
|
||||
f"Failed on Text: '{text}'. "
|
||||
f"Context (now): {NOW}. "
|
||||
f"Expected: {expected_clean}. "
|
||||
f"Actual: {actual_clean}."
|
||||
)
|
||||
|
||||
|
||||
def test_error():
|
||||
parser = Parser(now=datetime.datetime(2025, 10, 9, 16, 34, 1, 114))
|
||||
|
||||
with pytest.raises(TokenUnhandledException):
|
||||
parser.parse("六点半整")
|
||||
|
||||
with pytest.raises(MultipleSpecificationException):
|
||||
parser.parse("六点半一刻")
|
||||
# --- 测试带分钟的时间 ---
|
||||
@pytest.mark.parametrize(
|
||||
"text, expected",
|
||||
[
|
||||
("六点半", dt(2025, 10, 9, 18, 30)),
|
||||
("六点半钟", dt(2025, 10, 9, 18, 30)),
|
||||
("六点一刻", dt(2025, 10, 9, 18, 15)),
|
||||
("六点过一刻", dt(2025, 10, 9, 18, 15)),
|
||||
],
|
||||
)
|
||||
def test_parse_hour_with_minute(text, expected):
|
||||
NOW = dt(2025, 10, 9, 16, 34, 1, 114)
|
||||
parser = Parser(now=NOW)
|
||||
actual = parser.parse(text)
|
||||
expected_clean = expected.replace(microsecond=0)
|
||||
actual_clean = actual.replace(microsecond=0)
|
||||
assert actual_clean == expected_clean, (
|
||||
f"Failed on Text: '{text}'. "
|
||||
f"Context (now): {NOW}. "
|
||||
f"Expected: {expected_clean}. "
|
||||
f"Actual: {actual_clean}."
|
||||
)
|
||||
|
||||
|
||||
def test_absolute_date():
|
||||
now = datetime.datetime(2025, 10, 9, 16, 34, 1, 114)
|
||||
parser = Parser(now=now)
|
||||
# 完整年月日
|
||||
assert parser.parse("2025年10月9日") == datetime.datetime(2025, 10, 9, 0, 0, 0, 0)
|
||||
assert parser.parse("2025-10-09") == datetime.datetime(2025, 10, 9, 0, 0, 0, 0)
|
||||
assert parser.parse("2025/10/09") == datetime.datetime(2025, 10, 9, 0, 0, 0, 0)
|
||||
# 仅月日(默认今年)
|
||||
assert parser.parse("10月9日") == datetime.datetime(2025, 10, 9, 0, 0, 0, 0)
|
||||
assert parser.parse("十月九日") == datetime.datetime(2025, 10, 9, 0, 0, 0, 0)
|
||||
# 年月日 + 时间
|
||||
assert parser.parse("2025年10月9日 15点") == datetime.datetime(2025, 10, 9, 15, 0, 0, 0)
|
||||
assert parser.parse("10月9日 下午3点") == datetime.datetime(2025, 10, 9, 15, 0, 0, 0)
|
||||
assert parser.parse("十月九日 晚上八点") == datetime.datetime(2025, 10, 9, 20, 0, 0, 0)
|
||||
# ISO 格式(如果支持)
|
||||
assert parser.parse("2025-10-09T15:30") == datetime.datetime(2025, 10, 9, 15, 30, 0, 0)
|
||||
# --- 错误处理测试 ---
|
||||
def test_parse_errors():
|
||||
NOW = dt(2025, 10, 9, 16, 34, 1, 114)
|
||||
parser = Parser(now=NOW)
|
||||
|
||||
|
||||
def test_absolute_time():
|
||||
now = datetime.datetime(2025, 10, 9, 16, 34, 1, 114)
|
||||
parser = Parser(now=now)
|
||||
assert parser.parse("5:30") == datetime.datetime(2025, 10, 9, 17, 30, 0, 0)
|
||||
assert parser.parse("5:11") == datetime.datetime(2025, 10, 9, 17, 11, 0, 0)
|
||||
assert parser.parse("5点30分") == datetime.datetime(2025, 10, 9, 17, 30, 0, 0)
|
||||
assert parser.parse("17:20") == datetime.datetime(2025, 10, 9, 17, 20, 0, 0)
|
||||
|
||||
|
||||
def test_relative_date():
|
||||
now = datetime.datetime(2025, 10, 9, 10, 0, 0)
|
||||
parser = Parser(now=now)
|
||||
assert parser.parse("明天") == datetime.datetime(2025, 10, 10, 0, 0, 0, 0)
|
||||
assert parser.parse("后天") == datetime.datetime(2025, 10, 11, 0, 0, 0, 0)
|
||||
assert parser.parse("昨天") == datetime.datetime(2025, 10, 8, 0, 0, 0, 0)
|
||||
assert parser.parse("大前天") == datetime.datetime(2025, 10, 6, 0, 0, 0, 0)
|
||||
assert parser.parse("大后天") == datetime.datetime(2025, 10, 12, 0, 0, 0, 0)
|
||||
|
||||
assert parser.parse("三天后") == datetime.datetime(2025, 10, 12, 0, 0, 0, 0)
|
||||
assert parser.parse("五天前") == datetime.datetime(2025, 10, 4, 0, 0, 0, 0)
|
||||
|
||||
assert parser.parse("下周一") == datetime.datetime(2025, 10, 13, 0, 0, 0, 0)
|
||||
assert parser.parse("上周五") == datetime.datetime(2025, 10, 3, 0, 0, 0, 0)
|
||||
assert parser.parse("本周五") == datetime.datetime(2025, 10, 10, 0, 0, 0, 0)
|
||||
|
||||
end_of_month = datetime.datetime(2025, 10, 31, 10, 0, 0)
|
||||
parser2 = Parser(now=end_of_month)
|
||||
assert parser2.parse("两天后") == datetime.datetime(2025, 11, 2, 0, 0, 0, 0)
|
||||
|
||||
|
||||
def test_relative_time():
|
||||
now = datetime.datetime(2025, 10, 9, 16, 30, 0, 0)
|
||||
parser = Parser(now=now)
|
||||
|
||||
assert parser.parse("五分钟后") == datetime.datetime(2025, 10, 9, 16, 35, 0, 0)
|
||||
assert parser.parse("十分钟前") == datetime.datetime(2025, 10, 9, 16, 20, 0, 0)
|
||||
assert parser.parse("半小时后") == datetime.datetime(2025, 10, 9, 17, 0, 0, 0)
|
||||
assert parser.parse("一个半小时后") == datetime.datetime(2025, 10, 9, 18, 0, 0, 0)
|
||||
|
||||
assert parser.parse("两小时后") == datetime.datetime(2025, 10, 9, 18, 30, 0, 0)
|
||||
assert parser.parse("一小时前") == datetime.datetime(2025, 10, 9, 15, 30, 0, 0)
|
||||
|
||||
late_night = datetime.datetime(2025, 10, 9, 23, 50, 0, 0)
|
||||
parser3 = Parser(now=late_night)
|
||||
assert parser3.parse("二十分钟后") == datetime.datetime(2025, 10, 10, 0, 10, 0, 0)
|
||||
|
||||
assert parser.parse("5分钟后") == datetime.datetime(2025, 10, 9, 16, 35, 0, 0)
|
||||
assert parser.parse("三十分钟前") == datetime.datetime(2025, 10, 9, 16, 0, 0, 0)
|
||||
|
||||
|
||||
|
||||
def test_robustness_edge_cases():
|
||||
parser = Parser(now=datetime.datetime(2025, 2, 28, 10, 0, 0))
|
||||
|
||||
assert parser.parse("明天") == datetime.datetime(2025, 3, 1, 0, 0, 0, 0)
|
||||
|
||||
parser_leap = Parser(now=datetime.datetime(2024, 2, 28, 10, 0, 0))
|
||||
assert parser_leap.parse("两天后") == datetime.datetime(2024, 3, 1, 0, 0, 0, 0)
|
||||
|
||||
with pytest.raises(TokenUnhandledException):
|
||||
# 完全无效输入
|
||||
with pytest.raises(TokenUnhandledException, match="随便乱写"):
|
||||
parser.parse("随便乱写")
|
||||
parser.parse(" 明天 ")
|
||||
|
||||
def test_mixed_expressions():
|
||||
now = datetime.datetime(2025, 10, 9, 14, 0, 0)
|
||||
# 但允许前后空格
|
||||
result = parser.parse(" 明天 ")
|
||||
# 使用 f-string 包含上下文信息
|
||||
expected_date = dt(2025, 10, 10).date()
|
||||
assert result.date() == expected_date, (
|
||||
f"Failed on Text: ' 明天 '. "
|
||||
f"Context (now): {NOW}. "
|
||||
f"Expected Date: {expected_date}. "
|
||||
f"Actual Date: {result.date()}."
|
||||
)
|
||||
|
||||
|
||||
# --- 绝对日期测试 ---
|
||||
@pytest.mark.parametrize(
|
||||
"text, expected",
|
||||
[
|
||||
("2025年10月9日", dt(2025, 10, 9, 0, 0)),
|
||||
("2025-10-09", dt(2025, 10, 9, 0, 0)),
|
||||
("2025/10/09", dt(2025, 10, 9, 0, 0)),
|
||||
("10月9日", dt(2025, 10, 9, 0, 0)),
|
||||
("十月九日", dt(2025, 10, 9, 0, 0)),
|
||||
("2025年10月9日 15点", dt(2025, 10, 9, 15, 0)),
|
||||
("10月9日 下午3点", dt(2025, 10, 9, 15, 0)),
|
||||
("十月九日 晚上八点", dt(2025, 10, 9, 20, 0)),
|
||||
("2025-10-09T15:30", dt(2025, 10, 9, 15, 30)),
|
||||
],
|
||||
)
|
||||
def test_parse_absolute_date(text, expected):
|
||||
NOW = dt(2025, 10, 9, 16, 34, 1, 114)
|
||||
parser = Parser(now=NOW)
|
||||
actual = parser.parse(text)
|
||||
expected_clean = expected.replace(microsecond=0)
|
||||
actual_clean = actual.replace(microsecond=0)
|
||||
assert actual_clean == expected_clean, (
|
||||
f"Failed on Text: '{text}'. "
|
||||
f"Context (now): {NOW}. "
|
||||
f"Expected: {expected_clean}. "
|
||||
f"Actual: {actual_clean}."
|
||||
)
|
||||
|
||||
|
||||
# --- 绝对时间(无日期)测试 ---
|
||||
@pytest.mark.parametrize(
|
||||
"text, expected",
|
||||
[
|
||||
("5:30", dt(2025, 10, 9, 17, 30)),
|
||||
("5:11", dt(2025, 10, 9, 17, 11)),
|
||||
("5点30分", dt(2025, 10, 9, 17, 30)),
|
||||
("17:20", dt(2025, 10, 9, 17, 20)),
|
||||
("六点零五", dt(2025, 10, 9, 18, 5, 0, 0)),
|
||||
],
|
||||
)
|
||||
def test_parse_absolute_time(text, expected):
|
||||
NOW = dt(2025, 10, 9, 16, 34, 1, 114)
|
||||
parser = Parser(now=NOW)
|
||||
actual = parser.parse(text)
|
||||
expected_clean = expected.replace(microsecond=0)
|
||||
actual_clean = actual.replace(microsecond=0)
|
||||
assert actual_clean == expected_clean, (
|
||||
f"Failed on Text: '{text}'. "
|
||||
f"Context (now): {NOW}. "
|
||||
f"Expected: {expected_clean}. "
|
||||
f"Actual: {actual_clean}."
|
||||
)
|
||||
|
||||
|
||||
# --- 相对日期测试 ---
|
||||
@pytest.mark.parametrize(
|
||||
"now, text, expected",
|
||||
[
|
||||
(dt(2025, 10, 9, 10, 0), "明天", dt(2025, 10, 10)),
|
||||
(dt(2025, 10, 9, 10, 0), "后天", dt(2025, 10, 11)),
|
||||
(dt(2025, 10, 9, 10, 0), "昨天", dt(2025, 10, 8)),
|
||||
(dt(2025, 10, 9, 10, 0), "大前天", dt(2025, 10, 6)),
|
||||
(dt(2025, 10, 9, 10, 0), "大后天", dt(2025, 10, 12)),
|
||||
(dt(2025, 10, 9, 10, 0), "三天后", dt(2025, 10, 12)),
|
||||
(dt(2025, 10, 9, 10, 0), "五天前", dt(2025, 10, 4)),
|
||||
(dt(2025, 10, 9, 10, 0), "下周一", dt(2025, 10, 13)), # 10-9 是周四
|
||||
(dt(2025, 10, 9, 10, 0), "上周五", dt(2025, 10, 3)),
|
||||
(dt(2025, 10, 9, 10, 0), "本周五", dt(2025, 10, 10)),
|
||||
(dt(2025, 10, 31, 10, 0), "两天后", dt(2025, 11, 2)),
|
||||
],
|
||||
)
|
||||
def test_parse_relative_date(now, text, expected):
|
||||
parser = Parser(now=now)
|
||||
# 如“明天下午三点”
|
||||
assert parser.parse("明天下午三点") == datetime.datetime(2025, 10, 10, 15, 0, 0, 0)
|
||||
assert parser.parse("后天早上八点") == datetime.datetime(2025, 10, 11, 8, 0, 0, 0)
|
||||
assert parser.parse("大后天晚上十点") == datetime.datetime(2025, 10, 12, 22, 0, 0, 0)
|
||||
# “下周三上午”
|
||||
# 2025-10-09 是周四,下周三是 2025-10-15
|
||||
assert parser.parse("下周三") == datetime.datetime(2025, 10, 15, 0, 0, 0, 0)
|
||||
assert parser.parse("下周三早八") == datetime.datetime(2025, 10, 15, 8, 0, 0, 0) # 默认0点?或上午9点?需根据实现
|
||||
# 若实现中“上午”不指定小时则设为9点,可调整;这里假设设为0点以简化
|
||||
actual = parser.parse(text)
|
||||
expected_clean = expected.replace(microsecond=0)
|
||||
actual_clean = actual.replace(microsecond=0)
|
||||
assert actual_clean == expected_clean, (
|
||||
f"Failed on Text: '{text}'. "
|
||||
f"Context (now): {now}. "
|
||||
f"Expected: {expected_clean}. "
|
||||
f"Actual: {actual_clean}."
|
||||
)
|
||||
|
||||
|
||||
# --- 相对时间(分钟/小时)测试 ---
|
||||
@pytest.mark.parametrize(
|
||||
"now, text, expected",
|
||||
[
|
||||
(dt(2025, 10, 9, 16, 30), "五分钟后", dt(2025, 10, 9, 16, 35)),
|
||||
(dt(2025, 10, 9, 16, 30), "十分钟前", dt(2025, 10, 9, 16, 20)),
|
||||
(dt(2025, 10, 9, 16, 30), "半小时后", dt(2025, 10, 9, 17, 0)),
|
||||
(dt(2025, 10, 9, 16, 30), "一个半小时后", dt(2025, 10, 9, 18, 0)),
|
||||
(dt(2025, 10, 9, 16, 30), "两个半小时后", dt(2025, 10, 9, 19, 0)),
|
||||
(dt(2025, 10, 9, 16, 30), "两小时后", dt(2025, 10, 9, 18, 30)),
|
||||
(dt(2025, 10, 9, 16, 30), "一小时前", dt(2025, 10, 9, 15, 30)),
|
||||
(dt(2025, 10, 9, 23, 50), "二十分钟后", dt(2025, 10, 10, 0, 10)),
|
||||
(dt(2025, 10, 9, 16, 30), "5分钟后", dt(2025, 10, 9, 16, 35)),
|
||||
(dt(2025, 10, 9, 16, 30), "三十分钟前", dt(2025, 10, 9, 16, 0)),
|
||||
(dt(2025, 10, 9, 16, 30, 0, 0), "两秒后", dt(2025, 10, 9, 16, 30, 2, 0)),
|
||||
# 同义词支持
|
||||
(dt(2025, 10, 19, 20, 16), "一小时后", dt(2025, 10, 19, 21, 16)),
|
||||
(dt(2025, 10, 19, 20, 16), "一小时以后", dt(2025, 10, 19, 21, 16)),
|
||||
(dt(2025, 10, 19, 20, 16), "一小时之后", dt(2025, 10, 19, 21, 16)),
|
||||
(dt(2025, 10, 19, 20, 16), "一个小时以后", dt(2025, 10, 19, 21, 16)),
|
||||
],
|
||||
)
|
||||
def test_parse_relative_time(now, text, expected):
|
||||
parser = Parser(now=now)
|
||||
actual = parser.parse(text)
|
||||
expected_clean = expected.replace(microsecond=0)
|
||||
actual_clean = actual.replace(microsecond=0)
|
||||
assert actual_clean == expected_clean, (
|
||||
f"Failed on Text: '{text}'. "
|
||||
f"Context (now): {now}. "
|
||||
f"Expected: {expected_clean}. "
|
||||
f"Actual: {actual_clean}."
|
||||
)
|
||||
|
||||
|
||||
# --- 混合表达式(日期 + 时间)---
|
||||
@pytest.mark.parametrize(
|
||||
"now, text, expected",
|
||||
[
|
||||
(dt(2025, 10, 9, 14, 0), "明天下午三点", dt(2025, 10, 10, 15, 0)),
|
||||
(dt(2025, 10, 9, 14, 0), "后天早上八点", dt(2025, 10, 11, 8, 0)),
|
||||
(dt(2025, 10, 9, 14, 0), "大后天晚上十点", dt(2025, 10, 12, 22, 0)),
|
||||
(dt(2025, 10, 9, 14, 0), "下周三", dt(2025, 10, 15, 0, 0)), # 10-9 周四 → 下周三 10-15
|
||||
(dt(2025, 10, 9, 14, 0), "下周三早八", dt(2025, 10, 15, 8, 0)),
|
||||
(dt(2025, 10, 19, 20, 16), "八点二十", dt(2025, 10, 19, 20, 20)),
|
||||
(dt(2025, 10, 19, 20, 16), "明天八点二十", dt(2025, 10, 20, 8, 20)),
|
||||
(dt(2025, 10, 19, 10, 10), "今晚八点", dt(2025, 10, 19, 20, 0)),
|
||||
(dt(2025, 10, 19, 10, 10), "今天早上六点", dt(2025, 10, 19, 6, 0)),
|
||||
(dt(2025, 10, 19, 10, 10), "今早七点五十分", dt(2025, 10, 19, 7, 50)),
|
||||
],
|
||||
)
|
||||
def test_parse_mixed_expressions(now, text, expected):
|
||||
parser = Parser(now=now)
|
||||
actual = parser.parse(text)
|
||||
expected_clean = expected.replace(microsecond=0)
|
||||
actual_clean = actual.replace(microsecond=0)
|
||||
assert actual_clean == expected_clean, (
|
||||
f"Failed on Text: '{text}'. "
|
||||
f"Context (now): {now}. "
|
||||
f"Expected: {expected_clean}. "
|
||||
f"Actual: {actual_clean}."
|
||||
)
|
||||
|
||||
|
||||
# --- 边界情况与鲁棒性 ---
|
||||
def test_robustness_edge_cases():
|
||||
# 闰年 & 月末
|
||||
parser_feb = Parser(now=dt(2025, 2, 28, 10, 0))
|
||||
expected_march = dt(2025, 3, 1, 0, 0)
|
||||
actual_march = parser_feb.parse("明天")
|
||||
assert actual_march == expected_march, (
|
||||
f"Failed on '明天' (now=2025-02-28). "
|
||||
f"Expected: {expected_march}. "
|
||||
f"Actual: {actual_march}."
|
||||
)
|
||||
|
||||
parser_leap = Parser(now=dt(2024, 2, 28, 10, 0))
|
||||
expected_leap = dt(2024, 3, 1, 0, 0)
|
||||
actual_leap = parser_leap.parse("两天后")
|
||||
assert actual_leap == expected_leap, (
|
||||
f"Failed on '两天后' (now=2024-02-28 - Leap Year). "
|
||||
f"Expected: {expected_leap}. "
|
||||
f"Actual: {actual_leap}."
|
||||
)
|
||||
|
||||
# 空格容忍
|
||||
NOW = dt(2025, 10, 9, 10, 0)
|
||||
parser = Parser(now=NOW)
|
||||
result = parser.parse(" 明天 ")
|
||||
expected_date = dt(2025, 10, 10).date()
|
||||
assert result.date() == expected_date, (
|
||||
f"Failed on Text: ' 明天 ' (Whitespace). "
|
||||
f"Context (now): {NOW}. "
|
||||
f"Expected Date: {expected_date}. "
|
||||
f"Actual Date: {result.date()}."
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user