Browse Source

add NLP time package

highing666 4 năm trước cách đây
mục cha
commit
dd27a2dcfa

+ 0 - 0
app/utils/nlp_time/__init__.py


+ 10 - 0
app/utils/nlp_time/enums.py

@@ -0,0 +1,10 @@
+# 范围时间的默认时间点
+class RangeTimeEnum:
+    day_break = 3  # 黎明
+    early_morning = 8  # 早
+    morning = 10  # 上午
+    noon = 12  # 中午、午间
+    afternoon = 15  # 下午、午后
+    night = 18  # 晚上、傍晚
+    lateNight = 20  # 晚、晚间
+    midNight = 23  # 深夜

+ 0 - 0
app/utils/nlp_time/helpers/__init__.py


+ 10 - 0
app/utils/nlp_time/helpers/arrow_helper.py

@@ -0,0 +1,10 @@
+from typing import List
+from arrow import Arrow, get
+
+
+def arrow2grid(arrow_ins: Arrow) -> List[str]:
+    return arrow_ins.format("YYYY-M-D-H-m-s").split("-")
+
+
+def grid2arrow(str_list: List[str]) -> Arrow:
+    return get("-".join(str_list), "YYYY-M-D-H-m-s")

+ 0 - 0
app/utils/nlp_time/helpers/int_common.py


+ 598 - 0
app/utils/nlp_time/helpers/lunar_solar_converter.py

