ソフトウェア開発でよくある問題のひとつに、日付や時刻の取り扱いがあります。例えば、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_timezone
と naive
パラメータを指定しなければならないことに注意してください。
アロー
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形式でデータベースに時刻を保存し、必要なときにユーザーのローカルタイムゾーンに変換することである。
これらのライブラリは、文字列のパースに適しているだけでなく、さまざまな種類の日付時刻関連の操作に使用することができます。ぜひ、ドキュメントを読んで、その機能を詳しく知ってほしい。