Python 元号(和暦)年を西暦に変換する

# 和暦を西暦に変換する
文章中からなるべく正規表現を使わずにdictを使った変換処理を考えてみた。
これにより元号が増えても、処理時間に影響はない、はず。変換対象文字数に対して線形時間

* 日付までの処理は非対応
* 年数の変換に特化
* 大化の改新から令和まで対応
* 各元号の開始年は[Wikipedia](https://ja.wikipedia.org/wiki/%E5%85%83%E5%8F%B7%E4%B8%80%E8%A6%A7_(%E6%97%A5%E6%9C%AC))の始期を参考
* python3オンリー


```python
# coding=utf8

from datetime import datetime, timedelta

g2d = {
    '令和': datetime(2019, 5, 1), 'R.': datetime(2019, 5, 1),
    '平成': datetime(1989, 1, 8), 'H.': datetime(1989, 1, 8),
    '昭和': datetime(1926, 12, 25), 'S.': datetime(1926, 12, 25),
    '大正': datetime(1912, 7, 30), 'T.': datetime(1912, 7, 30),
    '明治': datetime(1868, 10, 23), 'M.': datetime(1868, 10, 23),
    '慶応': datetime(1865, 5, 1), '元治': datetime(1864, 3, 27), '文久': datetime(1861, 3, 29), '万延': datetime(1860, 4, 8), '安政': datetime(1855, 1, 15), '嘉永': datetime(1848, 4, 1), '弘化': datetime(1845, 1, 9), '天保': datetime(1831, 1, 23), '文政': datetime(1818, 5, 26), '文化': datetime(1804, 3, 22), '享和': datetime(1801, 3, 19), '寛政': datetime(1789, 2, 19), '天明': datetime(1781, 4, 25), '安永': datetime(1772, 12, 10), '明和': datetime(1764, 6, 30), '宝暦': datetime(1751, 12, 14), '寛延': datetime(1748, 8, 5), '延享': datetime(1744, 4, 3), '寛保': datetime(1741, 4, 12), '元文': datetime(1736, 6, 7), '享保': datetime(1716, 8, 9), '正徳': datetime(1711, 6, 11), '宝永': datetime(1704, 4, 16), '元禄': datetime(1688, 10, 23), '貞享': datetime(1684, 4, 5), '天和': datetime(1681, 11, 9), '延宝': datetime(1673, 10, 30), '寛文': datetime(1661, 5, 23), '万治': datetime(1658, 8, 21), '明暦': datetime(1655, 5, 18), '承応': datetime(1652, 10, 20), '慶安': datetime(1648, 4, 7), '正保': datetime(1645, 1, 13), '寛永': datetime(1624, 4, 17), '元和': datetime(1615, 9, 5), '慶長': datetime(1596, 12, 16), '文禄': datetime(1593, 1, 10), '天正': datetime(1573, 8, 25), '元亀': datetime(1570, 5, 27), '永禄': datetime(1558, 3, 18), '弘治': datetime(1555, 11, 7), '天文': datetime(1532, 8, 29), '享禄': datetime(1528, 9, 3), '大永': datetime(1521, 9, 23), '永正': datetime(1504, 3, 16), '文亀': datetime(1501, 3, 18), '明応': datetime(1492, 8, 12), '延徳': datetime(1489, 9, 16), '長享': datetime(1487, 8, 9), '文明': datetime(1469, 6, 8), '応仁': datetime(1467, 4, 9), '文正': datetime(1466, 3, 14),
    '寛正': datetime(1461, 2, 1), '長禄': datetime(1457, 10, 16), '康正': datetime(1455, 9, 6), '享徳': datetime(1452, 8, 10), '宝徳': datetime(1449, 8, 16), '文安': datetime(1444, 2, 23), '嘉吉': datetime(1441, 3, 10), '永享': datetime(1429, 10, 3), '正長': datetime(1428, 6, 10), '応永': datetime(1394, 8, 2), '明徳': datetime(1390, 4, 12), '康応': datetime(1389, 3, 7), '嘉慶': datetime(1387, 10, 5), '至徳': datetime(1384, 3, 19), '永徳': datetime(1381, 3, 20), '康暦': datetime(1379, 4, 9), '永和': datetime(1375, 3, 29), '応安': datetime(1368, 3, 7), '貞治': datetime(1362, 10, 11), '康安': datetime(1361, 5, 4), '延文': datetime(1356, 4, 29), '文和': datetime(1352, 11, 4), '観応': datetime(1350, 4, 4), '貞和': datetime(1345, 11, 15), '康永': datetime(1342, 6, 1), '暦応': datetime(1338, 10, 11), '元中': datetime(1384, 5, 18), '弘和': datetime(1381, 3, 6), '天授': datetime(1375, 6, 26), '文中': datetime(1372, 5, 1), '建徳': datetime(1370, 8, 16), '正平': datetime(1347, 1, 20), '興国': datetime(1340, 5, 25), '延元': datetime(1336, 4, 11), '建武': datetime(1334, 3, 5), '正慶': datetime(1332, 5, 23), '元弘': datetime(1331, 9, 11), '元徳': datetime(1329, 9, 22), '嘉暦': datetime(1326, 5, 28), '正中': datetime(1324, 12, 25), '元亨': datetime(1321, 3, 22), '元応': datetime(1319, 5, 18), '文保': datetime(1317, 3, 16), '正和': datetime(1312, 4, 27), '応長': datetime(1311, 5, 17), '延慶': datetime(1308, 11, 22), '徳治': datetime(1307, 1, 18), '嘉元': datetime(1303, 9, 16), '乾元': datetime(1302, 12, 10), '正安': datetime(1299, 5, 25), '永仁': datetime(1293, 9, 6), '正応': datetime(1288, 5, 29), '弘安': datetime(1278, 3, 23), '建治': datetime(1275, 5, 22), '文永': datetime(1264, 3, 27), '弘長': datetime(1261, 3, 22), '文応': datetime(1260, 5, 24), '正元': datetime(1259, 4, 20), '正嘉': datetime(1257, 3, 31), '康元': datetime(1256, 10, 24), '建長': datetime(1249, 5, 2), '宝治': datetime(1247, 4, 5), '寛元': datetime(1243, 3, 18), '仁治': datetime(1240, 8, 5), '延応': datetime(1239, 3, 13), '暦仁': datetime(1238, 12, 30), '嘉禎': datetime(1235, 11, 1), '文暦': datetime(1234, 11, 27), '天福': datetime(1233, 5, 25), '貞永': datetime(1232, 4, 23), '寛喜': datetime(1229, 3, 31), '安貞': datetime(1228, 1, 18), '嘉禄': datetime(1225, 5, 28), '元仁': datetime(1224, 12, 31), '貞応': datetime(1222, 5, 25), '承久': datetime(1219, 5, 27), '建保': datetime(1214, 1, 18), '建暦': datetime(1211, 4, 23), '承元': datetime(1207, 11, 16), '建永': datetime(1206, 6, 5), '元久': datetime(1204, 3, 23), '建仁': datetime(1201, 3, 19), '正治': datetime(1199, 5, 23), '建久': datetime(1190, 5, 16), '文治': datetime(1185, 9, 9), '元暦': datetime(1184, 5, 27), '寿永': datetime(1182, 6, 29), '養和': datetime(1181, 8, 25), '治承': datetime(1177, 8, 29), '安元': datetime(1175, 8, 16), '承安': datetime(1171, 5, 27), '嘉応': datetime(1169, 5, 6), '仁安': datetime(1166, 9, 23), '永万': datetime(1165, 7, 14), '長寛': datetime(1163, 5, 4), '応保': datetime(1161, 9, 24), '永暦': datetime(1160, 2, 18),
    '平治': datetime(1159, 5, 9), '保元': datetime(1156, 5, 18), '久寿': datetime(1154, 12, 4), '仁平': datetime(1151, 2, 14), '久安': datetime(1145, 8, 12), '天養': datetime(1144, 3, 28), '康治': datetime(1142, 5, 25), '永治': datetime(1141, 8, 13), '保延': datetime(1135, 6, 10), '長承': datetime(1132, 9, 21), '天承': datetime(1131, 2, 28), '大治': datetime(1126, 2, 15), '天治': datetime(1124, 5, 18), '保安': datetime(1120, 5, 9), '元永': datetime(1118, 4, 25), '永久': datetime(1113, 8, 25), '天永': datetime(1110, 7, 31), '天仁': datetime(1108, 9, 9), '嘉承': datetime(1106, 5, 13), '長治': datetime(1104, 3, 8), '康和': datetime(1099, 9, 15), '承徳': datetime(1097, 12, 27), '永長': datetime(1097, 1, 3), '嘉保': datetime(1095, 1, 23), '寛治': datetime(1087, 5, 11), '応徳': datetime(1084, 3, 15), '永保': datetime(1081, 3, 22), '承暦': datetime(1077, 12, 5), '承保': datetime(1074, 9, 16), '延久': datetime(1069, 5, 6), '治暦': datetime(1065, 9, 4), '康平': datetime(1058, 9, 19), '天喜': datetime(1053, 2, 2), '永承': datetime(1046, 5, 22), '寛徳': datetime(1044, 12, 16), '長久': datetime(1040, 12, 16), '長暦': datetime(1037, 5, 9), '長元': datetime(1028, 8, 18), '万寿': datetime(1024, 8, 19), '治安': datetime(1021, 3, 17), '寛仁': datetime(1017, 5, 21), '長和': datetime(1013, 2, 8), '寛弘': datetime(1004, 8, 8), '長保': datetime(999, 2, 1), '長徳': datetime(995, 3, 25), '正暦': datetime(990, 11, 26), '永祚': datetime(989, 9, 10), '永延': datetime(987, 5, 5), '寛和': datetime(985, 5, 19), '永観': datetime(983, 5, 29), '天元': datetime(978, 12, 31), '貞元': datetime(976, 8, 11), '天延': datetime(974, 1, 16), '天禄': datetime(970, 5, 3), '安和': datetime(968, 9, 8), '康保': datetime(964, 8, 19), '応和': datetime(961, 3, 5), '天徳': datetime(957, 11, 21), '天暦': datetime(947, 5, 15), '天慶': datetime(938, 6, 22), '承平': datetime(931, 5, 16), '延長': datetime(923, 5, 29), '延喜': datetime(901, 8, 31), '昌泰': datetime(898, 5, 20), '寛平': datetime(889, 5, 30), '仁和': datetime(885, 3, 11), '元慶': datetime(877, 6, 1), '貞観': datetime(859, 5, 20), '天安': datetime(857, 3, 20), '斉衡': datetime(854, 12, 23), '仁寿': datetime(851, 6, 1), '嘉祥': datetime(848, 7, 16), '承和': datetime(834, 2, 14), '天長': datetime(824, 2, 8), '弘仁': datetime(810, 10, 20), '大同': datetime(806, 6, 8), '延暦': datetime(782, 9, 30), '天応': datetime(781, 1, 30), '宝亀': datetime(770, 10, 23), '神護景雲': datetime(767, 9, 13), '天平神護': datetime(765, 2, 1), '天平宝字': datetime(757, 9, 6), '天平勝宝': datetime(749, 8, 19), '天平感宝': datetime(749, 5, 4), '天平': datetime(729, 9, 2), '神亀': datetime(724, 3, 3), '養老': datetime(717, 12, 24), '霊亀': datetime(715, 10, 3), '和銅': datetime(708, 2, 7), '慶雲': datetime(704, 6, 16), '大宝': datetime(701, 5, 3), '朱鳥': datetime(686, 8, 14), '白雉': datetime(650, 3, 22),
    '大化': datetime(645, 7, 17)
}

g2yidx = {k[1]:{} for k in g2d}
end = datetime(9999, 12, 31)
for k, v in g2d.items():
    g2yidx[k[1]][k[0]] = [len(k), k, v, end - timedelta(seconds=1) if end else end]
    end = v
g2yidx["."]["R"][3] = datetime(9999, 12, 31)
g2yidx["."]["H"][3] = g2d["令和"] - timedelta(seconds=1)
g2yidx["."]["S"][3] = g2d["平成"] - timedelta(seconds=1)
g2yidx["."]["T"][3] = g2d["昭和"] - timedelta(seconds=1)
g2yidx["."]["M"][3] = g2d["大正"] - timedelta(seconds=1)

g2ynum = {
    '〇': 0, '一': 1, '二': 2, '三': 3, '四': 4, '五': 5,
    '六': 6, '七': 7, '八': 8, '九': 9, '０': 0, '１': 1,
    '２': 2, '３': 3, '４': 4, '５': 5, '６': 6, '７': 7,
    '８': 8, '９': 9, '0': 0, '1': 1, '2': 2, '3': 3,
    '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
    '零': 0, '壱': 1, '弐': 2, '参': 3,
    ' ': -1, '　': -1, '十': -1, '拾': -1, '元': 1
}

def gengo2date(s, strict = False):

    slen = len(s)
    if slen < 3:
        return s

    ret = ""
    i = -1

    while (i < slen - 3):
        i += 1

        s1 = s[i + 1]
        if (s1 not in g2yidx):
            ret += s[i]
            continue

        s0 = s[i]
        s1idx = g2yidx[s1]
        if (s0 not in s1idx):
            ret += s0
            continue

        sidx = s1idx[s0]
        elen, era, startdt, enddt = sidx
        i += 2

        if (elen == 4):
            if (s[i - 2] == era[0] and s[i - 1] == era[1]):
                i += 2
            else:
                ret += s0 + s1
                continue

        sy = s[i]
        r = 0
        while (sy in g2ynum):
            y = g2ynum[sy]
            if y != -1:
                r = 10 * r + y
            i += 1
            sy = s[i]

        if (r == 0):
            ret += s0
            i -= elen
            continue

        if (strict and enddt.year < startdt.year + r - 1):
            raise ValueError(f"`{era}` ended in B.C.`{enddt.year}`. years")
        ret += str(startdt.year + r - 1)
        i -= 1

    if (i < slen - 1):
        ret += s[i + 1:slen]

    return ret


print(gengo2date("令和元年05月1日"))     # -> 2019年05月1日
print(gengo2date("平成元年01月1日"))     # -> 1989年01月1日
print(gengo2date("令和２年04月1日"))     # -> 2020年04月1日
print(gengo2date("令和二十三年04月1日"))  # -> 2041年04月1日
print(gengo2date("令和三二年04月1日"))   # -> 2050年04月1日
print(gengo2date("令和 三二年04月1日"))  # -> 2050年04月1日
print(gengo2date("あいうえお令和 三 二年04月1日かきくけこ"))  # -> あいうえお2050年04月1日かきくけこ
print(gengo2date("令和 三 十 二 年04月1日"))  # -> 2050年04月1日
print(gengo2date("R.4 年04月1日"))  # -> 2022年04月1日
print(gengo2date("R.4/04/01"))  # -> 2022/04/01
print(gengo2date("R .4-04-01"))  # -> R .4-04-01(変換しない)
print(gengo2date("平成ほげ年"))  # -> 平成ほげ年(変換しない)
print(gengo2date("R.H"))        # -> R.H(変換しない)
print(gengo2date("R.1グランプリ"))  # -> 2019グランプリ(こういう場合は変換される)
print(gengo2date("R1グランプリ"))  # -> R1グランプリ(これは大丈夫)
print(gengo2date("初代M.1王者"))       # -> 初代1868王者(こういう場合は変換される)
print(gengo2date("初代M1王者"))  # -> 初代M1王者(これは大丈夫)
print(gengo2date("大化1377年"))  # -> 2021年(存在しない年数を入れても変換される)
print(gengo2date("大化1377年", strict = True))  # -> ValueError: `大化` ended in B.C.`650`. years(オプション付ければありえない年数の場合はエラーを出すことも可能。)

In [1]: %timeit gengo2date("平成二十三年04月1日")
2.59 µs ± 51.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
```

全く面倒なルール作りやがってエコじゃないなあ