@@ -0,0 +1,598 @@
+class Lunar:
+    def __init__(self, lunarYear, lunarMonth, lunarDay, isleap):
+        self.isleap = isleap
+        self.lunarDay = lunarDay
+        self.lunarMonth = lunarMonth
+        self.lunarYear = lunarYear
+
+
+class Solar:
+    def __init__(self, solarYear, solarMonth, solarDay):
+        self.solarDay = solarDay
+        self.solarMonth = solarMonth
+        self.solarYear = solarYear
+
+
+def GetBitInt(data, length, shift):
+    return (data & (((1 << length) - 1) << shift)) >> shift
+
+
+def SolarToInt(y, m, d):
+    m = (m + 9) % 12
+    y -= int(m / 10)
+    return (
+            365 * y
+            + int(y / 4)
+            - int(y / 100)
+            + int(y / 400)
+            + int((m * 306 + 5) / 10)
+            + (d - 1)
+    )
+
+
+def SolarFromInt(g):
+    y = int((10000 * g + 14780) / 3652425)
+    ddd = g - (365 * y + int(y / 4) - int(y / 100) + int(y / 400))
+    if ddd < 0:
+        y -= 1
+        ddd = g - (365 * y + int(y / 4) - int(y / 100) + int(y / 400))
+
+    mi = int((100 * ddd + 52) / 3060)
+    mm = (mi + 2) % 12 + 1
+    y += int((mi + 2) / 12)
+    dd = ddd - int((mi * 306 + 5) / 10) + 1
+    solar = Solar(y, mm, dd)
+    return solar
+
+
+class LunarSolarConverter:
+    #####################################################################################
+    # 1888~2111年农历数据表
+    # 农历数据 每个元素的存储格式如下:
+    #   16~13    12          11~0
+    #  闰几月 闰月日数  1~12月份农历日数(大小月)
+    # 注:1、bit0表示农历1月份日数,为1表示30天,为0表示29天。bit1表示农历2月份日数,依次类推。
+    #     2、bit12表示闰月日数,1为30天,0为29天。bit16~bit13表示第几月是闰月(注:为0表示该年无闰月)
+    # 数据来源参考: http://data.weather.gov.hk/gts/time/conversion1_text_c.htm
+    #####################################################################################
+    lunar_month_days = [
+        1887,
+        0x1694,
+        0x16AA,
+        0x4AD5,
+        0xAB6,
+        0xC4B7,
+        0x4AE,
+        0xA56,
+        0xB52A,
+        0x1D2A,
+        0xD54,
+        0x75AA,
+        0x156A,
+        0x1096D,
+        0x95C,
+        0x14AE,
+        0xAA4D,
+        0x1A4C,
+        0x1B2A,
+        0x8D55,
+        0xAD4,
+        0x135A,
+        0x495D,
+        0x95C,
+        0xD49B,
+        0x149A,
+        0x1A4A,
+        0xBAA5,
+        0x16A8,
+        0x1AD4,
+        0x52DA,
+        0x12B6,
+        0xE937,
+        0x92E,
+        0x1496,
+        0xB64B,
+        0xD4A,
+        0xDA8,
+        0x95B5,
+        0x56C,
+        0x12AE,
+        0x492F,
+        0x92E,
+        0xCC96,
+        0x1A94,
+        0x1D4A,
+        0xADA9,
+        0xB5A,
+        0x56C,
+        0x726E,
+        0x125C,
+        0xF92D,
+        0x192A,
+        0x1A94,
+        0xDB4A,
+        0x16AA,
+        0xAD4,
+        0x955B,
+        0x4BA,
+        0x125A,
+        0x592B,
+        0x152A,
+        0xF695,
+        0xD94,
+        0x16AA,
+        0xAAB5,
+        0x9B4,
+        0x14B6,
+        0x6A57,
+        0xA56,
+        0x1152A,
+        0x1D2A,
+        0xD54,
+        0xD5AA,
+        0x156A,
+        0x96C,
+        0x94AE,
+        0x14AE,
+        0xA4C,
+        0x7D26,
+        0x1B2A,
+        0xEB55,
+        0xAD4,
+        0x12DA,
+        0xA95D,
+        0x95A,
+        0x149A,
+        0x9A4D,
+        0x1A4A,
+        0x11AA5,
+        0x16A8,
+        0x16D4,
+        0xD2DA,
+        0x12B6,
+        0x936,
+        0x9497,
+        0x1496,
+        0x1564B,
+        0xD4A,
+        0xDA8,
+        0xD5B4,
+        0x156C,
+        0x12AE,
+        0xA92F,
+        0x92E,
+        0xC96,
+        0x6D4A,
+        0x1D4A,
+        0x10D65,
+        0xB58,
+        0x156C,
+        0xB26D,
+        0x125C,
+        0x192C,
+        0x9A95,
+        0x1A94,
+        0x1B4A,
+        0x4B55,
+        0xAD4,
+        0xF55B,
+        0x4BA,
+        0x125A,
+        0xB92B,
+        0x152A,
+        0x1694,
+        0x96AA,
+        0x15AA,
+        0x12AB5,
+        0x974,
+        0x14B6,
+        0xCA57,
+        0xA56,
+        0x1526,
+        0x8E95,
+        0xD54,
+        0x15AA,
+        0x49B5,
+        0x96C,
+        0xD4AE,
+        0x149C,
+        0x1A4C,
+        0xBD26,
+        0x1AA6,
+        0xB54,
+        0x6D6A,
+        0x12DA,
+        0x1695D,
+        0x95A,
+        0x149A,
+        0xDA4B,
+        0x1A4A,
+        0x1AA4,
+        0xBB54,
+        0x16B4,
+        0xADA,
+        0x495B,
+        0x936,
+        0xF497,
+        0x1496,
+        0x154A,
+        0xB6A5,
+        0xDA4,
+        0x15B4,
+        0x6AB6,
+        0x126E,
+        0x1092F,
+        0x92E,
+        0xC96,
+        0xCD4A,
+        0x1D4A,
+        0xD64,
+        0x956C,
+        0x155C,
+        0x125C,
+        0x792E,
+        0x192C,
+        0xFA95,
+        0x1A94,
+        0x1B4A,
+        0xAB55,
+        0xAD4,
+        0x14DA,
+        0x8A5D,
+        0xA5A,
+        0x1152B,
+        0x152A,
+        0x1694,
+        0xD6AA,
+        0x15AA,
+        0xAB4,
+        0x94BA,
+        0x14B6,
+        0xA56,
+        0x7527,
+        0xD26,
+        0xEE53,
+        0xD54,
+        0x15AA,
+        0xA9B5,
+        0x96C,
+        0x14AE,
+        0x8A4E,
+        0x1A4C,
+        0x11D26,
+        0x1AA4,
+        0x1B54,
+        0xCD6A,
+        0xADA,
+        0x95C,
+        0x949D,
+        0x149A,
+        0x1A2A,
+        0x5B25,
+        0x1AA4,
+        0xFB52,
+        0x16B4,
+        0xABA,
+        0xA95B,
+        0x936,
+        0x1496,
+        0x9A4B,
+        0x154A,
+        0x136A5,
+        0xDA4,
+        0x15AC,
+    ]
+    # 额外添加数据,方便快速计算阴历转阳历 每个元素的存储格式如下:
+    #    12~7         6~5    4~0
+    #  离元旦多少天  春节月  春节日
+    #####################################################################################
+    solar_1_1 = [
+        1887,
+        0xEC04C,
+        0xEC23F,
+        0xEC435,
+        0xEC649,
+        0xEC83E,
+        0xECA51,
+        0xECC46,
+        0xECE3A,
+        0xED04D,
+        0xED242,
+        0xED436,
+        0xED64A,
+        0xED83F,
+        0xEDA53,
+        0xEDC48,
+        0xEDE3D,
+        0xEE050,
+        0xEE244,
+        0xEE439,
+        0xEE64D,
+        0xEE842,
+        0xEEA36,
+        0xEEC4A,
+        0xEEE3E,
+        0xEF052,
+        0xEF246,
+        0xEF43A,
+        0xEF64E,
+        0xEF843,
+        0xEFA37,
+        0xEFC4B,
+        0xEFE41,
+        0xF0054,
+        0xF0248,
+        0xF043C,
+        0xF0650,
+        0xF0845,
+        0xF0A38,
+        0xF0C4D,
+        0xF0E42,
+        0xF1037,
+        0xF124A,
+        0xF143E,
+        0xF1651,
+        0xF1846,
+        0xF1A3A,
+        0xF1C4E,
+        0xF1E44,
+        0xF2038,
+        0xF224B,
+        0xF243F,
+        0xF2653,
+        0xF2848,
+        0xF2A3B,
+        0xF2C4F,
+        0xF2E45,
+        0xF3039,
+        0xF324D,
+        0xF3442,
+        0xF3636,
+        0xF384A,
+        0xF3A3D,
+        0xF3C51,
+        0xF3E46,
+        0xF403B,
+        0xF424E,
+        0xF4443,
+        0xF4638,
+        0xF484C,
+        0xF4A3F,
+        0xF4C52,
+        0xF4E48,
+        0xF503C,
+        0xF524F,
+        0xF5445,
+        0xF5639,
+        0xF584D,
+        0xF5A42,
+        0xF5C35,
+        0xF5E49,
+        0xF603E,
+        0xF6251,
+        0xF6446,
+        0xF663B,
+        0xF684F,
+        0xF6A43,
+        0xF6C37,
+        0xF6E4B,
+        0xF703F,
+        0xF7252,
+        0xF7447,
+        0xF763C,
+        0xF7850,
+        0xF7A45,
+        0xF7C39,
+        0xF7E4D,
+        0xF8042,
+        0xF8254,
+        0xF8449,
+        0xF863D,
+        0xF8851,
+        0xF8A46,
+        0xF8C3B,
+        0xF8E4F,
+        0xF9044,
+        0xF9237,
+        0xF944A,
+        0xF963F,
+        0xF9853,
+        0xF9A47,
+        0xF9C3C,
+        0xF9E50,
+        0xFA045,
+        0xFA238,
+        0xFA44C,
+        0xFA641,
+        0xFA836,
+        0xFAA49,
+        0xFAC3D,
+        0xFAE52,
+        0xFB047,
+        0xFB23A,
+        0xFB44E,
+        0xFB643,
+        0xFB837,
+        0xFBA4A,
+        0xFBC3F,
+        0xFBE53,
+        0xFC048,
+        0xFC23C,
+        0xFC450,
+        0xFC645,
+        0xFC839,
+        0xFCA4C,
+        0xFCC41,
+        0xFCE36,
+        0xFD04A,
+        0xFD23D,
+        0xFD451,
+        0xFD646,
+        0xFD83A,
+        0xFDA4D,
+        0xFDC43,
+        0xFDE37,
+        0xFE04B,
+        0xFE23F,
+        0xFE453,
+        0xFE648,
+        0xFE83C,
+        0xFEA4F,
+        0xFEC44,
+        0xFEE38,
+        0xFF04C,
+        0xFF241,
+        0xFF436,
+        0xFF64A,
+        0xFF83E,
+        0xFFA51,
+        0xFFC46,
+        0xFFE3A,
+        0x10004E,
+        0x100242,
+        0x100437,
+        0x10064B,
+        0x100841,
+        0x100A53,
+        0x100C48,
+        0x100E3C,
+        0x10104F,
+        0x101244,
+        0x101438,
+        0x10164C,
+        0x101842,
+        0x101A35,
+        0x101C49,
+        0x101E3D,
+        0x102051,
+        0x102245,
+        0x10243A,
+        0x10264E,
+        0x102843,
+        0x102A37,
+        0x102C4B,
+        0x102E3F,
+        0x103053,
+        0x103247,
+        0x10343B,
+        0x10364F,
+        0x103845,
+        0x103A38,
+        0x103C4C,
+        0x103E42,
+        0x104036,
+        0x104249,
+        0x10443D,
+        0x104651,
+        0x104846,
+        0x104A3A,
+        0x104C4E,
+        0x104E43,
+        0x105038,
+        0x10524A,
+        0x10543E,
+        0x105652,
+        0x105847,
+        0x105A3B,
+        0x105C4F,
+        0x105E45,
+        0x106039,
+        0x10624C,
+        0x106441,
+        0x106635,
+        0x106849,
+        0x106A3D,
+        0x106C51,
+        0x106E47,
+        0x10703C,
+        0x10724F,
+        0x107444,
+        0x107638,
+        0x10784C,
+        0x107A3F,
+        0x107C53,
+        0x107E48,
+    ]
+
+    def LunarToSolar(self, lunar):
+        days = LunarSolarConverter.lunar_month_days[
+            lunar.lunarYear - LunarSolarConverter.lunar_month_days[0]
+            ]
+        leap = GetBitInt(days, 4, 13)
+        offset = 0
+        loopend = leap
+        if not lunar.isleap:
+
+            if lunar.lunarMonth <= leap or leap == 0:
+
+                loopend = lunar.lunarMonth - 1
+
+            else:
+
+                loopend = lunar.lunarMonth
+
+        for i in range(0, loopend):
+            offset += GetBitInt(days, 1, 12 - i) == 1 and 30 or 29
+
+        offset += lunar.lunarDay
+
+        solar11 = LunarSolarConverter.solar_1_1[
+            lunar.lunarYear - LunarSolarConverter.solar_1_1[0]
+            ]
+
+        y = GetBitInt(solar11, 12, 9)
+        m = GetBitInt(solar11, 4, 5)
+        d = GetBitInt(solar11, 5, 0)
+
+        return SolarFromInt(SolarToInt(y, m, d) + offset - 1)
+
+    def SolarToLunar(self, solar):
+
+        lunar = Lunar(0, 0, 0, False)
+        index = solar.solarYear - LunarSolarConverter.solar_1_1[0]
+        data = (solar.solarYear << 9) | (solar.solarMonth << 5) | solar.solarDay
+        if LunarSolarConverter.solar_1_1[index] > data:
+            index -= 1
+
+        solar11 = LunarSolarConverter.solar_1_1[index]
+        y = GetBitInt(solar11, 12, 9)
+        m = GetBitInt(solar11, 4, 5)
+        d = GetBitInt(solar11, 5, 0)
+        offset = SolarToInt(
+            solar.solarYear, solar.solarMonth, solar.solarDay
+        ) - SolarToInt(y, m, d)
+
+        days = LunarSolarConverter.lunar_month_days[index]
+        leap = GetBitInt(days, 4, 13)
+
+        lunarY = index + LunarSolarConverter.solar_1_1[0]
+        lunarM = 1
+        offset += 1
+
+        for i in range(0, 13):
+
+            dm = GetBitInt(days, 1, 12 - i) == 1 and 30 or 29
+            if offset > dm:
+
+                lunarM += 1
+                offset -= dm
+
+            else:
+
+                break
+
+        lunarD = int(offset)
+        lunar.lunarYear = lunarY
+        lunar.lunarMonth = lunarM
+        lunar.isleap = False
+        if leap != 0 and lunarM > leap:
+
+            lunar.lunarMonth = lunarM - 1
+            if lunarM == leap + 1:
+                lunar.isleap = True
+
+        lunar.lunarDay = lunarD
+        return lunar
+
+    def __init__(self):
+        pass

