見出し画像

Pythonで西暦を和暦に変換するプログラムを作成する

無事に年号が変わり、令和を迎えました。平成生まれの自分としては少し寂しい気持ちもありながら、令和を迎えると共にエンジニアとしても新しい人生が始まります。

平成が終わりかけになる時、以下の記事をおみかけしました。

普段お世話になっているhirokikyさんが開発されているPyQというPythonのプログラミング学習をブラウザで出来るサービスがあり、それに新たに追加されたクエストに「西暦から和暦に変換する」というものがあります。

今回は新たに令和を迎えたというのもあり、これを解いてみます。ちなみにネタバレになるため、自分で問題を解いてみたいという方は、これ以上先を見ないことをオススメいたします。
また、実際にこれから出るコードは実際にPyQの上記クエストで動いたかのテストはしておりません。しばらくPyQはやっていないため、時間があったときにでも試してみたいと思います。それを踏まえた上でご了承ください。

西暦から和暦に変換する関数を作る

convert_to_warekiという関数を作成します。引数は、西暦年のyear, 月であるmonth, 日にちであるdayの3つです。

# coding: utf-8

from datetime import datetime

WAREKI_START = {
    '令和': datetime(2019, 5, 1),
    '平成': datetime(1989, 1, 8),
    '昭和': datetime(1926, 12, 25)
}


def convert_to_wareki(y, m, d):
    """西暦の年月日を和暦の年に変換する."""
    return ''

def main():
    print('西暦2020年4月1日は、', convert_to_wareki(2020, 4, 1), sep='')
    print('西暦2019年5月1日は、', convert_to_wareki(2019, 5, 1), sep='')
    print('西暦2019年4月30日は、', convert_to_wareki(2019, 4, 30), sep='')
    print('西暦1989年1月8日は、', convert_to_wareki(1989, 1, 8), sep='')
    print('西暦1989年1月7日は、', convert_to_wareki(1989, 1, 7), sep='')
    print('西暦1974年5月5日は、', convert_to_wareki(1974, 5, 5), sep='')
    print('西暦1926年12月25日は、', convert_to_wareki(1926, 12, 25), sep='')
    print('西暦1926年12月24日は、', convert_to_wareki(1926, 12, 24), sep='')

if __name__ == '__main__':
   main()

上記コードであるconvert_to_wareki関数に3つの数値型の引数を与え、以下のような「令和2年」「平成31年」といった文字列を返す関数を作成します。

西暦2020年4月1日は、令和2年
西暦2019年5月1日は、令和元年
西暦2019年4月30日は、平成31年
西暦1989年1月8日は、平成元年
西暦1989年1月7日は、昭和64年
西暦1974年5月5日は、昭和49年
西暦1926年12月25日は、昭和元年
西暦1926年12月24日は、昭和以前
西暦1989年4月1日は、平成元年

和暦変換関数の仕様を考える

ここで西暦から和暦に変換するconvert_to_warekiの仕様を考えます。今回は上記のPyQ記事と同じような以下の条件を満たす関数を考えていきます。

- 引数 y に西暦の年、 m に月、 d に日を数値で指定
- 和暦(元号XX年)を返す(例. 1974年5月5日は「昭和49年」)
- 昭和開始日以前の日付の場合は、「昭和以前」と返す
- 各元号の1年は「元年」と返す(例. 2019年6月1日は「令和元年」)

実際に修正を加えた関数

convert_to_warekiを実際に修正を加えて和暦の文字列を返す関数を作成していきましょう。

コード全体図としては以下のような感じです。処理途中で値の例外が発生した際にはValueErrorが走ります。

# coding: utf-8

from datetime import datetime

WAREKI_START = {
   '令和': datetime(2019, 5, 1),
   '平成': datetime(1989, 1, 8),
   '昭和': datetime(1926, 12, 25)
}


def convert_to_wareki(y, m, d):
    """西暦の年月日を和暦の年に変換する."""
    try:
        y_m_d = datetime(y, m, d)
        if WAREKI_START['令和'] <= y_m_d:
            reiwa_year = WAREKI_START['令和'].year
            era_year = y_m_d.year
            year = (era_year - reiwa_year) + 1
            era_str = '令和'
        elif WAREKI_START['平成'] <= y_m_d:
            reiwa_year = WAREKI_START['平成'].year
            era_year = y_m_d.year
            year = (era_year - reiwa_year) + 1
            era_str = '平成'
        elif WAREKI_START['昭和'] <= y_m_d:
            reiwa_year = WAREKI_START['昭和'].year
            era_year = y_m_d.year
            year = (era_year - reiwa_year) + 1
            era_str = '昭和'
        else:
            return '昭和以前'

        if year == 1:
            year = '元'
       
        return era_str + str(year) + '年'
    except ValueError as e:
        raise e

