Pythonで文字列をdatetimeに変換する

ソフトウェア開発でよくある問題のひとつに、日付や時刻の取り扱いがあります。例えば、APIから日付と時刻の文字列を取得した後、それを人間が読める形式に変換する必要があります。この場合も、同じAPIを異なるタイムゾーンで使用すると、変換の仕方が異なってくる。優れた日時ライブラリであれば、タイムゾーンに応じた時刻の変換を行うはずだ。これは日付と時刻を扱うときに扱わなければならない多くのニュアンスのうちの1つに過ぎません。

ありがたいことに、Pythonには日付と時刻を扱うための組み込みモジュール datetime が用意されています。おそらく想像がつくと思いますが、日付と時間を操作するための様々な関数が付属しています。このモジュールを使えば、どんな日付と時刻の文字列でも簡単にパースして、 datetime オブジェクトに変換することができます。

datetimeを使った文字列の変換

datetimeモジュールは3つの異なるオブジェクトタイプから構成されています。date,time, そしてdatetimeです。明らかに、dateオブジェクトは日付を保持し、timeは時間を保持し、datetime` は日付と時間の両方を保持します。

例えば、次のコードは現在の日付と時刻を表示します。

import datetime


print ('Current date/time: {}'.format(datetime.datetime.now()))


このコードを実行すると、次のようなものが表示されます。

$ python3 datetime-print-1.py
Current date/time: 2018-06-29 08:15:27.243860


例えば、”2018-06-29 08:15:27.243860″ のフォーマットは ISO 8601 フォーマット (YYYY-MM-DDTHH:MM:SS.mmmmm) である。もし、 datetime オブジェクトを作成するための入力文字列が同じ ISO 8601 フォーマットであれば、簡単に datetime オブジェクトにパースすることができます。

以下のコードを見てみましょう。

import datetime


date_time_str = '2018-06-29 08:15:27.243860'
date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S.%f')


print('Date:', date_time_obj.date())
print('Time:', date_time_obj.time())
print('Date-time:', date_time_obj)


これを実行すると、日付と時刻、そしてdate-timeが表示されます。

$ python3 datetime-print-2.py
Date: 2018-06-29
Time: 08:15:27.243860
Date-time: 2018-06-29 08:15:27.243860


この例では、 strptime という新しいメソッドを使っています。このメソッドは2つの引数をとります。1つ目は日付時刻の文字列表現で、2つ目は入力文字列のフォーマットです。このようにフォーマットを指定することで、 datetime が独自にフォーマットを解釈しようとする必要がなくなるため、パースが非常に高速になります。戻り値は datetime 型である。

この例では、 "2018-06-29 08:15:27.243860" が入力文字列で、 "%Y-%m-%d %H:%M:%S.%f" が日付文字列の形式である。返された datetime 値は date_time_obj 変数に格納される。これは datetime オブジェクトなので、 date()time() のメソッドを直接呼び出すことができます。出力からわかるように、入力された文字列の ‘date’ と ‘time’ の部分が表示されます。

Y-%m-%d %H:%M:%S.%f”` という書式はどういう意味なのか、不思議に思うかもしれません。これらはフォーマルトークンとして知られています。それぞれのトークンは、日、月、年などのように、日付時刻の異なる部分を表します。Pythonでサポートされているすべての異なるタイプのフォーマットコードのリストについては、strptimeのドキュメントをチェックしてください。簡単なリファレンスのために、上のコードで使っているものは以下の通りです。

  • %Y: 年 (4桁)
  • %m: 月
  • %d: その月の日
  • %H: 時間 (24時間)
  • %M: 分
  • %S: 秒
  • %f: マイクロ秒

年を除くこれらのトークンはすべてゼロパディングされることが期待されている。

したがって、文字列のフォーマットがわかっていれば、 strptime を使用して簡単に datetime オブジェクトにパースすることができる。もうひとつ、自明でない例を示そう。