+ 227 - 0
app/utils/nlp_time/helpers/str_common.py

@@ -0,0 +1,227 @@
+import regex as re
+from loguru import logger
+
+
+def filter_irregular_expression(input_query):
+    # logger.debug(f"对一些不规范的表达:转换前 {input_query}")
+
+    # 这里对于下个周末这种做转化 把个给移除掉
+    input_query = number_translator(input_query)
+
+    rule = r"[0-9]月[0-9]"
+    pattern = re.compile(rule)
+    match = pattern.search(input_query)
+    if match is not None:
+        index = input_query.find("月")
+        rule = r"日|号"
+        pattern = re.compile(rule)
+        match = pattern.search(input_query[index:])
+        if match is None:
+            rule = r"[0-9]月[0-9]+"
+            pattern = re.compile(rule)
+            match = pattern.search(input_query)
+            if match is not None:
+                end = match.span()[1]
+                input_query = input_query[:end] + "号" + input_query[end:]
+    # 一个半小时
+    pattern = re.compile(r"(.*半)(?=(小时|月))")
+    match = pattern.search(input_query)
+    if match is not None:
+        test_ge = re.compile(r"(.*)(?=个半)")
+        test_ge_match = test_ge.match(match.group())
+        if match.group() == "半":
+            input_query = input_query.replace("半", "0.5")
+        elif test_ge_match is not None:
+            number = test_ge_match.group() + ".5"
+            input_query = input_query.replace(test_ge_match.group() + "个半", number)
+
+    pattern = re.compile(r"小时|月")
+    match = pattern.search(input_query)
+    if match is None:
+        input_query = input_query.replace("个", "")
+
+    input_query = input_query.replace("中旬", "15号")
+    input_query = input_query.replace("傍晚", "午后")
+    input_query = input_query.replace("大年", "")
+    input_query = input_query.replace("新年", "春节")
+    input_query = input_query.replace("五一", "劳动节")
+    input_query = input_query.replace("白天", "早上")
+    input_query = input_query.replace(":", ":")
+    # logger.debug(f"对一些不规范的表达:转换后 {input_query}")
+    return input_query
+
+
+def del_keyword(target, rules):
+    """
+    该方法删除一字符串中所有匹配某一规则字串
+    可用于清理一个字符串中的空白符和语气助词
+    :param target: 待处理字符串
+    :param rules: 删除规则
+    :return: 清理工作完成后的字符串
+    """
+    pattern = re.compile(rules)
+    res = pattern.sub("", target)
+    return res
+
+
+def number_translator(target):
+    """
+    该方法可以将字符串中所有的用汉字表示的数字转化为用阿拉伯数字表示的数字
+    如"这里有一千两百个人,六百零五个来自中国"可以转化为
+    "这里有1200个人,605个来自中国"
+    此外添加支持了部分不规则表达方法
+    如两万零六百五可转化为20650
+    两百一十四和两百十四都可以转化为214
+    一六零加一五八可以转化为160+158
+    该方法目前支持的正确转化范围是0-99999999
+    该功能模块具有良好的复用性
+    :param target: 待转化的字符串
+    :return: 转化完毕后的字符串
+    """
+    # logger.debug(f"before number_translator: {target}")
+    pattern = re.compile(r"[一二两三四五六七八九123456789]万[一二两三四五六七八九123456789](?!(千|百|十))")
+    match = pattern.finditer(target)
+    for m in match:
+        group = m.group()
+        s = group.split("万")
+        s = list(filter(None, s))
+        num = 0
+        if len(s) == 2:
+            num += word2number(s[0]) * 10000 + word2number(s[1]) * 1000
+        target = pattern.sub(str(num), target, 1)
+
+    pattern = re.compile(r"[一二两三四五六七八九123456789]千[一二两三四五六七八九123456789](?!(百|十))")
+    match = pattern.finditer(target)
+    for m in match:
+        group = m.group()
+        s = group.split("千")
+        s = list(filter(None, s))
+        num = 0
+        if len(s) == 2:
+            num += word2number(s[0]) * 1000 + word2number(s[1]) * 100
+        target = pattern.sub(str(num), target, 1)
+
+    pattern = re.compile(r"[一二两三四五六七八九123456789]百[一二两三四五六七八九123456789](?!十)")
+    match = pattern.finditer(target)
+    for m in match:
+        group = m.group()
+        s = group.split("百")
+        s = list(filter(None, s))
+        num = 0
+        if len(s) == 2:
+            num += word2number(s[0]) * 100 + word2number(s[1]) * 10
+        target = pattern.sub(str(num), target, 1)
+
+    pattern = re.compile(r"[零一二两三四五六七八九]")
+    match = pattern.finditer(target)
+    for m in match:
+        target = pattern.sub(str(word2number(m.group())), target, 1)
+
+    # 星期天表达式替换为星期7
+    pattern = re.compile("(?<=(周|星期))[末天日]")
+    match = pattern.finditer(target)
+    for m in match:
+        target = pattern.sub("7", target, 1)
+
+    pattern = re.compile("(?<!(周|星期))0?[0-9]?十[0-9]?")
+    match = pattern.finditer(target)
+    for m in match:
+        group = m.group()
+        s = group.split("十")
+        num = 0
+        ten = str2int(s[0])
+        if ten == 0:
+            ten = 1
+        unit = str2int(s[1])
+        num = ten * 10 + unit
+        target = pattern.sub(str(num), target, 1)
+
+    pattern = re.compile("0?[1-9]百[0-9]?[0-9]?")
+    match = pattern.finditer(target)
+    for m in match:
+        group = m.group()
+        s = group.split("百")
+        s = list(filter(None, s))
+        num = 0
+        if len(s) == 1:
+            hundred = int(s[0])
+            num += hundred * 100
+        elif len(s) == 2:
+            hundred = int(s[0])
+            num += hundred * 100
+            num += int(s[1])
+        target = pattern.sub(str(num), target, 1)
+
+    pattern = re.compile("0?[1-9]千[0-9]?[0-9]?[0-9]?")
+    match = pattern.finditer(target)
+    for m in match:
+        group = m.group()
+        s = group.split("千")
+        s = list(filter(None, s))
+        num = 0
+        if len(s) == 1:
+            thousand = int(s[0])
+            num += thousand * 1000
+        elif len(s) == 2:
+            thousand = int(s[0])
+            num += thousand * 1000
+            num += int(s[1])
+        target = pattern.sub(str(num), target, 1)
+
+    pattern = re.compile("[0-9]+万[0-9]?[0-9]?[0-9]?[0-9]?")
+    match = pattern.finditer(target)
+    for m in match:
+        group = m.group()
+        s = group.split("万")
+        s = list(filter(None, s))
+        num = 0
+        if len(s) == 1:
+            tenthousand = int(s[0])
+            num += tenthousand * 10000
+        elif len(s) == 2:
+            tenthousand = int(s[0])
+            num += tenthousand * 10000
+            num += int(s[1])
+        target = pattern.sub(str(num), target, 1)
+
+    # logger.debug(f"after number_translator: {target}")
+    return target
+
+
+def word2number(s: str):
+    """
+    方法number_translator的辅助方法,可将[零-九]正确翻译为[0-9]
+    :param s: 大写数字
+    :return: 对应的整形数,如果不是数字返回-1
+    """
+    return {
+        "零": 0,
+        "0": 0,
+        "一": 1,
+        "1": 1,
+        "二": 2,
+        "两": 2,
+        "2": 2,
+        "三": 3,
+        "3": 3,
+        "四": 4,
+        "4": 4,
+        "五": 5,
+        "5": 5,
+        "六": 6,
+        "6": 6,
+        "七": 7,
+        "7": 7,
+        "八": 8,
+        "8": 8,
+        "九": 9,
+        "9": 9,
+    }.get(s, -1)
+
+
+def str2int(s: str):
+    try:
+        res = int(s)
+    except Exception:
+        res = 0
+    return res

