2018/12/03
[Python] datetime オブジェクト、Unix time、文字列の相互変換まとめ
Python で [datetime オブジェクト]() 、 [Unix time](https://ja.wikipedia.org/wiki/UNIX%E6%99%82%E9%96%93)、文字列を相互変換する方法はいろいろあります。 中には直感に反するものもあり 変換には様々な方法があるので、上記が唯一の方法というわけではありませんが、 なるべく、安全かつシンプルになるものを選んだつもりです。 また、変換可能でも、安全では無いと判断したものは矢印を引いていません 。 以下2点は Python のバージョンによっては使用できないので、代替案を掲載しておきました。 - `datetime.timestamp()` (3.3 以降で使用可) - `datetime.fromisoformat()` (3.7 以降で使用可) なお、掲載コードは以下のように import 済みとします。 ```python from datetime import datetime import pytz ``` ## 1. Unix time と datetime オブジェクトの相互変換 ### Unix time → datetime (aware) Unix time から datetime オブジェクトを生成するには、[fromtimestamp()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.fromtimestamp) を使用します。 この時、`tz` は必ず指定するようにしましょう。そうしないと、**ローカルのタイムゾーンを適用した native オブジェクト** が生成されてしまいます。 例外的に、[datetime.now()](https://docs.python.org/3/library/datetime.html#datetime.datetime.now) 等でローカル時間を取得して処理する時は `tz` を指定しなくても良いかもしれません。 ```python datetime.fromtimestamp(1541030400, tz=pytz.utc) # 2018-11-01 00:00:00+00:00 datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo')) # 2018-11-01 09:00:00+09:00 datetime.fromtimestamp(1541030400) # native # 2018-11-01 09:00:00 (+09:00 のタイムゾーン設定されているマシンで実行した場合) # 2018-10-31 20:00:00 (-05:00 のタイムゾーン設定されているマシンで実行した場合) ``` ### Unix time → datetime (native) Unix Time から native オブジェクトを生成したい場合は、[utcfromtimestamp()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.utcfromtimestamp) が使えます。 ```python datetime.utcfromtimestamp(1541030400) # 2018-11-01 00:00:00 ``` 直感的には、UTC な aware オブジェクトが生成されて欲しいところ。 間違えないように気をつけましょう。 ### datetime (aware) → Unix time datetime オブジェクトから Unix time に変換する場合は、[timestamp()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.timestamp) を使用します。 この時、native オブジェクトから変換すると、**ローカルのタイムゾーンを適用して変換**されてしまいます。 こちらも、[fromtimestamp()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.fromtimestamp) と同様、[datetime.now()](https://docs.python.org/3/library/datetime.html#datetime.datetime.now) 等で取得したローカル時間の native オブジェクトを処理する時は良いかもしれませんが、基本的には使わないようにした方が良いでしょう。 ```python dt = datetime.fromtimestamp(1541030400, tz=pytz.utc) int(dt.timestamp()) # 1541030400 dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo')) int(dt.timestamp()) # 1541030400 dt = datetime.utcfromtimestamp(1541030400) # native int(dt.timestamp()) # 1540998000 (+09:00 のタイムゾーン設定されているマシンで実行した場合) # 1541044800 (-05:00 のタイムゾーン設定されているマシンで実行した場合) ``` ### timestamp() が使えない時 [timestamp()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.timestamp) は Python 3.3 以降で導入されました。 それ以前のバージョンで Unix time に変換したい場合は、以下のように calendar を使うと良いようです。 この時、UTC の aware に変換しないと想定外の値に変換されてしまうので注意が必要です。 ```python import calendar dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo')) calendar.timegm(dt.astimezone(pytz.utc).timetuple()) # 1541030400 ``` > 参考 > > [Pythonの日付処理とTimeZone | Nekoya press](http://nekoya.github.io/blog/2013/06/21/python-datetime/) ## 2. aware と native の相互変換 ### datetime (aware) → datetime (native) aware から native にする場合は `replace(tzinfo=None)` を使用します。 ```python dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo')) # 2018-11-01 09:00:00+09:00 dt.replace(tzinfo=None) # 2018-11-01 09:00:00 ``` ### datetime (native) → datetime (aware) native から aware に変換する時は `replace(tzinfo=xxx)` を**使ってはいけません**。 代わりに `pytz.localize()` を使用します。 ```python dt = datetime.utcfromtimestamp(1541030400) # 2018-11-01 00:00:00 pytz.timezone('Asia/Tokyo').localize(dt) # 2018-11-01 00:00:00+09:00 ``` タイムゾーンは日付によってオフセットが変わるので 、このような仕様になっているのですが、 かなり紛らわしく間違いやすいので注意が必要です。 > 参考 > - [Pythonでdatetimeにtzinfoを付与するのにreplaceを使ってはいけない | Nekoya press](http://nekoya.github.io/blog/2013/07/05/python-datetime-with-jst/) > - [pytzの仕様が変わっている - Qiita](https://qiita.com/higitune/items/0ca244373d380cf1c060) ## 3. aware のタイムゾーンを変更する 同じ Unix time を持つ別のタイムゾーンに変換するには [astimezone()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.astimezone) を使用します。 ```python dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo')) # 2018-11-01 09:00:00+09:00 dt.astimezone(pytz.timezone('America/New_York')) # 2018-10-31 20:00:00-04:00 ``` なお、`astimezone(None)` は native に変換するのではなく、**ローカルのタイムゾーンに変更**します。 最初、メリットがわからなかったのですが、これを使ってローカルタイムゾーンを取得するという技が紹介されていました 。 > 参考 > > [datetime - Python: Figure out local timezone - Stack Overflow](https://stackoverflow.com/a/39079819) ## 4. 文字列と datetime オブジェクトの相互変換 書式文字列を使用して、任意の文字列と datetime を相互変換することが出来ます。 > 書式文字列のディレクティブ一覧は以下 > > [strftime() and strptime() Behavior](https://docs.python.org/3.6/library/datetime.html#strftime-strptime-behavior) ### 文字列 → datetime 文字列から datetime オブジェクトに変換する場合は [strptime()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.strptime) を使用します。 `%z` を含む場合は aware になり、含まない場合は native になります。 なお、`%Z`(大文字)は `strptime()` では使用できないようです。 ```python datetime.strptime("2018/11/01 09:00", "%Y/%m/%d %H:%M") # 2018-11-01 09:00:00 datetime.strptime("2018/11/01 09:00+0900", "%Y/%m/%d %H:%M%z") # 2018-11-01 09:00:00+09:00 ``` ### datetime → 文字列 datetime から文字列に変換するには [strftime()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.strftime) を使用します。 native に対し `%z` を指定しても何も表示されません。 ```python dt = datetime.utcfromtimestamp(1541030400) # 2018-11-01 00:00:00 dt.strftime("%Y/%m/%d %H:%M%z") # 2018/11/01 00:00 dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo')) # 2018-11-01 09:00:00+09:00 dt.strftime("%Y/%m/%d %H:%M%z") # 2018/11/01 09:00+0900 dt.strftime("%Y/%m/%d %H:%M(%Z)") # 2018/11/01 09:00(JST) ``` ## 5. ISO フォーマット文字列と datetime オブジェクトの相互変換 [ISO 8601 形式](https://ja.wikipedia.org/wiki/ISO_8601) と datetime の相互変換も提供されています。 ただ、ISO 8601 にも基本と拡張が存在し、拡張形式にしか対応していない様子。 さらに UTC を表す "Z" には対応していないなど、使い勝手はイマイチです。 個人的には [strptime()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.strptime) と [strftime()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.strftime) を使用した方が柔軟性が高く、便利かと思います。 ### ISO フォーマット文字列 → datetime オブジェクト ISO フォーマット文字列から datetime に変換するには [fromisoformat()](https://docs.python.org/3.7/library/datetime.html#datetime.datetime.fromisoformat) を使用します。 上述のように、拡張形式にしか対応していません 。 ```python iso_string = "2018-11-01T09:00:00+09:00" datetime.fromisoformat(iso_string) # 2018-11-01 09:00:00+09:00 ``` [fromisoformat()](https://docs.python.org/3.7/library/datetime.html#datetime.datetime.fromisoformat) は Python 3.7 で導入されたので、 それ以前の場合は [strptime()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.strptime) で代用することになります。 厄介なのは ISO フォーマットと `strptime()` のタイムゾーンの記述方法が異なる点です。 ISO は `+09:00` なのに対し、 `strptime()` は `+0900` でコロンがありません。 あまり良い方法は提供されていないようなので、愚直に置換してやるのがよさそうです > 参考 > > [python - parsing timezone with colon - Stack Overflow](https://stackoverflow.com/a/45300534) ```python iso_string = "2018-11-01T09:00:00+09:00" # タイムゾーンのフォーマットを変更 dt_string = iso_string if ":" != iso_string[-3:-2] else iso_string[:-3]+iso_string[-2:] # 2018-11-01T09:00:00+0900 datetime.strptime(dt_string, "%Y-%m-%dT%H:%M:%S%z") # 2018-11-01 09:00:00+09:00 ``` ### datetime オブジェクト → ISO フォーマット文字列 datetime から IOSフォーマット文字列に変換するには [isoformat()](https://docs.python.org/3.6/library/datetime.html#datetime.datetime.isoformat) を使用します。 ```python dt = datetime.fromtimestamp(1541030400, tz=pytz.timezone('Asia/Tokyo')) # 2018-11-01 09:00:00+09:00 dt.isoformat() # 2018-11-01T09:00:00+09:00 dt.isoformat(timespec='hours') # 2018-11-01T09+09:00 ``` `timespec` や `sep` を用いて多少フォーマットを変更できますが、上述のように全ての ISO 8601 形式をカバーしているわけではないので、使用できるシーンは限られそうです。 ## 6. コンストラクタ コンストラクタでタイムゾーンを指定すると、native に対し `replace()` したのと同等になってしまいます。 コンストラクタで `tzinfo` を指定するのではなく、native で作成してから `pytz.localize()` で aware に変換するようにしましょう。 ```python # こちらが正解 dt = datetime(2018, 9, 1) # native pytz.timezone('Asia/Tokyo').localize(dt) # 2018-09-01 00:00:00+09:00 # 以下のようにコンストラクタで tzinfo を指定すると19分というずれが発生 datetime.datetime(2018, 9, 1, tzinfo=pytz.timezone('Asia/Tokyo')) # 2018-09-01 00:00:00+09:19 ``` > 参考 > > - [[Python] datetime のコンストラクタでタイムゾーンは指定しない方がよい](https://kokufu.blogspot.com/2018/11/python-datetime.html) > - [pytzの仕様が変わっている - Qiita](https://qiita.com/higitune/items/0ca244373d380cf1c060) > - [Pythonでdatetimeにtzinfoを付与するのにreplaceを使ってはいけない | Nekoya press](http://nekoya.github.io/blog/2013/07/05/python-datetime-with-jst/)、 過去に1度間違えたのに、再度間違えるということもしばしば。 そこで、2度と間違えないように、個人的にベストと思う方法をまとめてみました。
0 件のコメント:
コメントを投稿