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

前回記事と目的は同じだが、ジェネレータ（イテレータ）として使えるように手を加えた。
というかジェネレータで動かせるようにすることが目標だったし一旦備忘録として残しておく。

* Python3.3以上
* ひとつ前の記事に同じ

イテレータで処理できると何がうれしいかというと

## メモリに乗らないような巨大ファイルの変換とか

```python
with open("big_wareki.txt") as f, open("big_seireki.txt", "w") as w:
    for word in gengo2date(f):
        w.write(w)
```

## IOネックなデータのストリーミング変換とか

```python
import urllib.request
with urllib.request.urlopen('http://www.example.com') as response:
    dl = (b.decode('utf-8') for b in response)
    for word in gengo2date(dl):
        print(word)
```


# 和暦→西暦変換　ジェネレータバージョンのソース

いろんなデータパターンでちゃんと期待した動作をするかまでちゃんとテストできてない。。

```python
# coding=utf8

from datetime import datetime, timedelta
from collections import deque

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(it, strict=False):
    try:
        q = deque(next(it), maxlen=4)

        while q:
            q.append(next(it))

            s1 = q[1]
            if (s1 not in g2yidx):
                yield q.popleft()
                continue

            s0 = q[0]
            s1idx = g2yidx[s1]
            if (s0 not in s1idx):
                yield q.popleft()
                continue

            sidx = s1idx[s0]
            elen, era, startdt, enddt = sidx

            sy = next(it)
            q.append(sy)
            q.append(next(it))

            if (elen == 4):
                if (q[0] == era[0] and q[1] == era[1]):
                    sy = next(it)
                    q.append(sy)
                    q.append(next(it))
                else:
                    yield q.popleft() + q.popleft()
                    continue

            if (sy not in g2ynum):
                yield s0
                continue

            q.append(next(it))

            r = 0
            try:
                while (sy in g2ynum):
                    y = g2ynum[sy]
                    if y != -1:
                        r = 10 * r + y
                    q.append(next(it))
                    sy = q[1]
            except StopIteration:
                if (q[1] in g2ynum):
                    y = g2ynum[sy]
                    if y != -1:
                        r = 10 * r + y
                    q.popleft()
                q.popleft()
                q.popleft()

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

    except StopIteration:
        yield from q


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


```

本当はLinuxサーバ上で直接動かせるようPython2.6でも動くよう共通化したかったが、
途中でユニコード処理、nextと辞書の順序記憶の仕様差に挫折して、Python3系限定になっちゃった。