+ 114 - 0
app/utils/nlp_time/normalizer.py

@@ -0,0 +1,114 @@
+from typing import List, Optional
+
+import arrow
+from loguru import logger
+
+from .helpers.str_common import (
+    del_keyword,
+    filter_irregular_expression,
+    number_translator,
+)
+from .point import TimePoint
+from .result import Result, DeltaType
+from .unit import TimeUnit
+
+
+# 时间表达式识别的主要工作类
+class TimeNormalizer:
+    def __init__(self, isPreferFuture=True, pattern=None):
+        self.isPreferFuture = isPreferFuture
+        if pattern is None:
+            from .resource.pattern import pattern
+
+        self.pattern = pattern
+
+    def parse(self, target: str, baseTime: arrow.Arrow = None) -> dict:
+        """
+        TimeNormalizer的构造方法,baseTime取默认的系统当前时间
+        :param baseTime: 基准时间点
+        :param target: 待分析字符串
+        :return: 时间单元数组
+        """
+        if baseTime is None:
+            baseTime = arrow.now("Asia/Shanghai")
+
+        # logger.debug(f"目标字符串: {target}")
+
+        self.isTimeDelta = False
+        self.timeDelta = None  # type: Optional[DeltaType]
+        self.target = target
+        self.baseTime = baseTime
+        return self.extract()
+
+    def pre(self):
+        """
+        待匹配字符串的清理空白符和语气助词以及大写数字转化的预处理
+        """
+        self.target = filter_irregular_expression(self.target)
+        self.target = del_keyword(self.target, r"\s+")  # 清理空白符
+        self.target = del_keyword(self.target, "[的]+")  # 清理语气助词
+        self.target = number_translator(self.target)  # 大写数字转化
+        # logger.debug(f"清理空白符和语气助词以及大写数字转化的预处理 {self.target}")
+
+    def extract(self) -> dict:
+        """返回 TimeUnit[] 时间表达式类型数组
+        """
+        self.pre()
+        startline = -1
+        endline = -1
+        rpointer = 0
+        temp = []
+
+        match = self.pattern.finditer(self.target)
+        # logger.debug("=======")
+        # logger.debug("用正则提取关键字:")
+        for m in match:
+            # logger.debug(m)
+            startline = m.start()
+            if startline == endline:
+                rpointer -= 1
+                temp[rpointer] = temp[rpointer] + m.group()
+            else:
+                temp.append(m.group())
+            # logger.debug(f"temp:{temp}")
+            endline = m.end()
+            rpointer += 1
+        # logger.debug("=======")
+
+        res: List[TimeUnit] = []
+        # 时间上下文: 前一个识别出来的时间会是下一个时间的上下文,用于处理:周六3点到5点这样的多个时间的识别,第二个5点应识别到是周六的。
+        contextTp = TimePoint()
+
+        # logger.debug(f"基础时间: {self.baseTime}")
+        # logger.debug(f"待处理的字段: {temp}")
+        # logger.debug(f"待处理字段长度: {rpointer}")
+        for i in range(0, rpointer):
+            # 这里是一个类嵌套了一个类
+            res.append(TimeUnit(temp[i], self, contextTp))
+            contextTp = res[i].tp
+
+        # logger.debug(f"全部字段处理后的结果: {res}")
+        res = self.filter(res)
+
+        if self.isTimeDelta and self.timeDelta:
+            return Result.from_timedelta(self.timeDelta)
+        if len(res) == 1:
+            return Result.from_timestamp(res)
+        if len(res) == 2:
+            return Result.from_timespan(res)
+
+        return Result.from_invalid()
+
+    def filter(self, tu_arr: List[TimeUnit]):
+        """
+        过滤掉无效识别。
+        """
+        res = []
+        for tu in tu_arr:
+            if not tu:
+                continue
+
+            if tu.time.timestamp != 0:
+                res.append(tu)
+        # logger.debug(f"过滤无效识别后: {res}")
+        return res