import datetime


date_time_str = 'Jun 28 2018 7:40AM'
date_time_obj = datetime.datetime.strptime(date_time_str, '%b %d %Y %I:%M%p')


print('Date:', date_time_obj.date())
print('Time:', date_time_obj.time())
print('Date-time:', date_time_obj)


以下の出力から、文字列が正しくパースされたことがわかります。

$ python3 datetime-print-3.py
Date: 2018-06-28
Time: 07:40:00
Date-time: 2018-06-28 07:40:00


以下は、よく使われる時刻のフォーマットと、パースに使われるトークンの例です。

"Jun 28 2018 at 7:40AM" -> "%b %d %Y at %I:%M%p"
"September 18, 2017, 22:19:55" -> "%B %d, %Y, %H:%M:%S"
"Sun,05/12/99,12:30PM" -> "%a,%d/%m/%y,%I:%M%p"
"Mon, 21 March, 2015" -> "%a, %d %B, %Y"
"2018-03-12T10:12:45Z" -> "%Y-%m-%dT%H:%M:%SZ"


strptimeのドキュメントで紹介されているテーブルを使えば、どんな形式の日付-時刻文字列でもパースすることができます。

タイムゾーンとdatetimeを扱う

タイムゾーンを扱う場合、datetime の扱いはより複雑になります。つまり、これらのオブジェクトはタイムゾーンに関連するデータを一切含んでいません。しかし、 datetime オブジェクトはタイムゾーンの情報を保持する tzinfo という変数を持っています。

import datetime as dt


dtime = dt.datetime.now()


print(dtime)
print(dtime.tzinfo)


このコードは次のように表示されます。

$ python3 datetime-tzinfo-1.py
2018-06-29 22:16:36.132767
None


tzinfoの出力はNoneで、これは単純なdatetimeオブジェクトです。タイムゾーンの変換を行うために、Pythonではpytzというライブラリが利用可能である。このインストラクションにあるようにインストールすることができる。それでは、pytz` ライブラリを使用して、上記のタイムスタンプを UTC に変換してみましょう。

import datetime as dt
import pytz


dtime = dt.datetime.now(pytz.utc)


print(dtime)
print(dtime.tzinfo)


出力します。

$ python3 datetime-tzinfo-2.py
2018-06-29 17:08:00.586525+00:00
UTC


00:00は表示されている時刻とUTCの時刻の差です。この例では、tzinfoの値もたまたまUTCであったため、00:00のオフセットが発生しています。この場合、datetime` オブジェクトはタイムゾーンを考慮したオブジェクトとなります。

同様に、日付と時刻を表す文字列を他のタイムゾーンに変換することができます。例えば、以下のように “2018-06-29 17:08:00.586525+00:00” という文字列を “America/New_York” タイムゾーンに変換することができる。

import datetime as dt
import pytz


date_time_str = '2018-06-29 17:08:00'
date_time_obj = dt.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S')


timezone = pytz.timezone('America/New_York')
timezone_date_time_obj = timezone.localize(date_time_obj)


print(timezone_date_time_obj)
print(timezone_date_time_obj.tzinfo)


と出力されます。

$ python3 datetime-tzinfo-3.py
2018-06-29 17:08:00-04:00
America/New_York


まず、文字列を datetime オブジェクトである date_time_obj に変換しています。次に、タイムゾーンが設定された datetime オブジェクト、 timezone_date_time_obj に変換します。タイムゾーンを “America/New_York “としたので、UTC時間より4時間遅れていることがわかる。利用可能なタイムゾーンの完全なリストは、このWikipediaのページで確認することができます。

タイムゾーンの変換

以下の例のように、datetime オブジェクトのタイムゾーンをある地域から別の地域へ変換することができます。

import datetime as dt
import pytz


timezone_nw = pytz.timezone('America/New_York')
nw_datetime_obj = dt.datetime.now(timezone_nw)