def main():
    print('西暦2020年4月1日は、', convert_to_wareki(2020, 4, 1), sep='')
    print('西暦2019年5月1日は、', convert_to_wareki(2019, 5, 1), sep='')
    print('西暦2019年4月30日は、', convert_to_wareki(2019, 4, 30), sep='')
    print('西暦1989年1月8日は、', convert_to_wareki(1989, 1, 8), sep='')
    print('西暦1989年1月7日は、', convert_to_wareki(1989, 1, 7), sep='')
    print('西暦1974年5月5日は、', convert_to_wareki(1974, 5, 5), sep='')
    print('西暦1926年12月25日は、', convert_to_wareki(1926, 12, 25), sep='')
    print('西暦1926年12月24日は、', convert_to_wareki(1926, 12, 24), sep='')

if __name__ == '__main__':
   main()

ロジックとしては非常にシンプルで、引数で与えられたy, m, dの値を元にdatetime型の変数であるy_m_dを作ります。

その変数を元にそれぞれの和暦が始まる西暦の年より上か下かで比較していくという至ってシンプルなものです。

y_m_d = datetime(y, m, d)
if WAREKI_START['令和'] <= y_m_d:
    reiwa_year = WAREKI_START['令和'].year
    era_year = y_m_d.year
    year = (era_year - reiwa_year) + 1
    era_str = '令和'

テストコードの作成

この章は飛ばしていただていもかまいません。python標準のunittestでのテストコードを書いて、テストを実施してみました。

# coding: utf-8
import unittest

from datetime import datetime
from wareki import convert_to_wareki

class TestSuccessWareki(unittest.TestCase):
    """test class of wareki.py
    """

    def setUp(self):
        """ setUp
        """
        self.wareki_dict = {
            '令和': datetime(2019, 5, 1),
            '平成': datetime(1989, 1, 8),
            '昭和': datetime(1926, 12, 25)
        }

    def test_reiwa(self):
        """test method for reiwa of convert_to_wareki
        """
        reiwa_start = self.wareki_dict['令和']
        for year in range(int(reiwa_start.year), 2030):
            month = reiwa_start.month
            day = reiwa_start.day

            wareki = convert_to_wareki(year, month, day)
            wareki_str = '令和' + str(year-2018) + '年'
            if (year-2018) is 1:
                wareki_str = '令和元年'
            self.assertEqual(wareki, wareki_str)

    def test_heisei(self):
        """test method for heisei of convert_to_wareki
        """
        heisei_start = self.wareki_dict['平成']
        reiwa_start = self.wareki_dict['令和']
        for year in range(int(heisei_start.year), int(reiwa_start.year)+1):
            month = heisei_start.month
            day = heisei_start.day

            wareki = convert_to_wareki(year, month, day)
            wareki_str = '平成' + str(year-(int(heisei_start.year)-1)) + '年'
            if (year-(int(heisei_start.year)-1)) is 1:
                wareki_str = '平成元年'
            self.assertEqual(wareki, wareki_str)

    def test_showa(self):
        """test method for showa of convert_to_wareki
        """
        showa_start = self.wareki_dict['昭和']
        heisei_start = self.wareki_dict['平成']
        for year in range(int(showa_start.year), int(heisei_start.year)):
            month = showa_start.month
            day = showa_start.day

            wareki = convert_to_wareki(year, month, day)
            wareki_str = '昭和' + str(year-(int(showa_start.year)-1)) + '年'
            if (year-(int(showa_start.year)-1)) is 1:
                wareki_str = '昭和元年'
            self.assertEqual(wareki, wareki_str)

    def test_other(self):
        """test method for other of convert_to_wareki
        """
        showa_start = self.wareki_dict['昭和']
        for year in range(
            showa_start.year-1,
            showa_start.year - int(str(showa_start.year)[2:])-1,
            -1
        ):
            month = showa_start.month
            day = showa_start.day
            wareki = convert_to_wareki(year, month, day)
            self.assertEqual(wareki, "昭和以前")

class TestFailedWareki(unittest.TestCase):

    def test_out_of_range_month(self):
        """
        """
        year = 2019
        day = 1
        start_month = 13
        for month in range(start_month, 20):
            with self.assertRaises(ValueError) as er:
                convert_to_wareki(year, month, day)

        self.assertIsInstance(er.exception, ValueError)
        self.assertEqual(er.exception.args[0], 'month must be in 1..12')

    def test_out_of_range_day(self):
        """
        """
        year = 2019
        for month in range(1, 13):
            for day in range(32, 50):
                with self.assertRaises(ValueError) as er:
                    convert_to_wareki(year, month, day)

                err_mes = 'day is out of range for month'
                self.assertIsInstance(er.exception, ValueError)
                self.assertEqual(er.exception.args[0], err_mes)

if __name__ == "__main__":
    unittest.main()


まとめ

convert_to_warekiの中身のロジックはいろんな書き方があると思います。今回はdatetime型を使ったため、非常に簡単に実装することができましたが、あえて自分で月や日にちの範囲を判定する処理を書いたりしてみても面白いのかなと思います。

良い書き方などがあれば教えていただけると幸いです。

この記事が気に入ったらサポートをしてみませんか?