+ 103 - 0
app/utils/nlp_time/point.py

@@ -0,0 +1,103 @@
+from arrow import Arrow
+
+from .helpers.arrow_helper import arrow2grid
+from .result import DeltaType
+
+
+class TimePoint:
+    """
+    时间表达式单元规范化对应的内部类,对应时间表达式规范化的每个字段。\n
+    六个字段分别是:年-月-日-时-分-秒 \n
+    每个字段初始化为-1
+    """
+
+    def __init__(self):
+        #             0年 1月 2日 3时 4分 5秒
+        self.tunit = [-1, -1, -1, -1, -1, -1]
+
+    def copy(self) -> "TimePoint":
+        new_instance = TimePoint()
+        new_instance.tunit = self.tunit.copy()
+        return new_instance
+
+    @property
+    def year(self) -> int:
+        return self.tunit[0]
+
+    @year.setter
+    def year(self, value: int):
+        self.tunit[0] = value
+
+    @property
+    def month(self) -> int:
+        return self.tunit[1]
+
+    @month.setter
+    def month(self, value: int):
+        self.tunit[1] = value
+
+    @property
+    def day(self) -> int:
+        return self.tunit[2]
+
+    @day.setter
+    def day(self, value: int):
+        self.tunit[2] = value
+
+    @property
+    def hour(self) -> int:
+        return self.tunit[3]
+
+    @hour.setter
+    def hour(self, value: int):
+        self.tunit[3] = value
+
+    @property
+    def minute(self) -> int:
+        return self.tunit[4]
+
+    @minute.setter
+    def minute(self, value: int):
+        self.tunit[4] = value
+
+    @property
+    def second(self) -> int:
+        return self.tunit[5]
+
+    @second.setter
+    def second(self, value: int):
+        self.tunit[5] = value
+
+    def is_valid(self):
+        flag = 0
+        for i in self.tunit:
+            if i > 0:
+                flag = 1
+                return True
+        if flag == 0:
+            return False
+
+    def gen_delta(self) -> DeltaType:
+        return {
+            "year": max(self.year, 0),
+            "month": max(self.month, 0),
+            "day": max(self.day, 0),
+            "hour": max(self.hour, 0),
+            "minute": max(self.minute, 0),
+            "second": max(self.second, 0),
+        }
+
+    def get_arrow(self) -> Arrow:
+        year = max(self.year, 1)
+        month = max(self.month, 1)
+        day = max(self.day, 1)
+        hour = max(self.hour, 0)
+        minute = max(self.minute, 0)
+        second = max(self.second, 0)
+        return Arrow(year, month, day, hour, minute, second)
+
+    def set_unit(self, arrow: Arrow):
+        self.tunit = list(map(int, arrow2grid(arrow)))
+
+    def __repr__(self) -> str:
+        return str(self.tunit)

+ 0 - 0
app/utils/nlp_time/resource/__init__.py


+ 44 - 0
app/utils/nlp_time/resource/holiday.py

@@ -0,0 +1,44 @@
+lunar = {
+    "中和节": "02-02",
+    "中秋节": "08-15",
+    "中元节": "07-15",
+    "端午节": "05-05",
+    "春节": "01-01",
+    "元宵节": "01-15",
+    "重阳节": "09-09",
+    "七夕节": "07-07",
+    "7夕节": "07-07",
+    "初1节": "01-01",
+    "初2节": "01-02",
+    "初3节": "01-03",
+    "初4节": "01-04",
+    "初5节": "01-05",
+    "初6节": "01-06",
+    "初7节": "01-07",
+    "初8节": "01-08",
+    "初9节": "01-09",
+    "初10节": "01-10",
+    "初11节": "01-11",
+    "初12节": "01-12",
+    "初13节": "01-13",
+    "初14节": "01-14",
+    "初15节": "01-15",
+}
+
+solar = {
+    "植树节": "03-12",
+    "圣诞节": "12-25",
+    "青年节": "05-04",
+    "教师节": "09-10",
+    "儿童节": "06-01",
+    "元旦节": "01-01",
+    "国庆节": "10-01",
+    "劳动节": "05-01",
+    "妇女节": "03-08",
+    "建军节": "08-01",
+    "航海日节": "07-11",
+    "建党节": "07-01",
+    "记者节": "11-08",
+    "情人节": "02-14",
+    "母亲节": "05-11",
+}

+ 455 - 0
app/utils/nlp_time/resource/pattern.py

