多维度日期处理
This commit is contained in:
@@ -178,6 +178,13 @@ def gregorian_to_ganzhi(year: int) -> str:
|
|||||||
|
|
||||||
_gz_pair = '[' + _tiangan + '][' + _dizhi + ']'
|
_gz_pair = '[' + _tiangan + '][' + _dizhi + ']'
|
||||||
|
|
||||||
|
# 干支纪年中的年号组(已知年号按长度降序;单字年号加前瞻(?![地支]),
|
||||||
|
# 防止单字年号"丙"吞掉天干"丙午"中的"丙")
|
||||||
|
_gz_era_group = r'(?P<era>' + '|'.join(
|
||||||
|
(re.escape(e) + (r'(?![子丑寅卯辰巳午未申酉戌亥])' if len(e) == 1 else ''))
|
||||||
|
for e in sorted(KNOWN_ERAS_ALL, key=len, reverse=True)
|
||||||
|
) + r')'
|
||||||
|
|
||||||
# === 数字转中文 ===
|
# === 数字转中文 ===
|
||||||
def arabic_to_chinese_year(num: int) -> str:
|
def arabic_to_chinese_year(num: int) -> str:
|
||||||
digits = "〇一二三四五六七八九"
|
digits = "〇一二三四五六七八九"
|
||||||
@@ -249,6 +256,73 @@ _patterns_no_dynasty = _build_pattern(with_dynasty=False)
|
|||||||
_patterns_dynasty = _build_pattern(with_dynasty=True)
|
_patterns_dynasty = _build_pattern(with_dynasty=True)
|
||||||
|
|
||||||
|
|
||||||
|
# === 农历月日(干支纪年后的"十一月十四""闰六月廿一日") ===
|
||||||
|
_month_special = {"正": 1, "冬": 11, "腊": 12}
|
||||||
|
|
||||||
|
def _parse_month(text):
|
||||||
|
"""月文本转数字:正月=1、冬月=11、腊月=12,其余走中文数字(十一月→11)"""
|
||||||
|
return _month_special.get(text, chinese_to_num(text))
|
||||||
|
|
||||||
|
def _parse_day(text):
|
||||||
|
"""日文本转数字:剥"初"前缀(初八→8、初十→10),廿三→23"""
|
||||||
|
if text.startswith("初"):
|
||||||
|
text = text[1:]
|
||||||
|
return chinese_to_num(text)
|
||||||
|
|
||||||
|
_month_day_re = re.compile(
|
||||||
|
r'(?P<leap>闰)?(?P<month>[正冬腊〇零一二三四五六七八九十廿卄卅卌]{1,3})月'
|
||||||
|
r'(?:(?P<day>[初〇零一二三四五六七八九十廿卄卅卌]{1,3})(?:日)?)?'
|
||||||
|
)
|
||||||
|
# 嵌入干支正则的可选月日片段(整体捕获,事后用 _month_day_re 解析)
|
||||||
|
_md_body = r'(?:闰)?[正冬腊〇零一二三四五六七八九十廿卄卅卌]{1,3}月' \
|
||||||
|
r'(?:[初〇零一二三四五六七八九十廿卄卅卌]{1,3}(?:日)?)?'
|
||||||
|
# 族谱常见的生死动词:月日与(公元对照)之间可能隔着"生/殁"等单字
|
||||||
|
_md_verb = r'[生死殁卒薨殓]'
|
||||||
|
|
||||||
|
def _attach_month_day(result, m):
|
||||||
|
"""把匹配中的月日组(md1/md2/md,括号前/后位置)并入结果字段"""
|
||||||
|
md = None
|
||||||
|
for name in ("md1", "md2", "md"):
|
||||||
|
if name in m.groupdict() and m.group(name):
|
||||||
|
md = m.group(name)
|
||||||
|
break
|
||||||
|
if not md:
|
||||||
|
return
|
||||||
|
mm = _month_day_re.match(md)
|
||||||
|
if not mm:
|
||||||
|
return
|
||||||
|
result["月"] = _parse_month(mm.group("month"))
|
||||||
|
if mm.group("day"):
|
||||||
|
result["日"] = _parse_day(mm.group("day"))
|
||||||
|
result["月日"] = md
|
||||||
|
|
||||||
|
|
||||||
|
# === 2c. 干支+(公元对照)(如"清同治壬申(1872)十一月十四生") ===
|
||||||
|
# 干支纪年旁直接给出公元年份:合并为一条结果、对照校验干支、紧跟的月日并入字段。
|
||||||
|
# 置于公元段之前(见 extract_era_years),避免"(1872)"被单独摘出成公元纪年。
|
||||||
|
_dynasty_opt = (r'(?P<dynasty>(?:(?<![一-鿿])[' + _dynasty_prefix + r'](?:' + _dynasty_alt + r'))'
|
||||||
|
r'|(?:(?<![一-鿿])' + _dynasty_alt + r'))?')
|
||||||
|
# 括号中的公元年份:阿拉伯/全角/中文数字 3-4 位,可带"年"字
|
||||||
|
_gz_gy_paren = r'[((]\s*(?P<gy_year>[〇零○O一二三四五六七八九\d0-9]{3,4})\s*年?[))]'
|
||||||
|
_ganzhi_gongyuan_re = re.compile(
|
||||||
|
_dynasty_opt
|
||||||
|
+ _gz_era_group
|
||||||
|
+ r'(?P<ganzhi>' + _gz_pair + r')(?:年)?'
|
||||||
|
+ r'(?P<md1>' + _md_body + r')?'
|
||||||
|
+ r'(?:' + _md_verb + r')?'
|
||||||
|
+ _gz_gy_paren
|
||||||
|
+ r'(?P<md2>' + _md_body + r')?'
|
||||||
|
)
|
||||||
|
# 裸干支+(对照):无年号可锚定(如"壬申(1872)"),公元取对照值
|
||||||
|
_ganzhi_bare_gongyuan_re = re.compile(
|
||||||
|
r'(?P<ganzhi>' + _gz_pair + r')(?:年)?'
|
||||||
|
+ r'(?P<md1>' + _md_body + r')?'
|
||||||
|
+ r'(?:' + _md_verb + r')?'
|
||||||
|
+ _gz_gy_paren
|
||||||
|
+ r'(?P<md2>' + _md_body + r')?'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# === 主函数 ===
|
# === 主函数 ===
|
||||||
def extract_era_years(text):
|
def extract_era_years(text):
|
||||||
# 标注符号([]、【】)不参与匹配,但保留在文本中——位置偏移以原始文本为准
|
# 标注符号([]、【】)不参与匹配,但保留在文本中——位置偏移以原始文本为准
|
||||||
@@ -378,6 +452,74 @@ def extract_era_years(text):
|
|||||||
result["干支不符"] = True
|
result["干支不符"] = True
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
|
# === 2c. 干支+(公元对照)(如"清同治壬申(1872)十一月十四生") ===
|
||||||
|
# 干支纪年旁直接给出公元对照:合并为一条结果,用对照校验干支,紧跟的月日并入字段。
|
||||||
|
# 置于公元段之前,避免"(1872)"被单独摘出成公元纪年(同 2b 的合并优先策略)。
|
||||||
|
def _process_ganzhi_gongyuan(m):
|
||||||
|
raw_dynasty = m.group("dynasty") if "dynasty" in m.groupdict() else ""
|
||||||
|
dynasty = normalize_dynasty_name(raw_dynasty or "")
|
||||||
|
era = cc.convert(m.group("era")) if "era" in m.groupdict() and m.group("era") else None
|
||||||
|
ganzhi = m.group("ganzhi")
|
||||||
|
gy_year = chinese_to_num(m.group("gy_year"))
|
||||||
|
if ganzhi not in _ganzhi_to_offset or not (1000 <= gy_year <= 2100):
|
||||||
|
return
|
||||||
|
start, end = m.start(), m.end()
|
||||||
|
if any(pos in covered for pos in range(start, end)):
|
||||||
|
return
|
||||||
|
# 查找年号基准年(与干支纪年段同逻辑:先精确朝代,后全量候选取首个并标注歧义)
|
||||||
|
base = None
|
||||||
|
multi = []
|
||||||
|
if dynasty and era:
|
||||||
|
for d, eras in era_dict.items():
|
||||||
|
if dynasty in d or d in dynasty:
|
||||||
|
if era in eras:
|
||||||
|
base = eras[era]
|
||||||
|
dynasty = d
|
||||||
|
break
|
||||||
|
if base is None and era:
|
||||||
|
for d, eras in era_dict.items():
|
||||||
|
if era in eras:
|
||||||
|
multi.append((d, eras[era]))
|
||||||
|
if multi:
|
||||||
|
dynasty, base = multi[0]
|
||||||
|
covered.update(range(start, end))
|
||||||
|
result = {
|
||||||
|
"原文": original_text[start:end],
|
||||||
|
"原文(简体)": m.group(0),
|
||||||
|
"干支": ganzhi,
|
||||||
|
"公元": gy_year,
|
||||||
|
"公元中文": f"{arabic_to_chinese_year(gy_year)}年",
|
||||||
|
"位置": {"起始": start, "结束": end},
|
||||||
|
"类型": "干支纪年",
|
||||||
|
}
|
||||||
|
if base is not None:
|
||||||
|
result["朝代"] = dynasty
|
||||||
|
result["年号"] = era
|
||||||
|
# 干支60年循环推算(同干支纪年段公式);与对照不一致时标注,公元仍取对照值
|
||||||
|
base_gz = (base - 4) % 60
|
||||||
|
year_in_era = ((_ganzhi_to_offset[ganzhi] - base_gz) % 60) + 1
|
||||||
|
result["年数"] = year_in_era
|
||||||
|
computed = base + year_in_era - 1
|
||||||
|
if computed != gy_year:
|
||||||
|
result["干支不符"] = True
|
||||||
|
result["干支推算"] = computed
|
||||||
|
if len(multi) > 1:
|
||||||
|
result["多候选"] = True
|
||||||
|
result["候选"] = [list(c) for c in multi]
|
||||||
|
span = era_span.get(dynasty, {}).get(era)
|
||||||
|
if span is not None and gy_year > base + span - 1:
|
||||||
|
result["超出使用期"] = True
|
||||||
|
elif gregorian_to_ganzhi(gy_year) != ganzhi:
|
||||||
|
# 裸干支+对照:无年号可锚定,直接用公元年份的干支校验(同公元段约定)
|
||||||
|
result["干支不符"] = True
|
||||||
|
_attach_month_day(result, m)
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
for m in _ganzhi_gongyuan_re.finditer(simplified_text):
|
||||||
|
_process_ganzhi_gongyuan(m)
|
||||||
|
for m in _ganzhi_bare_gongyuan_re.finditer(simplified_text):
|
||||||
|
_process_ganzhi_gongyuan(m)
|
||||||
|
|
||||||
# === 2. 公元(允许无"年"字)===
|
# === 2. 公元(允许无"年"字)===
|
||||||
gongyuan_pattern = re.compile(
|
gongyuan_pattern = re.compile(
|
||||||
r'(公元)?\s*([〇零○O一二三四五六七八九\d0-9]{3,4})\s*(?:(.*?))?\s*(年)?'
|
r'(公元)?\s*([〇零○O一二三四五六七八九\d0-9]{3,4})\s*(?:(.*?))?\s*(年)?'
|
||||||
@@ -480,18 +622,16 @@ def extract_era_years(text):
|
|||||||
span = era_span.get(dynasty, {}).get(era)
|
span = era_span.get(dynasty, {}).get(era)
|
||||||
if span is not None and gregorian > base + span - 1:
|
if span is not None and gregorian > base + span - 1:
|
||||||
result["超出使用期"] = True
|
result["超出使用期"] = True
|
||||||
|
_attach_month_day(result, m)
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
# 模式A:有朝代前缀(如"清雍正乙巳")
|
# 模式A:有朝代前缀(如"清雍正乙巳");年号组 _gz_era_group 见模块级定义
|
||||||
# 单字年号加前瞻(?![地支]),防止单字年号"丙"吞掉天干"丙午"中的"丙"
|
|
||||||
_gz_era_group = r'(?P<era>' + '|'.join(
|
|
||||||
(re.escape(e) + (r'(?![子丑寅卯辰巳午未申酉戌亥])' if len(e) == 1 else ''))
|
|
||||||
for e in sorted(KNOWN_ERAS_ALL, key=len, reverse=True)
|
|
||||||
) + r')'
|
|
||||||
ganzhi_dynasty = re.compile(
|
ganzhi_dynasty = re.compile(
|
||||||
r'(?P<dynasty>(?:(?<![一-鿿])[' + _dynasty_prefix + r'](?:' + _dynasty_alt + r'))|(?:(?<![一-鿿])' + _dynasty_alt + r'))'
|
r'(?P<dynasty>(?:(?<![一-鿿])[' + _dynasty_prefix + r'](?:' + _dynasty_alt + r'))|(?:(?<![一-鿿])' + _dynasty_alt + r'))'
|
||||||
+ _gz_era_group
|
+ _gz_era_group
|
||||||
+ r'(?P<ganzhi>' + _gz_pair + r')(?:年)?'
|
+ r'(?P<ganzhi>' + _gz_pair + r')(?:年)?'
|
||||||
|
# 紧跟的农历月日并入同一单位(如"清雍正乙巳十一月十二午时生")
|
||||||
|
+ r'(?P<md>' + _md_body + r')?'
|
||||||
)
|
)
|
||||||
for m in ganzhi_dynasty.finditer(simplified_text):
|
for m in ganzhi_dynasty.finditer(simplified_text):
|
||||||
_process_ganzhi_match(m)
|
_process_ganzhi_match(m)
|
||||||
@@ -503,6 +643,7 @@ def extract_era_years(text):
|
|||||||
r'(?P<dynasty>)'
|
r'(?P<dynasty>)'
|
||||||
+ r'(?P<era>' + _long_era_alt + r')'
|
+ r'(?P<era>' + _long_era_alt + r')'
|
||||||
+ r'(?P<ganzhi>' + _gz_pair + r')(?:年)?'
|
+ r'(?P<ganzhi>' + _gz_pair + r')(?:年)?'
|
||||||
|
+ r'(?P<md>' + _md_body + r')?'
|
||||||
)
|
)
|
||||||
for m in ganzhi_no_dynasty.finditer(simplified_text):
|
for m in ganzhi_no_dynasty.finditer(simplified_text):
|
||||||
_process_ganzhi_match(m)
|
_process_ganzhi_match(m)
|
||||||
|
|||||||
Reference in New Issue
Block a user