こんにちは。TATです。
今日のテーマは「【コード解説】Pythonで和暦を西暦に変換する関数を作る【使い回しOK】」です。
Pythonに関わらず、プログラムを書いているといろいろな問題に直面します。
特に日本で暦を扱うのは厄介です。
和暦と西暦が混在していて、変換がややこしくなります。
コンピューターでデータを扱う際には、便宜上全て西暦にした方が都合がいいことが多いです。
その際には、和暦を西暦に変換しないといけません。
本記事では、Pythonで和暦を西暦に変換するロジックをご紹介します。
コードの内容も解説していきます。
関数にしたので、そのままコピペしてお使いいただけます。
「とりあえずコードだけちょうだい❤️」という方は、「とりあえず全コードをまとめて公開」まで飛んじゃってください。
目次
【コード解説】Pythonで和暦を西暦に変換する関数を作る【使い回しOKです】
和暦→西暦への変換ロジックを考える
まずはコードをご紹介する前に暦変換のロジックを考えてみます。
和暦は遡っていくと、令和、平成、昭和、大正、明治・・・と続いています。
それぞれの和暦のスタートした年をまとめてみました。
ポイント
- 明治: 1868年
- 大正: 1912年
- 昭和: 1926年
- 平成: 1989年
- 令和: 2019年
暦は元年から始まり、以降2年、3年・・・と続いていきます。
元年になれば上記の年がそのまま当てはまります。
2年以降になると、その数字(例: 平成3年なら3)から計算することができます。
数字をそのまま足すと1年ずれるので、マイナス1をつける必要があります。
例えば、平成元年は1989年ですが、平成2年は1990年です。
元年(1998)に2を足すと1991年になってしまい1年ずれます。
ここからマイナス1をすればきちんと合うようになります。
つまりロジックは次のようになります。
ポイント
- 元年なら、該当する西暦をそのまま使えばOK
- 2年以降なら、元年の西暦 + 和暦の年 − 1
例えば、昭和30年であれば、昭和元年の1926 + 30 - 1 = 1955年となります。
これを使って和暦を西暦に変換するコードを書いていきます。
とりあえず全コードをまとめて公開
それではPythonを使った暦(和暦→西暦)変換用のコードをご紹介していきます。
とりあえず、まずはコードを全て公開してしまい、後から順を追って解説していくスタイルにします。
「コードだけ欲しい」という方はこちらからコピペしていただければと思います。
それではどうぞ。
import unicodedata import re import datetime # 各年号の元年を定義 eraDict = { "明治": 1868, "大正": 1912, "昭和": 1926, "平成": 1989, "令和": 2019, } def japanese_calendar_converter(text): # 正規化 normalized_text = unicodedata.normalize("NFKC", text) # 年月日を抽出 pattern = r"(?P<era>{eraList})(?P<year>[0-9]{{1,2}}|元)年(?P<month>[0-9]{{1,2}})月(?P<day>[0-9]{{1,2}})日".format(eraList="|".join(eraDict.keys())) date = re.search(pattern, normalized_text) # 抽出できなかったら終わり if date is None: print("Cannot convert to western year") # 年を変換 for era, startYear in eraDict.items(): if date.group("era") == era: if date.group("year") == "元": year = eraDict[era] else: year = eraDict[era] + int(date.group("year")) - 1 # date型に変換して返す return datetime.date(year, int(date.group("month")), int(date.group("day")))
japanese_calendar_converterという関数にしているので、ここに和暦を突っ込めば西暦に変換されてなおかつdate型に変換されて帰ってきます。
こんな感じできちんと変換されていることが確認できます。
文字列を正規化する処理も加えたので、全角数字が含まれていても自動で半角に変換して処理されます。
全角数字が使われてることって結構ありますからね。。。
今後、新しい年号が加わったり、さらに古い年号も入れたいという場合には、eraDictに年号と開始した西暦を追加いただければOKです。
コードの解説
ここからはコード内容を順を追って解説していきます。
このプログラムの流れは次のとおりです。
プログラムの流れ
- 文字列を正規化(ここで全角数字は半角数字に変換される)
- 正規表現で文字列から和暦を抽出する
- 年号から西暦を計算
- date型に変換する
順番に見ていきましょう。
eraDictで元年の西暦を定義
まず流れに入る前に、一番上にあるeraDictについて触れておきます。
こちらは各年号の元年の西暦を定義しています。
さらに古い年号を追加したい場合には、ここに追加いただければOKです。
順番は特に関係ないので、どこに追加いただいても問題ありません。
文字列を正規化(ここで全角数字は半角数字に変換される)
関数内での最初の処理は、文字列を正規化することです。
こちらのコードですね。
# 正規化 normalized_text = unicodedata.normalize("NFKC", text)
ここでおこなっているのは、文字列のフォーマットの統一です。
特にポイントになってくるのは、全角の数字が半角に変換されることです。
和暦データを扱うと、数字がなぜか全角で表示されていることも珍しくありません。
一応、Pythonでは全角でもきちんと文字列を数値に変換することはできます。
ただこのあと行う正規表現による抽出には文字列が統一されていると精度が向上するので、この正規化はやっておいた方が良いです。
正規表現で文字列から和暦を抽出する
次に行うのは、正規表現から文字列から和暦を抽出することです。
正規表現を使うとパターンにマッチした文字列を抽出することができます。
# 年月日を抽出 pattern = r"(?P<era>{eraList})(?P<year>[0-9]{{1,2}}|元)年(?P<month>[0-9]{{1,2}})月(?P<day>[0-9]{{1,2}})日".format(eraList="|".join(eraDict.keys())) date = re.search(pattern, normalized_text)
長ったらしくなってしまっているので解説しますね。
patternを表示するとこんな感じになります。
()で囲まれている箇所が4つあり、それぞれera, year, month, dayと名前をつけています。
?P<name>をつけると名前をつけることができます。
eraで使っている|はOR条件になります。明治、大正、昭和、平成、令和のいずれかがマッチしていればOKという意味です。
ここはeraDict.keys()から作っているので、年号を追加したらここに自動反映されます。
あとはyear, month, dayを3つですが、全て[0-9]{1,2}となっています。
yearだけは元年の可能性もあるので、OR条件で元も加えています。
[0-9]は0〜9の数字を示しており、{1, 2}は1桁か2桁という意味になります。
要は1桁か2桁の数字がここにきますという意味です。
基本的にここにくる数字は1桁か2桁しかないので桁数を指定しています。
あり得ないことですが、もし令和が100年以上続いて令和104年とかになると3桁になってしまうので、上記のパターンでは合致しなくなります。
その場合は{1, 2, 3}にして3桁を考慮するか、あるいは[0-9]+にしておくと桁数関係なく数字が続く限り抽出することができるようになります。
Pythonで正規表現を扱うreモジュールを使って抽出すればOKです。
ここで一致する和暦が存在しなければ関数は終了します。
年号から西暦を計算
和暦の抽出ができたら、次にやるべきは年号を西暦に変換することです。
ここが一番の肝ですね。
先ほどのロジックを適用します。
# 年を変換 for era, startYear in eraDict.items(): if date.group("era") == era: if date.group("year") == "元": year = eraDict[era] else: year = eraDict[era] + int(date.group("year")) - 1
items()を使うと、辞書のキーと値をセットで取り出すことができます。
また、reを使って抽出したデータは、名前(era, year, month, day)を指定して取り出すことができるので、これらを使って西暦に変換していきます。
抽出したデータは文字列なので、int()で数値に変換してあげる必要があります。
元年ならeraDictにある西暦をそのまま使い、2年以降であれば元年の西暦に和暦の年を足して最後に1を引けばOKです。
これで西暦に変換することができます。
date型に変換する
最後の仕上げが、date型に変換することです。
# date型に変換して返す return datetime.date(year, int(date.group("month")), int(date.group("day")))
先ほど計算したyearと文字列から取り出したmonthとdayを数値に変換したものを使えば一発で変換できます。
これで和暦から西暦への変換が完了します。
まとめ
いかがでしたでしょうか。
ここでは「【コード解説】Pythonで和暦を西暦に変換する関数を作る【使い回しOK】」というテーマで和暦と西暦に変換する方法についてご紹介しました。
和暦を使っているケースは結構あるので、ここで紹介したコードはわりと使えるかと思います。
そもそも西暦で統一してくれたらこんな変換作業は必要ないんですけどね。。。w
一度関数を作ってしまえば使いまわすことができるので、今後はこれを乱用していこうと思います。
皆様もよろしければコピペしてお使いください。
使い勝手のいいように自由に書き換えていただいてOKです。
ここまで読んでくださり、ありがとうございました。