@@ -0,0 +1,455 @@
+import regex as re
+
+r = r"""((前|昨|今|明|后)(天|日)?(早|晚)(晨|上|间)?)
+|(\d+个?[年月日天][以之]?[前后])
+|(\d?.?\d+个?半?(小时|钟头|h|H))
+|(\d+个?半?(小时|钟头|h|H))
+|(半个?(小时|钟头))
+|(\d+(分钟|min))
+|([13]刻钟)
+|((上|这|本|下)+(周|星期)([一二三四五六七天日]|[1-7])?)
+|((周|星期)([一二三四五六七天日]|[1-7]))
+|((早|晚)?([0-2]?[0-9](点|时)半)(am|AM|pm|PM)?)
+|((早|晚)?(\d+[::]\d+([::]\d+)*)\s*(am|AM|pm|PM)?)
+|((早|晚)?([0-2]?[0-9](点|时)[13一三]刻)(am|AM|pm|PM)?)
+|((早|晚)?(\d+[时点](\d+)?分?(\d+秒?)?)\s*(am|AM|pm|PM)?)
+|(大+(前|后)天)
+|(([零一二三四五六七八九十百千万]+|\d+)世)
+|([0-9]?[0-9]?[0-9]{2}\.((10)|(11)|(12)|([1-9]))\.((?<!\d))([0-3][0-9]|[1-9]))
+|(现在)
+|(届时)
+|(这个月)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)日)
+|(晚些时候)
+|(今年)
+|(长期)
+|(以前)
+|(过去)
+|(时期)
+|(时代)
+|(当时)
+|(近来)
+|(([零一二三四五六七八九十百千万]+|\d+)夜)
+|(当前)
+|(日(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|((\d+)点)
+|(今年([零一二三四五六七八九十百千万]+|\d+))
+|(\d+[::]\d+(分|))
+|((\d+):(\d+))
+|(\d+/\d+/\d+)
+|(未来)
+|((充满美丽、希望、挑战的)?未来)
+|(最近)
+|(早上)
+|(早(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(日前)
+|(新世纪)
+|(小时)
+|(明天)
+|(([0-3][0-9]|[1-9])[日号])
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)周)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)([零一二三四五六七八九十百千万]+|\d+)年)
+|([一二三四五六七八九十百千万几多]+[天日周月年][后前左右]*)
+|(每[年月日天小时分秒钟]+)|((\d+分)+(\d+秒)?)
+|([一二三四五六七八九十]+来?[岁年])
+|([新?|\d*]世纪末?)
+|((\d+)时)
+|(世纪)
+|(([零一二三四五六七八九十百千万]+|\d+)岁)
+|([星期周]+[一二三四五六七])
+|(星期([零一二三四五六七八九十百千万]+|\d+))
+|(([零一二三四五六七八九十百千万]+|\d+)年)
+|([本后昨当新后明今去前那这][一二三四五六七八九十]?[年月日天])
+|(早|早晨|早上|上午|中午|午后|下午|晚上|晚间|夜里|夜|凌晨|深夜)
+|(回归前后)
+|((\d+点)+(\d+分)?(\d+秒)?左右?)
+|((\d+)年代)
+|(本月(\d+))
+|(第(\d+)天)
+|((\d+)岁)
+|((\d+)年(\d+)月)
+|([去今明]?[年月](底|末))
+|(([零一二三四五六七八九十百千万]+|\d+)世纪)
+|(昨天(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|(年度)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)星期)
+|(年底)
+|([下个本]+赛季)
+|(今年(\d+)月(\d+)日)
+|((\d+)月(\d+)日(数|多|多少|好几|几|差不多|近|前|后|上|左右)午(\d+)时)
+|(今年晚些时候)
+|((\d+)号晚(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(今(数|多|多少|好几|几|差不多|近|前|后|上|左右)(\d+)年)
+|(这个时候)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)个小时)
+|(最(数|多|多少|好几|几|差不多|近|前|后|上|左右)(数|多|多少|好几|几|差不多|近|前|后|上|左右)年)
+|(凌晨)
+|((\d+)年(\d+)月(\d+)日)
+|((\d+)个月)
+|(今天早(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(第[一二三四五六七八九十\d+]+季)
+|(当地时间)
+|(今(数|多|多少|好几|几|差不多|近|前|后|上|左右)([零一二三四五六七八九十百千万]+|\d+)年)
+|(早晨)
+|(一段时间)
+|([本上]周[一二三四五六七])
+|(凌晨(\d+)点)
+|(去年(\d+)月(\d+)日)
+|(年关)
+|(如今)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)小时)
+|(当晚)
+|((\d+)日晚(\d+)时)
+|(([零一二三四五六七八九十百千万]+|\d+)(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|(每年(\d+)月(\d+)日)
+|(([零一二三四五六七八九十百千万]+|\d+)周)
+|((\d+)月)
+|(农历)
+|(两个小时)
+|(本周([零一二三四五六七八九十百千万]+|\d+))
+|(长久)
+|(清晨)
+|((\d+)号晚)
+|(春节)
+|(星期日)
+|(圣诞)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)段)
+|(现年)
+|(当日)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)分钟)
+|(\d+(天|日|周|月|年)(后|前|))
+|((文艺复兴|巴洛克|前苏联|前一|暴力和专制|成年时期|古罗马|我们所处的敏感)+时期)
+|((\d+)[年月天])
+|(清早)
+|(两年)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|(昨天(数|多|多少|好几|几|差不多|近|前|后|上|左右)午(\d+)时)
+|(([零一二三四五六七八九十百千万]+|\d+)(数|多|多少|好几|几|差不多|近|前|后|上|左右)年)
+|(今(数|多|多少|好几|几|差不多|近|前|后|上|左右)(\d+))
+|(圣诞节)
+|(学期)
+|(\d+来?分钟)
+|(过去(数|多|多少|好几|几|差不多|近|前|后|上|左右)年)
+|(星期天)
+|(夜间)
+|((\d+)日凌晨)
+|(([零一二三四五六七八九十百千万]+|\d+)月底)
+|(当天)
+|((\d+)日)
+|(((10)|(11)|(12)|([1-9]))月)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)(数|多|多少|好几|几|差不多|近|前|后|上|左右)年)
+|(今年(\d+)月份)
+|(晚(数|多|多少|好几|几|差不多|近|前|后|上|左右)(\d+)时)
+|(连[年月日夜])
+|((\d+)年(\d+)月(\d+)日(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|((一|二|两|三|四|五|六|七|八|九|十|百|千|万|几|多|上|\d+)+个?(天|日|周|月|年)(后|前|半))
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)年)
+|(早(数|多|多少|好几|几|差不多|近|前|后|上|左右)([零一二三四五六七八九十百千万]+|\d+)点(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|([0-9]{4}年)
+|(周末)
+|(([零一二三四五六七八九十百千万]+|\d+)个(数|多|多少|好几|几|差不多|近|前|后|上|左右)小时)
+|(([(小学)|初中?|高中?|大学?|研][一二三四五六七八九十]?(\d+)?)?[上下]半?学期)
+|(([零一二三四五六七八九十百千万]+|\d+)时期)
+|(午间)
+|(次年)
+|(这时候)
+|(农历新年)
+|([春夏秋冬](天|季))
+|((\d+)天)
+|(元宵节)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)分)
+|((\d+)月(\d+)日(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|(晚(数|多|多少|好几|几|差不多|近|前|后|上|左右)(\d+)时(\d+)分)
+|(傍晚)
+|(周([零一二三四五六七八九十百千万]+|\d+))
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)午(\d+)时(\d+)分)
+|(同日)
+|((\d+)年(\d+)月底)
+|((\d+)分钟)
+|((\d+)世纪)
+|(冬季)
+|((清明)(节)?)
+|(立春)
+|(雨水)
+|(惊蛰)
+|(春分)
+|(谷雨)
+|(立夏)
+|(小满)
+|(芒种)
+|(夏至)
+|(小暑)
+|(大暑)
+|(立秋)
+|(处暑)
+|(白露)
+|(秋分)
+|(寒露)
+|(霜降)
+|(立冬)
+|(小雪)
+|(大雪)
+|(冬至)
+|(小寒)
+|(大寒)
+|(青年节)
+|(教师节)
+|(中元节)
+|(端午)(节)?
+|(劳动节)
+|(7夕)(节)?
+|(建党节)
+|(建军节)
+|(初13)|(初14)|(初15)|(初12)|(初11)|(初9)|(初8)|(初7)|(初6)|(初5)|(初4)|(初3)|(初2)|(初1)
+|(情人节)
+|(母亲节)
+|(中和节)
+|(圣诞)(节)?
+|(中秋)(节)?
+|(元宵)(节)?
+|(航海日)
+|(儿童节)
+|(国庆)(节)?
+|(植树节)
+|(元旦)
+|(重阳节)
+|(妇女节)
+|(记者节)
+|(年代)
+|(([零一二三四五六七八九十百千万]+|\d+)年半)
+|(今年年底)
+|(新年)
+|(本周)
+|(当地时间星期([零一二三四五六七八九十百千万]+|\d+))
+|(([零一二三四五六七八九十百千万]+|\d+)(数|多|多少|好几|几|差不多|近|前|后|上|左右)岁)
+|(半小时)
+|(每周)
+|(([零一二三四五六七八九十百千万]+|\d+)周年)
+|((重要|最后)?时刻)
+|(([零一二三四五六七八九十百千万]+|\d+)期间)
+|(周日)
+|(晚(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(今后)
+|(([零一二三四五六七八九十百千万]+|\d+)段时间)
+|(明年)
+|([12][09][0-9]{2}(年度)?)
+|(今年([零一二三四五六七八九十百千万]+|\d+))|(\d+[::]\d+(分|))
+|((\d+):(\d+))|(\d+/\d+/\d+)
+|(([0-3][0-9]|[1-9])(日|号))
+|(\d+)月
+|(\d+)月(\d+)日
+|(\d+)月(\d+)
+|(两个星期)
+|(过去(数|多|多少|好几|几|差不多|近|前|后|上|左右)周)
+|(本赛季)
+|(半个(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(稍晚)
+|(\d+(天|日|周|月|年)(后|前))
+|(([半一二两三四五六七八九十百千万]+|\d+)年)
+|((一|二|两|三|四|五|六|七|八|九|十|百|千|万|几|多|上|\d+)+个?(天|日|周|月|年)(后|前|半|))
+|((胜利的)日子)
+|(青春期)
+|([12][09][0-9]{2}(年度?))
+|(([零一二三四五六七八九十百千万]+|\d+)生)
+|(今天凌晨)
+|(过去(\d+)年)
+|(元月)
+|((\d+)月(\d+)日凌晨)
+|([前去今明后新]+年)
+|((\d+)月(\d+))
+|(夏天)
+|((\d+)日凌晨(\d+)时许)
+|((\d+)月(\d+)日)
+|((\d+)点半)
+|(去年底)
+|(最后一[天刻])
+|(最(数|多|多少|好几|几|差不多|近|前|后|上|左右)(数|多|多少|好几|几|差不多|近|前|后|上|左右)个月)
+|(圣诞节?)
+|(下?个?(星期|周)(一|二|三|四|五|六|七|天))
+|((\d+)(数|多|多少|好几|几|差不多|近|前|后|上|左右)年)
+|(当天(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|(每年的(\d+)月(\d+)日)
+|((\d+)日晚(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(星期([零一二三四五六七八九十百千万]+|\d+)晚)
+|(深夜)
+|(现如今)
+|([上中下]+午)
+|(第(一|二|三|四|五|六|七|八|九|十|百|千|万|几|多|\d+)+个?(天|日|周|月|年))
+|(昨晚)
+|(近年)
+|(今天清晨)
+|(中旬)
+|(星期([零一二三四五六七八九十百千万]+|\d+)早)
+|(([零一二三四五六七八九十百千万]+|\d+)战期间)
+|(星期)
+|(昨天晚(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(较早时)
+|(个(数|多|多少|好几|几|差不多|近|前|后|上|左右)小时)
+|(([零一二三四五六七八九十百千万]+|\d+)个礼拜)
+|(昨日)
+|([年月]初)
+|((\d+)年的(\d+)月)
+|(每年)
+|(([零一二三四五六七八九十百千万]+|\d+)月份)
+|(今年(\d+)月(\d+)号)
+|(今年([零一二三四五六七八九十百千万]+|\d+)月)
+|((\d+)月底)|(未来(\d+)年)
+|(第([零一二三四五六七八九十百千万]+|\d+)季)
+|(\d?多年)
+|(([零一二三四五六七八九十百千万]+|\d+)个星期)
+|((\d+)年([零一二三四五六七八九十百千万]+|\d+)月)
+|([下上中]午)
+|(早(数|多|多少|好几|几|差不多|近|前|后|上|左右)(\d+)点)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)月)
+|(([零一二三四五六七八九十百千万]+|\d+)个(数|多|多少|好几|几|差不多|近|前|后|上|左右)月)
+|(同([零一二三四五六七八九十百千万]+|\d+)天)
+|((\d+)号凌晨)
+|(夜里)
+|(两个(数|多|多少|好几|几|差不多|近|前|后|上|左右)小时)
+|(昨天)
+|(罗马时代)
+|(目(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(([零一二三四五六七八九十百千万]+|\d+)月)
+|((\d+)年(\d+)月(\d+)号)
+|(((10)|(11)|(12)|([1-9]))月份?)
+|([12][0-9]世纪)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)([零一二三四五六七八九十百千万]+|\d+)天)
+|(工作日)
+|(稍后)
+|((\d+)号(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|(未来([零一二三四五六七八九十百千万]+|\d+)年)
+|([0-9]+[天日周月年][后前左右]*)
+|(([零一二三四五六七八九十百千万]+|\d+)日(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|(最(数|多|多少|好几|几|差不多|近|前|后|上|左右)([零一二三四五六七八九十百千万]+|\d+)刻)
+|(很久)
+|((\d+)(数|多|多少|好几|几|差不多|近|前|后|上|左右)岁)
+|(去年(\d+)月(\d+)号)
+|(两个月)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)午(\d+)时)
+|(古代)
+|(两天)
+|(\d+个?(小时|星期))
+|((\d+)年半)
+|(较早)
+|(([零一二三四五六七八九十百千万]+|\d+)个小时)
+|([一二三四五六七八九十]+周年)
+|(星期([零一二三四五六七八九十百千万]+|\d+)(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|(时刻)
+|((\d+天)+(\d+点)?(\d+分)?(\d+秒)?)
+|((\d+)日([零一二三四五六七八九十百千万]+|\d+)时)
+|((\d+)周年)
+|(([零一二三四五六七八九十百千万]+|\d+)早)
+|(([零一二三四五六七八九十百千万]+|\d+)日)
+|(去年(\d+)月)
+|(过去([零一二三四五六七八九十百千万]+|\d+)年)
+|((\d+)个星期)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)(数|多|多少|好几|几|差不多|近|前|后|上|左右)天)
+|(执政期间)
+|([当前昨今明后春夏秋冬]+天)
+|(去年(\d+)月份)
+|(今(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|((\d+)周)
+|(两星期)
+|(([零一二三四五六七八九十百千万]+|\d+)年代)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)天)
+|(昔日)
+|(两个半月)
+|([印尼|北京|美国]?当地时间)
+|(连日)
+|(本月(\d+)日)
+|(第([零一二三四五六七八九十百千万]+|\d+)天)
+|((\d+)点(\d+)分)
+|([长近多]年)
+|((\d+)日(数|多|多少|好几|几|差不多|近|前|后|上|左右)午(\d+)时)
+|(那时)
+|(冷战时代)
+|(([零一二三四五六七八九十百千万]+|\d+)天)
+|(这个星期)
+|(去年)
+|(昨天傍晚)
+|(近期)
+|(星期([零一二三四五六七八九十百千万]+|\d+)早些时候)
+|((\d+)([零一二三四五六七八九十百千万]+|\d+)年)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)两个月)
+|((\d+)个小时)
+|(([零一二三四五六七八九十百千万]+|\d+)个月)
+|(当年)
+|(本月)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)([零一二三四五六七八九十百千万]+|\d+)个月)
+|((\d+)点(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(目前)
+|(去年([零一二三四五六七八九十百千万]+|\d+)月)
+|((\d+)时(\d+)分)
+|(每月)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)段时间)
+|((\d+)日晚)
+|(早(数|多|多少|好几|几|差不多|近|前|后|上|左右)(\d+)点(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(下旬)
+|((\d+)月份)
+|(逐年)
+|(稍(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|((\d+)年)
+|(月底)
+|((\d+)年(\d+)个月)
+|(同年)
+|(每个月)
+|(一早)
+|((\d+)来?[岁年])
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)个月)
+|([鼠牛虎兔龙蛇马羊猴鸡狗猪]年)
+|(季度)
+|(早些时候)
+|(今天)
+|(每天)
+|(年半)
+|(午后)
+|((\d+)日(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|((数|多|多少|好几|几|差不多|近|前|后|上|左右)个星期)
+|(今天(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|(同[一二三四五六七八九十][年|月|天])
+|(元月)|((\d+)月(\d+)日凌晨)
+|((\d+)月底)
+|(\d+个?(小时|星期))|((\d+)年半)
+|([一二三四五六七八九十]+周年)|(星期([零一二三四五六七八九十百千万]+|\d+)(数|多|多少|好几|几|差不多|近|前|后|上|左右)午)
+|([长近多]年)|((\d+)日(数|多|多少|好几|几|差不多|近|前|后|上|左右)午(\d+)时)
+|((这|数|多|多少|好几|几|差不多|近|前|后|上|左右)两个月)
+|(\d+大寿)
+|(周([零一二三四五六七八九十百千万]+|\d+)早(数|多|多少|好几|几|差不多|近|前|后|上|左右))
+|(半年)
+|(今日)
+|(末日)
+|(昨天深夜)
+|(今年(\d+)月)
+|((\d+)月(\d+)号)
+|((\d+)日夜)
+|((早些|某个|晚间|本星期早些|前些)+时候)
+|((北京|那个|更长的|最终冲突的)时间)|(每个月)
+|(下*个?月)
+|(\d+秒)
+|(T\d+:\d+:\d+)
+|(\d+/\d+/\d+:\d+:\d+.\d+)
+|(\?\?\?\?-\?\?-\?\?T\d+:\d+:\d+)
+|(\d+-\d+-\d+T\d+:\d+:\d+)
+|(\d+/\d+/\d+ \d+:\d+:\d+.\d+)
+|(\d+-\d+-\d+|[0-9]{8})
+|(((\d+)年)?((10)|(11)|(12)|([1-9]))月(\d+))
+|((\d[\.\-])?((10)|(11)|(12)|([1-9]))[\.\-](\d+))""".replace(
+    "\n", ""
+)
+
+
+pattern = re.compile(r)
+
+if __name__ == "__main__":
+    import os
+    import pickle
+
+    # test pattern valid
+    a = pattern.match("一月10号")
+    print(a)
+    fpath = os.path.dirname(__file__) + "/reg.pkl"
+    with open(fpath, "rb") as f:
+        p1 = pickle.load(f)
+
+    a1 = p1.match("一月10号")
+    print(a1)

