みーのぺーじ

みーが趣味でやっているPCやソフトウェアについて.Python, Javascript, Processing, Unityなど.

Pythonで日時をISO 8601で扱う

Pythonのdatetimeを用いて,日時をISO 8601で表現します.

aware (タイムゾーンあり) か native (タイムゾーンなし) を意識し,isoformat()fromisoformat()の動作を確認するユニットテストを作成しました.

import datetime
import unittest


class ISOFormatTest(unittest.TestCase):
    def test_datetime_iso_format(self):
        utc = datetime.timezone.utc
        jst = datetime.timezone(offset=datetime.timedelta(hours=9))
        tests = [
            (datetime.datetime(2000, 1, 1, 0, 0, 0), "2000-01-01T00:00:00"),
            (datetime.date(2000, 1, 1), "2000-01-01"),
            (datetime.time(0, 0, 0), "00:00:00"),
            (
                datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=utc),
                "2000-01-01T00:00:00+00:00",
            ),
            (
                datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=jst),
                "2000-01-01T00:00:00+09:00",
            ),
            # datetime.date does not have tzinfo.
            (datetime.time(0, 0, 0, tzinfo=utc), "00:00:00+00:00"),
        ]
        for a, b in tests:
            with self.subTest(a=a):
                self.assertEqual(a.isoformat(), b)

    def test_iso_format_timespec(self):
        utc = datetime.timezone.utc
        dt = datetime.datetime(2000, 1, 1, 0, 0, 0, 0, tzinfo=utc)
        tests = [
            ("auto", "2000-01-01T00:00:00+00:00"),
            ("hours", "2000-01-01T00+00:00"),
            ("minutes", "2000-01-01T00:00+00:00"),
            ("seconds", "2000-01-01T00:00:00+00:00"),
            ("milliseconds", "2000-01-01T00:00:00.000+00:00"),
            ("microseconds", "2000-01-01T00:00:00.000000+00:00"),
        ]
        for a, b in tests:
            with self.subTest(a=a):
                self.assertEqual(dt.isoformat(timespec=a), b)

    def test_datetime_from_iso_format(self):
        utc = datetime.timezone.utc
        jst = datetime.timezone(offset=datetime.timedelta(hours=9))
        tests = [
            (datetime.datetime(2000, 1, 1, 0, 0, 0), "2000-01-01T00:00:00"),
            (
                datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=utc),
                "2000-01-01T00:00:00+00:00",
            ),
            (
                datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=jst),
                "2000-01-01T00:00:00+09:00",
            ),
        ]
        for a, b in tests:
            with self.subTest(a=a):
                self.assertEqual(datetime.datetime.fromisoformat(b), a)

    def test_date_from_iso_format(self):
        tests = [
            (datetime.date(2000, 1, 1), "2000-01-01"),
        ]
        for a, b in tests:
            with self.subTest(a=a):
                self.assertEqual(datetime.date.fromisoformat(b), a)

    def test_time_from_iso_format(self):
        utc = datetime.timezone.utc
        tests = [
            (datetime.time(0, 0, 0), "00:00:00"),
            (datetime.time(0, 0, 0, tzinfo=utc), "00:00:00+00:00"),
        ]
        for a, b in tests:
            with self.subTest(a=a):
                self.assertEqual(datetime.time.fromisoformat(b), a)

実行したところ,問題なく動作しました.

% sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
% python3 --version
Python 3.9.7
% python3 -m unittest test_datetime
.....
----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK

UTCの日時を示す場合は時刻の後ろに Z を添える表現をよく見ますが,2000-01-01T00:00:00+00:00のように+00:00を添えて出力するようです.