timezone_london = pytz.timezone('Europe/London')
london_datetime_obj = nw_datetime_obj.astimezone(timezone_london)


print('America/New_York:', nw_datetime_obj)
print('Europe/London:', london_datetime_obj)


まず、現在時刻を持つ datetime オブジェクトを一つ作成し、それを “America/New_York” タイムゾーンとして設定します。次に、astimezone() メソッドを使用して、この datetime を “Europe/London” タイムゾーンに変換しています。両者の時刻は次のように異なる値を表示します。

$ python3 datetime-tzinfo-4.py
America/New_York: 2018-06-29 22:21:41.349491-04:00
Europe/London: 2018-06-30 03:21:41.349491+01:00


予想通り、両者の時刻は5時間ほど離れているため、異なる値になっています。

サードパーティライブラリの使用

Python の datetime モジュールは、すべての異なるタイプの文字列を datetime オブジェクトに変換することができます。しかし、主な問題は、これを行うには strptime が理解できる適切なフォーマットコード文字列を作成する必要があるということです。この文字列の作成には時間がかかるし、コードが読みにくくなる。その代わり、他のサードパーティライブラリを使うことで簡単にできる。

場合によっては、これらのサードパーティライブラリは日付時刻を操作したり比較したりするためのより良いビルトインサポートを持っており、タイムゾーンまでビルトインされているものもあるので、追加のパッケージを入れる必要はない。

以下のセクションで、これらのライブラリのいくつかを見てみましょう。

日付指定

dateutil モジュールは datetime モジュールを拡張したものである。1つの利点は、文字列をパースするためのコードを渡す必要がないことである。例えば

from dateutil.parser import parse


datetime = parse('2018-06-29 22:21:41')


print(datetime)


この parse 関数は文字列を自動的にパースして、 datetime 変数に格納します。パースは自動的に行われます。フォーマット文字列を記述する必要はありません。それでは、 dateutil を使ってさまざまな種類の文字列をパースしてみましょう。

from dateutil.parser import parse


date_array = [
    '2018-06-29 08:15:27.243860',
    'Jun 28 2018 7:40AM',
    'Jun 28 2018 at 7:40AM',
    'September 18, 2017, 22:19:55',
    'Sun, 05/12/1999, 12:30PM',
    'Mon, 21 March, 2015',
    '2018-03-12T10:12:45Z',
    '2018-06-29 17:08:00.586525+00:00',
    '2018-06-29 17:08:00.586525+05:00',
    'Tuesday , 6th September, 2017 at 4:30pm'
]


