リモート開発メインのソフトウェア開発企業のエンジニアブログです

Python での日付・時刻の扱い方

にわか Pythonista の管理人です。ちょっとしたメモ書き程度の内容でもブログに書いていこうと思ったので、今回は Python での日付・時刻の扱い方について軽く書いていきます。

(言語問わず)日付・時刻の基本的な話

日付・時刻を保持する方法

日付・時刻(以下、基本的には「日時」と書きます)をプログラムで扱う際に、どういう形で日時を保持するかというと、大きく分けて以下の3通りがあります。

  • 文字列で保持する(e.g. 2022-05-23 11:30
  • Unix Timestamp (UTC 1970-01-01 00:00:00 からの秒数)で保持する
  • 日付・時刻専用のクラス(Python では datetime など)

これらの3つの形式で保持されている日時を、プログラム内で別の形式に変換するというのはよくある処理だと思います。

タイムゾーンの有無

さて、プログラマーをやっていると、UTC (協定世界時)と JST (日本標準時)の違いで躓いたりバグを出したりした経験は一度はあると思います。

タイムゾーンが異なると、同じ日時でも文字列としての表現が変わってきます。例えば、日本時間での2022年5月26日 午前11:30は、UTCでは2022年5月26日 午前02:30です。つまり、

  • 2022年5月26日 午前11:30(日本標準時)
  • 2022年5月26日 午前02:30(UTC)

という2つの文字列は、見た目は違いますが、実際には同じ日時を指しています。

では、以下の見た目が異なる2つの文字列は同じ日時を指しているでしょうか。

  • 2022年5月26日 11:30
  • 2022年5月26日 02:30

これについては、正解を先に言ってしまうと「分からない」です。理由としては、どのタイムゾーンの日時かが分からないためです。(また、02:30 や 11:30 というのが午前なのか午後なのかも分かりませんが、午前/午後の話は本題ではないので、以降は触れません。)

以上から分かるとおり、文字列の場合は、文字列中にタイムゾーンを表す情報が含まれていない場合、どのタイムゾーンの日時かが分かりません。こうした場合、多くのプログラムでは、

  • 「プログラム中の日付・時刻は、すべて日本標準時(JST)である」
  • 「プログラム中の日付・時刻は、すべて協定標準時(UTC)である」

といった前提で処理をしていると思います。

さて、最初の方で、日時を保持する方法として主に3通りあると書きましたが、文字列以外の残りの2つに関してはどうでしょうか。

Unix Timestamp に関しては、単なる整数値なのでタイムゾーンという概念はありません。日本時間での2022年5月26日 午前11:30 = UTC の2022年5月26日 午前02:30 = Unix Timestamp 1653532200 です。

次に「日付・時刻専用のクラス」に関してですが、これは言語・ライブラリによって異なりますが、多くの言語・ライブラリでは

  • タイムゾーン有りの日時
  • タイムゾーン無しの日時

を両方扱うことが出来ます。

まとめると、以下の通りになります。

  • 文字列 → 文字列中にタイムゾーンを表す文字列が含まれているか否かで、タイムゾーン有りの日時かどうかが決まる
  • Unix Timestamp → タイムゾーンの概念は無い
  • 日付・時刻専用のクラス → タイムゾーン有り・無し、両方を扱うことが出来る

そもそもタイムゾーンとは何か

今まで何となく「タイムゾーン」という言葉を使ってきましたが、そもそもどういう意味なのでしょうか。

Wikipedia の日本語版では「共通の標準時や常用時を使う地域全体の事」と書かれています。例えば、日本であれば北海道だろうと沖縄だろうと同じ時刻を使います。東日本では日が沈んで完全に暗くなっている頃、九州ではまだ夕暮れ時かもしれませんが、時刻としては同じ17時だったりします。

タイムゾーンの表記の仕方としては、大きく分けて以下の3通りがあります。

  • UTCとの時差を表す: +0900, +09:00, +09
  • 英単語の略称: JST (Japan Standard Time) 等
  • そのタイムゾーンの地域名や主要な都市名: Asia/Tokyo

+09:00 = JST = Asia/Tokyo です。

ここで厄介なのが夏時間です。例えば、アメリカの東海岸の場合は、以下の通りになります。

  • UTCとの時差: 冬時間は -05:00 , 夏時間は -04:00
  • 英単語の略称: 冬時間は EST (Eastern Standard Time), 夏時間は EDT (Eastern Daylight Time)
  • そのタイムゾーンの地域名や主要な都市名: America/New_York

3番目の表記方法なら夏時間でも冬時間でも一緒ですが、最初の2つの表記方法だと夏と冬で表記が変わります。

Python 標準ライブラリの datetime 型

ここからがようやく Python での話です。Python の標準ライブラリには datetime というクラスがありますが、これについて説明します。ドキュメントは以下にあります。

datetime — 基本的な日付型および時間型 — Python 3.10.4 ドキュメント

aware 及び naive な datetime オブジェクト

ドキュメントにあるとおり、datetime はタイムゾーン有り(aware)とタイムゾーン無し(naive)の日時の両方を扱うことが出来ます。

まずはタイムゾーン無し(naive)の datetime オブジェクトを生成する例を見てみます。

import datetime

dt_naive1 = datetime.datetime.now()
dt_naive2 = datetime.datetime.strptime('2022-05-26', '%Y-%m-%d')
dt_naive3 = datetime.datetime(2022, 5, 26, 0, 0, 0, 0)

print(dt_naive1) # 2022-05-26 13:06:08.211255
print(dt_naive2) # 2022-05-26 00:00:00
print(dt_naive3) # 2022-05-26 00:00:00

次にタイムゾーン有り(aware)の datetime オブジェクトを生成する例を見てみます。

import datetime

dt_aware1 = datetime.datetime.now(datetime.timezone.utc)
dt_aware2 = datetime.datetime.strptime('2022-05-26+0900', '%Y-%m-%d%z')
dt_aware3 = datetime.datetime(2022, 5, 26, 0, 0, 0, 0, tzinfo=datetime.timezone.utc)

print(dt_aware1) # 2022-05-26 02:06:08.211255+00:00
print(dt_aware2) # 2022-05-26 00:00:00+09:00
print(dt_aware3) # 2022-05-26 00:00:00+00:00

timezone オブジェクトの生成

先ほどの timezone aware な datetime オブジェクトを生成する際に datetime.timezone.utc というタイムゾーンを指定した箇所がありましたが、日本時間の場合にはどうすれば良いのでしょうか。

datetime.timezone.jst というのがあれば簡単なのですが、そのようなものは存在しません。その場合、timezone オブジェクトを生成して使用します。具体的には以下の通りです。

import datetime

tz_jst = datetime.timezone(datetime.timedelta(hours=9)) # UTC とは9時間差
dt_aware4 = datetime.datetime.now(tz_jst)
print(dt_aware4) # 2022-05-26 13:06:08.211255+09:00

勘の良い方は気づいたかもしれませんが、この方法だと、アメリカのように夏時間がある場合には簡単には対応できません。詳しくは後述します。

naive なオブジェクトを aware なオブジェクトに変換

上の方で、文字列にタイムゾーンを示す情報が含まれていない場合、

  • 「プログラム中の日付・時刻は、すべて日本標準時(JST)である」
  • 「プログラム中の日付・時刻は、すべて協定標準時(UTC)である」

といった前提で処理することが多いと書きましたが、タイムゾーン無し(naive)の datetime オブジェクトに関しても同じような感じで

  • naive なオブジェクトで示される日時は、JST の日時であるとみなして、aware なオブジェクトに変換する
  • naive なオブジェクトで示される日時は、UTC の日時であるとみなして、aware なオブジェクトに変換する

といった形で処理することが出来ます。具体例を見てみましょう。

import datetime

dt_naive2 = datetime.datetime.strptime('2022-05-26', '%Y-%m-%d')
print(dt_naive2) # 2022-05-26 00:00:00

tz_jst = datetime.timezone(datetime.timedelta(hours=9))
dt_aware_from_naive_jst = dt_naive2.replace(tzinfo=tz_jst)
dt_aware_from_naive_utc = dt_naive2.replace(tzinfo=datetime.timezone.utc)

print(dt_aware_from_naive_jst) # 2022-05-26 00:00:00+09:00
# ↑ unix timestamp だと 1653490800

print(dt_aware_from_naive_utc) # 2022-05-26 00:00:00+00:00
# ↑ unix timestamp だと 1653523200

dt_aware_from_naive_jstdt_aware_from_naive_utc は、同じ 2022-05-26 という文字列から生成されたものですが、それら2つのオブジェクトが指し示す日時は異なります。(コードのコメントに記載した unix timestamp の値に注目。)

aware な datetime オブジェクトを、別の timezone の datetime オブジェクトに変換する

1つ前の項と似ていますが、今回は「2022年5月26日 午前11:30(日本標準時)」を示す datetime オブジェクトを 「2022年5月26日 午前02:30(UTC)」の datetime オブジェクトに変換するというものです。

import datetime

tz_jst = datetime.timezone(datetime.timedelta(hours=9))

dt_aware5_jst = datetime.datetime(2022, 5, 26, 11, 30, 0, 0, tz_jst)
print(dt_aware5_jst) # 2022-05-26 11:30:00+09:00

dt_aware5_utc = dt_aware5_jst.astimezone(datetime.timezone.utc)
print(dt_aware5_utc) # 2022-05-26 02:30:00+00:00

dt_aware5_jstdt_aware5_utc は、print してみると別の文字列ですが、実際には同じ日時を指し示しています。

aware なオブジェクトを naive なオブジェクトに変換

naive → aware の場合と同じく replace メソッドを使います。

import datetime

tz_jst = datetime.timezone(datetime.timedelta(hours=9))

dt_aware5_jst = datetime.datetime(2022, 5, 26, 11, 30, 0, 0, tz_jst)
print(dt_aware5_jst) # 2022-05-26 11:30:00+09:00
dt_aware5_utc = dt_aware5_jst.astimezone(datetime.timezone.utc)
print(dt_aware5_utc) # 2022-05-26 02:30:00+00:00

dt_naive_from_jst = dt_aware5_jst.replace(tzinfo=None)
print(dt_naive_from_jst) # 2022-05-26 11:30:00

dt_naive_from_utc = dt_aware5_utc.replace(tzinfo=None)
print(dt_naive_from_utc) # 2022-05-26 02:30:00

pytz

上の方で書いたとおり、標準ライブラリである datetime.timezone では夏時間を簡単に扱う方法がありません。そのため、夏時間を扱いたい場合には pytz などの外部ライブラリを使用します。

冒頭で「軽く書いていきます」と書いたもののずいぶん長くなってしまったので、pytz については別の機会に書こうと思います。

まとめ

プログラム言語などで日時を保持する方法は主に3通りありますが、unix timestamp 以外の方法を使う場合には、タイムゾーンの有無・どのタイムゾーンなのか、に注意する必要があります。

Python の標準ライブラリである datetime ではタイムゾーン有り・無し両方を扱うことができ、タイムゾーン有りのオブジェクト、タイムゾーン無しのオブジェクトは、それぞれ aware なオブジェクト、naive なオブジェクトと呼ばれます。aware なオブジェクトと naive なオブジェクトは簡単に変換可能です。

datetime における制限事項としては、datetime ではタイムゾーンを UTC との時差でしか扱うことが出来ないため、夏時間を簡単に扱うことが出来ないという点が挙げられます。これを解消するためには、pytz などのライブラリーを使う必要があります。

Tags

← 前の投稿

Spark での regex_replace

次の投稿 →

[AWS] EventBridge Rules による ECS Scheduled Task はエラー時リトライできない

コメントを残す