+ 85 - 0
app/utils/nlp_time/result.py

@@ -0,0 +1,85 @@
+from typing import TYPE_CHECKING, List, Literal, Tuple, TypedDict
+
+from loguru import logger
+
+if TYPE_CHECKING:
+    from .unit import TimeUnit  # noqa: F401
+
+
+class DeltaType(TypedDict):
+    year: int
+    month: int
+    day: int
+    hour: int
+    minute: int
+    second: int
+
+
+class Result(dict):
+    @staticmethod
+    def from_invalid() -> "Error":
+        return Error(error="no time pattern could be extracted.")
+
+    @staticmethod
+    def from_timedelta(delta_dict: DeltaType) -> "Delta":
+        # logger.debug(f"time_delta: {delta_dict}")
+        return Delta(delta_dict)
+
+    @staticmethod
+    def from_timestamp(result: List["TimeUnit"]) -> "Stamp":
+        return Stamp(result[0].time.format("YYYY-MM-DD HH:mm:ss"))
+
+    @staticmethod
+    def from_timespan(result: List["TimeUnit"]) -> "Span":
+        return Span(
+            timespan=[
+                result[0].time.format("YYYY-MM-DD HH:mm:ss"),
+                result[1].time.format("YYYY-MM-DD HH:mm:ss"),
+            ]
+        )
+
+    @property
+    def type(
+            self,
+    ) -> Tuple[
+        Literal["error"],
+        Literal["timestamp"],
+        Literal["timedelta"],
+        Literal["timespan"],
+    ]:
+        """
+        结果类型,有 ``error``、``timespan``、``timedelta``、``timestamp``。
+        """
+        return self["type"]
+
+
+class Error(Result):
+    __slot__ = "type", "error"
+
+    def __init__(self, error) -> None:
+        self["type"] = "error"
+        self["error"] = error
+
+
+class Delta(Result):
+    __slot__ = "type", "timedelta"
+
+    def __init__(self, timedelta: DeltaType) -> None:
+        self["type"] = "timedelta"
+        self["timedelta"] = timedelta
+
+
+class Stamp(Result):
+    __slot__ = "type", "timestamp"
+
+    def __init__(self, timestamp: str) -> None:
+        self["type"] = "timestamp"
+        self["timestamp"] = timestamp
+
+
+class Span(Result):
+    __slot__ = "type", "timespan"
+
+    def __init__(self, timespan) -> None:
+        self["type"] = "timespan"
+        self["timespan"] = timespan

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1102 - 0
app/utils/nlp_time/unit.py