for date in date_array:
    print('Parsing: ' + date)
    dt = parse(date)
    print(dt.date())
    print(dt.time())
    print(dt.tzinfo)
    print('
')


出力

$ python3 dateutil-1.py
Parsing: 2018-06-29 08:15:27.243860
2018-06-29
08:15:27.243860
None


Parsing: Jun 28 2018 7:40AM
2018-06-28
07:40:00
None


Parsing: Jun 28 2018 at 7:40AM
2018-06-28
07:40:00
None


Parsing: September 18, 2017, 22:19:55
2017-09-18
22:19:55
None


Parsing: Sun, 05/12/1999, 12:30PM
1999-05-12
12:30:00
None


Parsing: Mon, 21 March, 2015
2015-03-21
00:00:00
None


Parsing: 2018-03-12T10:12:45Z
2018-03-12
10:12:45
tzutc()


Parsing: 2018-06-29 17:08:00.586525+00:00
2018-06-29
17:08:00.586525
tzutc()


Parsing: 2018-06-29 17:08:00.586525+05:00
2018-06-29
17:08:00.586525
tzoffset(None, 18000)


Parsing: Tuesday , 6th September, 2017 at 4:30pm
2017-09-06
16:30:00
None


dateutil` モジュールを使うと、ほとんどすべてのタイプの文字列を簡単にパースできることがおわかりいただけると思います。

これは便利ですが、フォーマットを予測する必要があるため、コードがかなり遅くなることを思い出してください。したがって、コードが高いパフォーマンスを必要とする場合、これはあなたのアプリケーションにとって正しいアプローチではないかもしれません。

マヤ

Maya では、文字列のパースやタイムゾーンの変更も非常に簡単です。簡単な例をいくつか紹介します。

import maya


dt = maya.parse('2018-04-29T17:45:25Z').datetime()
print(dt.date())
print(dt.time())
print(dt.tzinfo)


出力されます。

$ python3 maya-1.py
2018-04-29
17:45:25
UTC


異なるタイムゾーンに時間を変換する場合。

import maya


dt = maya.parse('2018-04-29T17:45:25Z').datetime(to_timezone='America/New_York', naive=False)
print(dt.date())
print(dt.time())
print(dt.tzinfo)


出力されます。

$ python3 maya-2.py
2018-04-29
13:45:25
America/New_York


さて、使い方は簡単でしょうか?では、dateutilで使ったのと同じ文字列を使って、mayaを試してみましょう。

import maya


date_array = [
    '2018-06-29 08:15:27.243860',
    'Jun 28 2018 7:40AM',
    'Jun 28 2018 at 7:40AM',
    'September 18, 2017, 22:19:55',
    'Sun, 05/12/1999, 12:30PM',
    'Mon, 21 March, 2015',
    '2018-03-12T10:12:45Z',
    '2018-06-29 17:08:00.586525+00:00',
    '2018-06-29 17:08:00.586525+05:00',
    'Tuesday , 6th September, 2017 at 4:30pm'
]


for date in date_array:
    print('Parsing: ' + date)
    dt = maya.parse(date).datetime()
    print(dt)
    print(dt.date())
    print(dt.time())
    print(dt.tzinfo)


出力してみましょう。

$ python3 maya-3.py
Parsing: 2018-06-29 08:15:27.243860
2018-06-29 08:15:27.243860+00:00
2018-06-29
08:15:27.243860
UTC


Parsing: Jun 28 2018 7:40AM
2018-06-28 07:40:00+00:00
2018-06-28
07:40:00
UTC


Parsing: Jun 28 2018 at 7:40AM
2018-06-28 07:40:00+00:00
2018-06-28
07:40:00
UTC


Parsing: September 18, 2017, 22:19:55
2017-09-18 22:19:55+00:00
2017-09-18
22:19:55
UTC


Parsing: Sun, 05/12/1999, 12:30PM
1999-05-12 12:30:00+00:00
1999-05-12
12:30:00
UTC


Parsing: Mon, 21 March, 2015
2015-03-21 00:00:00+00:00
2015-03-21
00:00:00
UTC


Parsing: 2018-03-12T10:12:45Z
2018-03-12 10:12:45+00:00
2018-03-12
10:12:45
UTC


Parsing: 2018-06-29 17:08:00.586525+00:00
2018-06-29 17:08:00.586525+00:00
2018-06-29
17:08:00.586525
UTC


Parsing: 2018-06-29 17:08:00.586525+05:00
2018-06-29 12:08:00.586525+00:00
2018-06-29
12:08:00.586525
UTC


Parsing: Tuesday , 6th September, 2017 at 4:30pm
2017-09-06 16:30:00+00:00
2017-09-06
16:30:00
UTC


見てわかるように、すべての日付フォーマットが正常にパースされました。

しかし、この違いに気づきましたか?もしタイムゾーンの情報を与えなければ、自動的にUTCに変換されます。ですから、時刻がUTCでない場合は to_timezonenaive パラメータを指定しなければならないことに注意してください。

アロー

ArrowはPythonでdatetimeを扱うための別のライブラリです。以前の maya と同じように、これも自動的に datetime の形式を判別してくれます。一旦解釈されると、 arrow オブジェクトから Python の datetime オブジェクトを返します。

それでは、 maya で使用したのと同じ文字列の例で試してみましょう。

import arrow


dt = arrow.get('2018-04-29T17:45:25Z')
print(dt.date())
print(dt.time())
print(dt.tzinfo)


出力されます。

$ python3 arrow-1.py
2018-04-29
17:45:25
tzutc()


そして、 arrow を使って to メソッドでタイムゾーンを変換する方法は次のとおりです。

import arrow


dt = arrow.get('2018-04-29T17:45:25Z').to('America/New_York')
print(dt)
print(dt.date())
print(dt.time())


出力

$ python3 arrow-2.py
2018-04-29T13:45:25-04:00
2018-04-29
13:45:25


このように、日付と時刻の文字列は “America/NewThink_York “の地域に変換されます。

では、もう一度、先ほどと同じ文字列のセットを使ってみましょう。

import arrow


date_array = [
    '2018-06-29 08:15:27.243860',
    #'Jun 28 2018 7:40AM',
    #'Jun 28 2018 at 7:40AM',
    #'September 18, 2017, 22:19:55',
    #'Sun, 05/12/1999, 12:30PM',
    #'Mon, 21 March, 2015',
    '2018-03-12T10:12:45Z',
    '2018-06-29 17:08:00.586525+00:00',
    '2018-06-29 17:08:00.586525+05:00',
    #'Tuesday , 6th September, 2017 at 4:30pm'
]


for date in date_array:
    dt = arrow.get(date)
    print('Parsing: ' + date)
    print(dt)
    print(dt.date())
    print(dt.time())
    print(dt.tzinfo)


このコードは、コメントアウトされた日付-時刻文字列に対して失敗します(この例の半分以上)。他の文字列は次のように出力されます。

$ python3 arrow-3.py
Parsing: 2018-06-29 08:15:27.243860
2018-06-29T08:15:27.243860+00:00
2018-06-29
08:15:27.243860
tzutc()


Parsing: 2018-03-12T10:12:45Z
2018-03-12T10:12:45+00:00
2018-03-12
10:12:45
tzutc()


Parsing: 2018-06-29 17:08:00.586525+00:00
2018-06-29T17:08:00.586525+00:00
2018-06-29
17:08:00.586525
tzoffset(None, 0)


Parsing: 2018-06-29 17:08:00.586525+05:00
2018-06-29T17:08:00.586525+05:00
2018-06-29
17:08:00.586525
tzoffset(None, 18000)


コメントアウトした日付-時刻文字列を正しくパースするには、対応するフォーマットトークンを渡して、ライブラリにパース方法の手がかりを与える必要があります。例えば、”1月、2月、3月 “などの月名には “MMM “を渡します。利用可能なすべてのトークンについては、このガイドを参照してください。

結論

この記事では、Pythonで文字列をパースして datetime オブジェクトを生成するさまざまな方法を紹介しました。Pythonのデフォルトの datetime ライブラリや、この記事で紹介したサードパーティライブラリなど、様々なライブラリを利用することができます。

デフォルトの datetime パッケージの主な問題は、ほとんどすべての日付時刻の文字列フォーマットに対して、パースコードを手動で指定する必要があることです。そのため、将来的に文字列フォーマットが変更された場合には、コードも変更しなければならない可能性が高いです。しかし、ここで紹介したような多くのサードパーティーライブラリは、それを自動的に処理してくれる。

もう一つ直面する問題は、タイムゾーンの扱いだ。これらを扱う最良の方法は、常にUTC形式でデータベースに時刻を保存し、必要なときにユーザーのローカルタイムゾーンに変換することである。

これらのライブラリは、文字列のパースに適しているだけでなく、さまざまな種類の日付時刻関連の操作に使用することができます。ぜひ、ドキュメントを読んで、その機能を詳しく知ってほしい。

タイトルとURLをコピーしました