Python datetime徹底解説:日時・時刻・時間差の扱い方を網羅的に理解する
Pythonで日付や時刻を扱う際、標準ライブラリであるdatetime
モジュールは非常に強力で便利なツールです。しかし、その機能は多岐にわたり、特にタイムゾーンや夏時間(DST)の扱いは複雑になりがちです。
この記事では、datetime
モジュールの基本的な使い方から、主要なクラス(date
, time
, datetime
, timedelta
, tzinfo
)の詳細、オブジェクトの生成、属性へのアクセス、日付・時刻の計算、文字列との変換(strftime
, strptime
)、そして多くの開発者がつまずきやすいタイムゾーンの扱いに至るまでを、徹底的に解説します。約5000語を目標とし、各機能について豊富なコード例を交えながら、読者がdatetime
モジュールを自信を持って使いこなせるようになることを目指します。
はじめに:なぜ日時処理は難しいのか?
コンピューターの世界で日付や時刻を正確に扱うことは、思った以上に複雑です。その主な理由として、以下の点が挙げられます。
- タイムゾーン: 地球上には多くのタイムゾーンが存在し、場所によって時刻が異なります。また、同じ場所でも歴史的な経緯や政治的な決定によってタイムゾーンが変わることがあります。
- 夏時間(サマータイム/DST – Daylight Saving Time): 多くの国や地域で、特定の期間に時刻を1時間進める夏時間が採用されています。夏時間の開始・終了日は毎年異なり、地域によってルールも異なります。これにより、存在しない時刻や重複する時刻が発生します。
- うるう年・うるう秒: 暦と地球の公転・自転のずれを調整するため、数年に一度「うるう年」が挿入されます。また、地球の自転速度の変動を調整するため、ごくまれに「うるう秒」が挿入されることがあります。これらも正確な日時計算を複雑にします。
- 暦の違い: 世界にはグレゴリオ暦以外にも様々な暦が存在しますが、プログラミングで主に扱うのはグレゴリオ暦です。それでも、特定の歴史的な日付を扱う際には暦の違いを考慮する必要が出てくることがあります。
Pythonのdatetime
モジュールは、これらの複雑さの一部(特にタイムゾーンと夏時間)をある程度抽象化し、開発者がより簡単に日時を扱えるように設計されています。
datetimeモジュールの概要と主要クラス
datetime
モジュールを使用するには、まずこれをインポートします。
python
import datetime
datetime
モジュール内で定義されている主要なクラスは以下の通りです。
date
: 年、月、日のみを扱うクラスです。時刻情報は持ちません。time
: 時、分、秒、マイクロ秒、そしてオプションでタイムゾーン情報(tzinfo
)を扱うクラスです。日付情報は持ちません。datetime
:date
クラスとtime
クラスの機能を組み合わせ、年、月、日、時、分、秒、マイクロ秒、そしてオプションでタイムゾーン情報(tzinfo
)を扱うクラスです。このクラスが最も頻繁に使用されます。timedelta
: 二つのdate
、time
、またはdatetime
オブジェクト間の時間差(期間)を表すクラスです。時間の加算や減算にも使用されます。tzinfo
: タイムゾーン情報の抽象基底クラスです。このクラス自体を直接インスタンス化することは通常ありません。具体的なタイムゾーンを扱うためには、このクラスを継承したクラスを使用するか、後述する外部ライブラリやPython 3.9以降の標準ライブラリzoneinfo
を使用します。
これらのクラスのオブジェクトは、イミュータブル(immutable)です。つまり、一度生成されたオブジェクトの内容(年、月、日など)を変更することはできません。内容を変更したい場合は、新しいオブジェクトを生成する必要があります。
1. date
クラス:日付のみを扱う
date
クラスは、年、月、日のみを表現します。
date
オブジェクトの生成
コンストラクタを使用して特定の年月日を持つdate
オブジェクトを生成できます。
“`python
date(年, 月, 日)
my_date = datetime.date(2023, 10, 27)
print(my_date)
出力例: 2023-10-27
“`
コンストラクタの引数には有効な日付を指定する必要があります。無効な日付(例: 2月30日)を指定するとValueError
が発生します。
また、いくつかのファクトリメソッド(クラスメソッド)を使ってdate
オブジェクトを生成することもできます。
-
date.today()
: ローカルコンピュータの「今日」の日付を取得します。“`python
today = datetime.date.today()
print(today)出力例: 2023-10-27 (実行日によって変わります)
“`
-
date.fromtimestamp(timestamp)
: タイムスタンプ(エポックからの経過秒数)からローカルタイムゾーンの日付を取得します。“`python
import time現在のタイムスタンプを取得
ts = time.time()
date_from_ts = datetime.date.fromtimestamp(ts)
print(f”タイムスタンプ {ts} からの日付: {date_from_ts}”)出力例: タイムスタンプ 1698375600.123456 からの日付: 2023-10-27
“`
-
date.fromordinal(ordinal)
: グレゴリオ暦の序数(西暦1年1月1日を1とする連続した日数)から日付を取得します。“`python
2023年10月27日の序数を取得
ordinal_date = datetime.date(2023, 10, 27)
print(f”2023-10-27 の序数: {ordinal_date.toordinal()}”)出力例: 2023-10-27 の序数: 738820
序数から日付を生成
date_from_ordinal = datetime.date.fromordinal(738820)
print(f”序数 738820 からの日付: {date_from_ordinal}”)出力例: 序数 738820 からの日付: 2023-10-27
“`
date
オブジェクトの属性とメソッド
date
オブジェクトからは、年、月、日などの情報を取り出すことができます。
“`python
my_date = datetime.date(2023, 10, 27)
print(f”年: {my_date.year}”) # 出力: 年: 2023
print(f”月: {my_date.month}”) # 出力: 月: 10
print(f”日: {my_date.day}”) # 出力: 日: 27
曜日を取得 (月曜日=0, 日曜日=6)
print(f”曜日 (weekday): {my_date.weekday()}”) # 出力: 曜日 (weekday): 4 (金曜日)
ISO曜日を取得 (月曜日=1, 日曜日=7)
print(f”ISO曜日 (isoweekday): {my_date.isoweekday()}”) # 出力: ISO曜日 (isoweekday): 5 (金曜日)
ISOカレンダー情報 (年, 週番号, 曜日)
print(f”ISOカレンダー: {my_date.isocalendar()}”) # 出力例: ISOカレンダー: datetime.IsoCalendarDate(year=2023, week=43, weekday=5)
年の何日目かを取得
print(f”年の何日目か: {my_date.toordinal() – datetime.date(my_date.year, 1, 1).toordinal() + 1}”)
出力例: 年の何日目か: 300
指定した属性を変更した新しいdateオブジェクトを生成 (replaceは元オブジェクトを変更せず新しいオブジェクトを返す)
new_date = my_date.replace(year=2024, month=11)
print(f”元のdate: {my_date}, 新しいdate: {new_date}”)
出力: 元のdate: 2023-10-27, 新しいdate: 2024-11-27
“`
2. time
クラス:時刻のみを扱う
time
クラスは、時、分、秒、マイクロ秒、そしてオプションでタイムゾーン情報を表現します。日付情報は持ちません。
time
オブジェクトの生成
コンストラクタを使用して特定の時刻を持つtime
オブジェクトを生成できます。
“`python
time(時, 分, 秒, マイクロ秒, tzinfo)
my_time = datetime.time(10, 30, 0, 123456)
print(my_time)
出力例: 10:30:00.123456
引数を省略した場合、デフォルト値は0またはNoneになります
noon = datetime.time(12) # 12:00:00.000000
print(noon)
出力: 12:00:00
タイムゾーン情報付きのtimeオブジェクト (tzinfoについては後述)
import zoneinfo # Python 3.9+
tokyo_tz = zoneinfo.ZoneInfo(“Asia/Tokyo”)
time_aware = datetime.time(10, 30, 0, tzinfo=tokyo_tz)
print(time_aware)
出力例: 10:30:00+09:00
“`
time
オブジェクトは日付情報を持たないため、date.today()
のようなメソッドはありません。
time
オブジェクトの属性とメソッド
time
オブジェクトからは、時、分、秒、マイクロ秒、タイムゾーン情報を取り出すことができます。
“`python
my_time = datetime.time(10, 30, 45, 123456)
print(f”時: {my_time.hour}”) # 出力: 時: 10
print(f”分: {my_time.minute}”) # 出力: 分: 30
print(f”秒: {my_time.second}”) # 出力: 秒: 45
print(f”マイクロ秒: {my_time.microsecond}”) # 出力: マイクロ秒: 123456
print(f”タイムゾーン情報: {my_time.tzinfo}”) # 出力: タイムゾーン情報: None (タイムゾーンなし)
指定した属性を変更した新しいtimeオブジェクトを生成
new_time = my_time.replace(hour=15, minute=0)
print(f”元のtime: {my_time}, 新しいtime: {new_time}”)
出力: 元のtime: 10:30:45.123456, 新しいtime: 15:00:45.123456
タイムゾーン情報があるかどうかの確認
print(f”タイムゾーン情報があるか (is_aware): {my_time.tzinfo is not None}”) # 出力: タイムゾーン情報があるか (is_aware): False
print(f”タイムゾーン情報があるか (is_aware): {time_aware.tzinfo is not None}”) # 出力: タイムゾーン情報があるか (is_aware): True (上のtime_awareオブジェクトがある場合)
“`
3. datetime
クラス:日付と時刻を扱う
datetime
クラスは、年、月、日、時、分、秒、マイクロ秒、そしてオプションでタイムゾーン情報をすべて扱います。これは最も多機能で、多くの場合に使用されるクラスです。
datetime
オブジェクトの生成
コンストラクタを使用して特定の年月日時刻を持つdatetime
オブジェクトを生成できます。最低限、年、月、日を指定する必要があります。
“`python
datetime(年, 月, 日, 時=0, 分=0, 秒=0, マイクロ秒=0, tzinfo=None)
dt = datetime.datetime(2023, 10, 27, 10, 30, 0)
print(dt)
出力例: 2023-10-27 10:30:00
マイクロ秒まで指定
dt_full = datetime.datetime(2023, 10, 27, 10, 30, 45, 123456)
print(dt_full)
出力例: 2023-10-27 10:30:45.123456
タイムゾーン情報付きのdatetimeオブジェクト (tzinfoについては後述)
import zoneinfo # Python 3.9+
utc_tz = zoneinfo.ZoneInfo(“UTC”)
dt_aware = datetime.datetime(2023, 10, 27, 10, 30, 0, tzinfo=utc_tz)
print(dt_aware)
出力例: 2023-10-27 10:30:00+00:00
“`
datetime
クラスにも、便利なファクトリメソッド(クラスメソッド)がいくつかあります。
-
datetime.now(tz=None)
: 現在のローカル日時を取得します。引数tz
にタイムゾーンオブジェクトを指定すると、そのタイムゾーンでの現在日時(awareなオブジェクト)を取得できます。tz
を省略すると、タイムゾーン情報を持たないnaive(ナイーブ)なオブジェクトを返します。“`python
Naive (タイムゾーン情報なし)
now_naive = datetime.datetime.now()
print(f”現在のローカル日時 (Naive): {now_naive}”)出力例: 現在のローカル日時 (Naive): 2023-10-27 18:30:00.123456 (日本時間の場合)
Aware (タイムゾーン情報あり) – Python 3.6以降
import zoneinfo # Python 3.9+
tokyo_tz = zoneinfo.ZoneInfo(“Asia/Tokyo”)
now_aware = datetime.datetime.now(tokyo_tz)
print(f”現在の東京日時 (Aware): {now_aware}”)
出力例: 現在の東京日時 (Aware): 2023-10-27 18:30:00.123456+09:00
``
datetime.now()`(引数なし)は、ローカルシステムの現在の時刻を返しますが、そのタイムゾーン情報を持たないNaiveなオブジェクトです。複数のタイムゾーンを扱う場合や、夏時間を考慮する必要がある場合は、後述のタイムゾーン対応を必ず行う必要があります。
**重要:** -
datetime.utcnow()
: 現在のUTC(協定世界時)を取得します。このメソッドは、Python 3.11以前ではNaiveなオブジェクトを返します。Python 3.11以降では非推奨となり、代わりにdatetime.now(datetime.UTC)
(Python 3.11で追加されたUTCタイムゾーン定数)を使用することが推奨されています。“`python
Python 3.11以前 (Naive UTC)
utc_naive = datetime.datetime.utcnow()
print(f”現在のUTC (Naive): {utc_naive}”)
出力例: 現在のUTC (Naive): 2023-10-27 09:30:00.123456 (日本時間から9時間引いた時刻)
Python 3.11以降 (Aware UTC)
utc_aware = datetime.datetime.now(datetime.UTC)
print(f”現在のUTC (Aware): {utc_aware}”)出力例: 現在のUTC (Aware): 2023-10-27 09:30:00.123456+00:00
“`
-
datetime.today()
:datetime.now()
(引数なし)とほぼ同じで、現在のローカル日時をNaiveオブジェクトとして返します。datetime.now()
の方が推奨されます。“`python
today_dt = datetime.datetime.today()
print(f”現在のローカル日時 (today): {today_dt}”)出力例: 現在のローカル日時 (today): 2023-10-27 18:30:00.123456
“`
-
datetime.fromtimestamp(timestamp, tz=None)
: タイムスタンプからローカルタイムゾーンの日時を取得します(Naiveオブジェクト)。Python 3.6以降では、オプションのtz
引数にタイムゾーンオブジェクトを指定することで、そのタイムゾーンのAwareオブジェクトを取得できます。“`python
import time
ts = time.time() # 現在のタイムスタンプNaive (ローカルタイムゾーン)
dt_from_ts_naive = datetime.datetime.fromtimestamp(ts)
print(f”タイムスタンプ {ts} からのローカル日時 (Naive): {dt_from_ts_naive}”)出力例: タイムスタンプ 1698375600.123456 からのローカル日時 (Naive): 2023-10-27 18:30:00.123456
Aware (指定したタイムゾーン) – Python 3.6以降
import zoneinfo # Python 3.9+
utc_tz = zoneinfo.ZoneInfo(“UTC”)
dt_from_ts_aware = datetime.datetime.fromtimestamp(ts, tz=utc_tz)
print(f”タイムスタンプ {ts} からのUTC日時 (Aware): {dt_from_ts_aware}”)
出力例: タイムスタンプ 1698375600.123456 からのUTC日時 (Aware): 2023-10-27 09:30:00.123456+00:00
“`
-
datetime.utcfromtimestamp(timestamp)
: タイムスタンプからUTCの日時を取得しますが、こちらもPython 3.11以前ではNaiveオブジェクトを返します。Python 3.12以降では非推奨となり、datetime.fromtimestamp(ts, tz=datetime.UTC)
の使用が推奨されます。“`python
Python 3.11以前 (Naive UTC)
dt_utc_from_ts_naive = datetime.datetime.utcfromtimestamp(ts)
print(f”タイムスタンプ {ts} からのUTC日時 (Naive): {dt_utc_from_ts_naive}”)
出力例: タイムスタンプ 1698375600.123456 からのUTC日時 (Naive): 2023-10-27 09:30:00.123456
“`
NaiveオブジェクトとAwareオブジェクトの重要性
datetime
オブジェクトには、「タイムゾーン情報を持っているかいないか」という重要な区別があります。
- Naiveオブジェクト: タイムゾーン情報(
tzinfo
属性がNone
)を持たないオブジェクト。これは「2023年10月27日 10時30分」のような絶対的な日時を表しますが、それが「どこの」10時30分なのかが不明確です。ローカルシステムのタイムゾーンだと見なされることが多いですが、これは保証されていませんし、複数のタイムゾーンや夏時間を扱う場合には不十分です。 - Awareオブジェクト: タイムゾーン情報(
tzinfo
属性がNone
ではない)を持つオブジェクト。これは「UTCの2023年10月27日 10時30分」や「東京時間の2023年10月27日 10時30分」のように、具体的な時点を明確に表します。異なるタイムゾーン間での変換や、夏時間を正しく考慮した計算が可能です。
複数のタイムゾーンを扱ったり、過去や未来の日時を扱ったりする場合は、必ずAwareオブジェクトを使用するように心がけてください。Naiveオブジェクトの使用は、単一のシステム内で現在のローカル日時だけを扱うような限定的なケースにとどめるべきです。
datetime
オブジェクトの属性とメソッド
datetime
オブジェクトは、date
オブジェクトとtime
オブジェクトの属性とメソッドを併せ持ちます。
“`python
dt = datetime.datetime(2023, 10, 27, 10, 30, 45, 123456)
Date部分の属性
print(f”年: {dt.year}”) # 出力: 年: 2023
print(f”月: {dt.month}”) # 出力: 月: 10
print(f”日: {dt.day}”) # 出力: 日: 27
print(f”曜日 (weekday): {dt.weekday()}”) # 出力: 曜日 (weekday): 4
Time部分の属性
print(f”時: {dt.hour}”) # 出力: 時: 10
print(f”分: {dt.minute}”) # 出力: 分: 30
print(f”秒: {dt.second}”) # 出力: 秒: 45
print(f”マイクロ秒: {dt.microsecond}”) # 出力: マイクロ秒: 123456
タイムゾーン情報
print(f”タイムゾーン情報: {dt.tzinfo}”) # 出力: タイムゾーン情報: None
Date部分のみを取得
date_part = dt.date()
print(f”日付部分: {date_part}”) # 出力: 日付部分: 2023-10-27
Time部分のみを取得 (tzinfoなし)
time_part = dt.time()
print(f”時刻部分 (tzinfoなし): {time_part}”) # 出力: 時刻部分 (tzinfoなし): 10:30:45.123456
Time部分のみを取得 (tzinfoあり) – 元のオブジェクトにtzinfoがあれば含まれる
import zoneinfo # Python 3.9+
tokyo_tz = zoneinfo.ZoneInfo(“Asia/Tokyo”)
dt_aware_tokyo = dt.replace(tzinfo=tokyo_tz)
time_part_aware = dt_aware_tokyo.timetz()
print(f”時刻部分 (tzinfoあり): {time_part_aware}”) # 出力例: 時刻部分 (tzinfoあり): 10:30:45.123456+09:00
指定した属性を変更した新しいdatetimeオブジェクトを生成
new_dt = dt.replace(year=2024, hour=15)
print(f”元のdatetime: {dt}, 新しいdatetime: {new_dt}”)
出力: 元のdatetime: 2023-10-27 10:30:45.123456, 新しいdatetime: 2024-10-27 15:30:45.123456
NaiveかAwareかの確認
print(f”Naiveか? {dt.tzinfo is None}”) # 出力: Naiveか? True
print(f”Awareか? {dt_aware_tokyo.tzinfo is not None}”) # 出力: Awareか? True (上のdt_aware_tokyoオブジェクトがある場合)
タイムスタンプへの変換 (Naiveオブジェクトはローカルタイムゾーンと見なされる)
timestamp_value = dt.timestamp()
print(f”タイムスタンプ: {timestamp_value}”)
出力例: タイムスタンプ: 1698376245.123456 (ローカルタイムゾーンでの値)
UTCタイムスタンプへの変換 (Awareオブジェクトの場合)
import zoneinfo # Python 3.9+
utc_tz = zoneinfo.ZoneInfo(“UTC”)
dt_aware_utc = dt.replace(tzinfo=utc_tz)
timestamp_utc = dt_aware_utc.timestamp()
print(f”UTCタイムスタンプ: {timestamp_utc}”)
出力例: UTCタイムスタンプ: 1698340245.123456 (+09:00 から +00:00 への変換)
astimezone() メソッド: タイムゾーン間の変換に必須 (Awareオブジェクトに対してのみ有効)
import zoneinfo # Python 3.9+
tokyo_tz = zoneinfo.ZoneInfo(“Asia/Tokyo”)
london_tz = zoneinfo.ZoneInfo(“Europe/London”)
dt_aware_tokyo = datetime.datetime(2023, 10, 27, 18, 30, 0, tzinfo=tokyo_tz)
print(f”東京時間: {dt_aware_tokyo}”)
london_dt = dt_aware_tokyo.astimezone(london_tz)
print(f”ロンドン時間: {london_dt}”)
出力例:
東京時間: 2023-10-27 18:30:00+09:00
ロンドン時間: 2023-10-27 10:30:00+01:00 (夏時間考慮)
Naiveオブジェクトに astimezone() を使うとエラーになる
naive_dt = datetime.datetime(2023, 10, 27, 18, 30, 0)
try:
naive_dt.astimezone(tokyo_tz)
except ValueError as e:
print(f”エラー: {e}”) # 出力: エラー: naive datetime is disallowed
“`
4. timedelta
クラス:時間差を表す
timedelta
クラスは、二つの日時間の差や、特定の日時に加算・減算する期間を表します。
timedelta
オブジェクトの生成
コンストラクタに様々な時間単位を指定して生成できます。内部的には、指定された期間は日、秒、マイクロ秒に変換されて格納されます。
“`python
timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
注意: seconds= はあくまで初期値設定。内部では日、秒、マイクロ秒で保持
delta_one_day = datetime.timedelta(days=1)
print(delta_one_day) # 出力: 1 day, 0:00:00
delta_two_weeks_three_days = datetime.timedelta(weeks=2, days=3)
print(delta_two_weeks_three_days) # 出力: 17 days, 0:00:00
delta_long = datetime.timedelta(days=50, hours=5, minutes=30)
print(delta_long) # 出力: 50 days, 5:30:00
delta_short = datetime.timedelta(seconds=10, microseconds=100)
print(delta_short) # 出力: 0:00:10.000100
“`
timedelta
の属性
timedelta
オブジェクトは、内部的に保持している日、秒、マイクロ秒の情報を属性として持ちます。
“`python
delta = datetime.timedelta(days=50, hours=5, minutes=30, seconds=15, microseconds=123456)
print(f”日: {delta.days}”) # 出力: 日: 50
hoursやminutesの引数で指定した値ではなく、残りの時間部分が秒数とマイクロ秒数に変換されて格納される
print(f”秒: {delta.seconds}”) # 出力: 秒: 19815 (5時間30分15秒 -> 53600 + 3060 + 15 = 18000 + 1800 + 15 = 19815)
print(f”マイクロ秒: {delta.microseconds}”) # 出力: マイクロ秒: 123456
全体の秒数を取得
print(f”全体の秒数 (total_seconds): {delta.total_seconds()}”) # 出力: 全体の秒数 (total_seconds): 4339815.123456
“`
timedelta
を使った日時計算
timedelta
オブジェクトは、date
オブジェクトやdatetime
オブジェクトとの間で加算・減算に使用できます。
“`python
today = datetime.date.today()
tomorrow = today + datetime.timedelta(days=1)
print(f”今日: {today}, 明日: {tomorrow}”)
出力例: 今日: 2023-10-27, 明日: 2023-10-28
dt = datetime.datetime.now()
next_week = dt + datetime.timedelta(weeks=1)
print(f”現在: {dt}, 一週間後: {next_week}”)
出力例: 現在: 2023-10-27 18:30:00.123456, 一週間後: 2023-11-03 18:30:00.123456
past_dt = dt – datetime.timedelta(hours=3)
print(f”現在: {dt}, 3時間前: {past_dt}”)
出力例: 現在: 2023-10-27 18:30:00.123456, 3時間前: 2023-10-27 15:30:00.123456
二つのdatetimeオブジェクト間の差はtimedeltaオブジェクトになる
dt1 = datetime.datetime(2023, 10, 27, 10, 0, 0)
dt2 = datetime.datetime(2023, 10, 28, 12, 30, 0)
difference = dt2 – dt1
print(f”{dt2} – {dt1} = {difference}”)
print(f”差の日数: {difference.days}”)
print(f”差の合計秒数: {difference.total_seconds()}”)
出力:
2023-10-28 12:30:00 – 2023-10-27 10:00:00 = 1 day, 2:30:00
差の日数: 1
差の合計秒数: 95400.0 (1日 + 2時間30分 = 86400 + 7200 + 1800 = 95400)
timedeltaオブジェクト同士の計算
delta1 = datetime.timedelta(days=1)
delta2 = datetime.timedelta(hours=12)
sum_delta = delta1 + delta2
print(f”{delta1} + {delta2} = {sum_delta}”) # 出力: 1 day, 12:00:00
timedeltaのスケーリング
scaled_delta = delta1 * 3
print(f”{delta1} * 3 = {scaled_delta}”) # 出力: 3 days, 0:00:00
“`
5. tzinfo
クラス:タイムゾーン情報(抽象基底クラス)
tzinfo
クラスは、タイムゾーン情報の抽象基底クラスです。このクラス自体はタイムゾーンに関する具体的なルール(UTCオフセット、夏時間の開始/終了など)を持っていません。具体的なタイムゾーン情報を扱うためには、tzinfo
を継承したクラスを使用する必要があります。
Pythonの標準ライブラリには、UTC(協定世界時)を表すシンプルなtimezone
クラスと、Python 3.9で追加された本格的なタイムゾーンデータベース(tzdata
)を扱うzoneinfo
モジュールがあります。それ以前のバージョンや、より網羅的なタイムゾーン情報を扱いたい場合は、pytz
のような外部ライブラリが広く使われてきました。
datetime.timezone
クラス(Python 3.2以降)
UTCからの固定オフセットを持つシンプルなタイムゾーンを表すクラスです。夏時間などの複雑なルールは扱えません。UTCそのものを表す定数datetime.UTC
(Python 3.11以降)がよく使われます。
“`python
UTCを表すAwareオブジェクト (Python 3.11以降推奨)
dt_utc_aware = datetime.datetime(2023, 10, 27, 10, 30, 0, tzinfo=datetime.UTC)
print(dt_utc_aware)
出力例: 2023-10-27 10:30:00+00:00
固定オフセットを持つタイムゾーン (例えばJST +09:00)
ただし、夏時間がない固定オフセットのタイムゾーンのみを表現できる
UTCからの差をtimedeltaで指定
jst_offset = datetime.timedelta(hours=9)
jst_tz_fixed = datetime.timezone(jst_offset, name=’JST’)
dt_jst_fixed = datetime.datetime(2023, 10, 27, 10, 30, 0, tzinfo=jst_tz_fixed)
print(dt_jst_fixed)
出力例: 2023-10-27 10:30:00+09:00
``
datetime.timezone`は夏時間を考慮しないため、特定の固定オフセットを持つタイムゾーン(例: UTC, JSTなど夏時間のない地域)を表すのには使えますが、多くの地域で採用されている夏時間を持つタイムゾーン(例: CET/CEST, PST/PDTなど)を正確に表すことはできません。
zoneinfo
モジュール(Python 3.9以降)
zoneinfo
モジュールは、標準ライブラリでIANA Time Zone Database (tzdata) を使用するための本格的な機能を提供します。これにより、外部ライブラリに依存することなく、多くのタイムゾーンと夏時間を正しく扱うことができます。
利用するには、システムにtzdataがインストールされているか、あるいはtzdata
パッケージをインストールする必要があります。
bash
pip install tzdata
使用例:
“`python
import datetime
Python 3.9以降
try:
import zoneinfo
except ImportError:
print(“zoneinfoモジュールが見つかりません。tzdataパッケージをインストールしてください。”)
zoneinfo = None # zoneinfoが利用できない場合のフォールバック
if zoneinfo:
# タイムゾーンオブジェクトを取得
tokyo_tz = zoneinfo.ZoneInfo(“Asia/Tokyo”)
london_tz = zoneinfo.ZoneInfo(“Europe/London”)
utc_tz = zoneinfo.ZoneInfo(“UTC”) # または datetime.UTC (Python 3.11+)
# Aware datetimeオブジェクトの作成
dt_tokyo = datetime.datetime(2023, 10, 27, 18, 30, 0, tzinfo=tokyo_tz)
print(f"東京時間 (Aware): {dt_tokyo}")
# 出力例: 東京時間 (Aware): 2023-10-27 18:30:00+09:00
# タイムゾーン間の変換
dt_london = dt_tokyo.astimezone(london_tz)
print(f"ロンドン時間 (Aware): {dt_london}")
# 出力例: ロンドン時間 (Aware): 2023-10-27 10:30:00+01:00
dt_utc = dt_tokyo.astimezone(utc_tz)
print(f"UTC (Aware): {dt_utc}")
# 出力例: UTC (Aware): 2023-10-27 09:30:00+00:00
# Naiveオブジェクトを指定したタイムゾーンのAwareオブジェクトに変換する場合
# Naiveオブジェクトがどのタイムゾーンで解釈されるべきかを明示的に指定する必要がある
# この場合、replace() メソッドは使えない (単にtzinfo属性を設定するだけで、オフセット調整を行わないため)
naive_dt = datetime.datetime(2023, 10, 27, 10, 30, 0)
print(f"Naive datetime: {naive_dt}")
# Naiveオブジェクトを「これはUTCだ」と解釈してAwareにする
# tz.localize(dt) のようなメソッドは zoneinfo には無い (pytz にはある)
# 代わりに、astimezone(tz) のソースを None とすることで Naive -> Aware にできる (Python 3.6+)
# ただし、これはローカルシステムタイムゾーンを想定した変換になるので推奨されない
# より安全なのは、最初から tzinfo を指定して datetime オブジェクトを作るか、
# Naiveオブジェクトを生成した後、tzinfo を replace する (ただしこれはオフセット調整しない)
# または、datetime.fromtimestamp(dt.timestamp(), tz=tz) を使う方法
# Naiveオブジェクトを特定のタイムゾーンのAwareオブジェクトに変換する最もクリーンな方法は、
# その Naive オブジェクトがどのタイムゾーンの時刻を意図しているかを明示することです。
# 例えば、naive_dt が東京時間を意図している場合:
dt_tokyo_from_naive = naive_dt.replace(tzinfo=tokyo_tz) # これは単にtzinfoを設定するだけで、時間は変わらない
print(f"Naive を東京時間として扱った Aware: {dt_tokyo_from_naive}")
# 出力例: Naive を東京時間として扱った Aware: 2023-10-27 10:30:00+09:00 (時間は10:30のまま、オフセットが付与されただけ)
# タイムゾーン情報を持たない時刻文字列を特定のタイムゾーンのAwareオブジェクトにパースする際は、strptime と replace/astimezone を組み合わせる必要がある
# 詳細後述
“`
zoneinfo.ZoneInfo(name)
でタイムゾーン名(例: “Asia/Tokyo”, “America/New_York”, “Europe/London”など)を指定してタイムゾーンオブジェクトを取得します。有効なタイムゾーン名のリストは、通常tzdataパッケージに含まれています。
pytz
ライブラリ(外部ライブラリ)
Python 3.9より前では、zoneinfo
モジュールが存在しなかったため、タイムゾーンを正確に扱うためのデファクトスタンダードとしてpytz
ライブラリが広く使われてきました。pytz
もIANA Time Zone Databaseを使用しており、夏時間などを正しく扱えます。
利用するにはインストールが必要です。
bash
pip install pytz
使用例:
“`python
import datetime
import pytz # 外部ライブラリ
try:
# タイムゾーンオブジェクトを取得
tokyo_tz_pytz = pytz.timezone(“Asia/Tokyo”)
london_tz_pytz = pytz.timezone(“Europe/London”)
utc_tz_pytz = pytz.utc # pytzにはUTCを表す定数がある
except pytz.UnknownTimeZoneError as e:
print(f”指定されたタイムゾーンが見つかりません: {e}”)
Aware datetimeオブジェクトの作成
pytzのタイムゾーンオブジェクトは、datetimeオブジェクトに直接tzinfoとして渡すこともできるが、
pytzの recommended approach は、datetime.datetime(y, m, d, …) で Naive オブジェクトを作成し、
タイムゾーンオブジェクトの localize() メソッドを使って Aware オブジェクトに変換することです。
これにより、夏時間の境界線にある曖昧な時刻や存在しない時刻を正しく扱えます。
naive_dt = datetime.datetime(2023, 10, 27, 18, 30, 0)
dt_tokyo_pytz = tokyo_tz_pytz.localize(naive_dt)
print(f”東京時間 (Aware, pytz): {dt_tokyo_pytz}”)
出力例: 東京時間 (Aware, pytz): 2023-10-27 18:30:00+09:00
タイムゾーン間の変換
dt_london_pytz = dt_tokyo_pytz.astimezone(london_tz_pytz)
print(f”ロンドン時間 (Aware, pytz): {dt_london_pytz}”)
出力例: ロンドン時間 (Aware, pytz): 2023-10-27 10:30:00+01:00
dt_utc_pytz = dt_tokyo_pytz.astimezone(utc_tz_pytz)
print(f”UTC (Aware, pytz): {dt_utc_pytz}”)
出力例: UTC (Aware, pytz): 2023-10-27 09:30:00+00:00
Naiveオブジェクトを「これはUTCだ」と解釈してAwareにする場合
naive_dt_utc_intent = datetime.datetime(2023, 10, 27, 9, 30, 0) # UTCでの9:30を意図
dt_utc_from_naive_pytz = pytz.utc.localize(naive_dt_utc_intent)
print(f”Naive を UTC として扱った Aware (pytz): {dt_utc_from_naive_pytz}”)
出力例: Naive を UTC として扱った Aware (pytz): 2023-10-27 09:30:00+00:00
“`
pytz
のlocalize()
メソッドは、Naiveオブジェクトを指定したタイムゾーンのAwareオブジェクトに変換する際に、夏時間などを考慮してオフセットを正しく設定します。一方、datetime
標準ライブラリのreplace(tzinfo=...)
は単にtzinfo属性を設定するだけで、時刻のオフセット調整は行いません。これが、pytz
が広く推奨されてきた理由の一つです。
Python 3.9以降であれば、標準ライブラリのzoneinfo
モジュールが利用可能です。zoneinfo
のdatetime.datetime(..., tzinfo=ZoneInfo(...))
コンストラクタやreplace(tzinfo=ZoneInfo(...))
メソッドは、pytz.localize()
と同様に夏時間などを考慮してAwareオブジェクトを生成します(ただし、夏時間の境界線にある曖昧/存在しない時刻の扱いに若干の違いがあります。fold
属性については後述)。新しいコードではzoneinfo
を使うのが良いでしょう。
Naiveオブジェクト vs. Awareオブジェクト 再強調
タイムゾーンに関する処理を行う上で最も重要なことは、常にNaiveオブジェクトとAwareオブジェクトを区別し、適切に扱うことです。
- Naiveオブジェクト: 「どこの」時刻か不明確。システム内部やログで一貫してUTC(またはローカルタイム)で記録する場合以外は避けるべきです。特に、ネットワーク経由で日時を受け渡しする場合や、複数のユーザー/システムの時刻を扱う場合は危険です。
- Awareオブジェクト: タイムゾーン情報を持つため、特定の「時点」を明確に指し示します。異なるタイムゾーン間での比較や変換、夏時間を考慮した計算が可能です。
原則として、外部とのインターフェース(ファイル入出力、データベース、ネットワーク通信など)で日時を扱う場合は、必ずAwareオブジェクトに変換して処理を行い、内部ではUTCのAwareオブジェクトで管理するのが安全な方法とされています。表示する直前にユーザーのタイムゾーンに変換するのが一般的です。
6. 文字列との変換:strftime
とstrptime
日時オブジェクトと文字列の間で変換を行うには、strftime
メソッドとstrptime
クラスメソッドを使用します。
strftime(format)
:date
,time
,datetime
オブジェクトを、指定したフォーマット文字列に従って文字列に変換します。strptime(date_string, format)
: 指定したフォーマット文字列に従って、日時文字列をdatetime
オブジェクトにパース(解析)します。
フォーマット文字列では、%Y
、%m
などのフォーマットコードを使用して、年、月、日、時、分、秒などのどの部分をどのように表示するかを指定します。
主要なフォーマットコード
コード | 意味 | 例 (2023年10月27日 10時30分0秒) | 備考 |
---|---|---|---|
%Y |
4桁の年 | 2023 | |
%y |
2桁の年 (00-99) | 23 | 1900年代以外では意図しない結果になることも |
%m |
0埋めした月 (01-12) | 10 | |
%B |
月の名称 (Locale依存) | October (en_US), 10月 (ja_JP) | |
%b |
月の略称 (Locale依存) | Oct (en_US), 10月 (ja_JP) | |
%d |
0埋めした日 (01-31) | 27 | |
%j |
年の何日目か (001-366) | 300 | |
%w |
曜日 (0-6, 日曜=0) | 5 | |
%A |
曜日の名称 (Locale依存) | Friday (en_US), 金曜日 (ja_JP) | |
%a |
曜日の略称 (Locale依存) | Fri (en_US), 金 (ja_JP) | |
%H |
時 (24時間表記, 00-23) | 10 | |
%I |
時 (12時間表記, 01-12) | 10 | |
%p |
AM/PM (Locale依存) | AM/PM | %I とセットで使用 |
%M |
分 (00-59) | 30 | |
%S |
秒 (00-61) | 00 | うるう秒を考慮 (まれ) |
%f |
マイクロ秒 (000000-999999) | 000000 | strptime では全てをサポートしないことも |
%Z |
タイムゾーン名 (存在すれば) | JST, UTCなど | Awarenessオブジェクトのみ |
%z |
UTCからのオフセット (+HHMM or -HHMM) | +0900 | Awarenessオブジェクトのみ |
%c |
ロケールに合わせた日時 | Fri Oct 27 10:30:00 2023 | strptime では信頼性が低い |
%x |
ロケールに合わせた日付 | 10/27/23 | strptime では信頼性が低い |
%X |
ロケールに合わせた時刻 | 10:30:00 | strptime では信頼性が低い |
%% |
リテラルの % |
% |
Locale依存のコード(%A
, %a
, %B
, %b
, %p
, %c
, %x
, %X
)を使用する場合、実行環境のロケール設定によって結果が変わることがあります。特に日本語環境以外で実行する場合や、特定の言語での表示を保証したい場合は注意が必要です。
strftime
: オブジェクトから文字列へ
date
, time
, datetime
オブジェクトのメソッドとして呼び出します。
“`python
dt = datetime.datetime(2023, 10, 27, 10, 30, 45, 123456)
基本的なフォーマット
print(dt.strftime(“%Y-%m-%d %H:%M:%S”))
出力: 2023-10-27 10:30:45
マイクロ秒を含める
print(dt.strftime(“%Y-%m-%d %H:%M:%S.%f”))
出力: 2023-10-27 10:30:45.123456
12時間表記とAM/PM
print(dt.strftime(“%I:%M %p”))
出力: 10:30 AM
曜日、月名を含める (Locale依存)
環境によってはロケール設定が必要: import locale; locale.setlocale(locale.LC_TIME, ‘ja_JP.UTF-8’) など
print(dt.strftime(“%A, %B %d, %Y”))
出力例 (en_US): Friday, October 27, 2023
出力例 (ja_JP): 金曜日, 10月 27, 2023
ISO 8601形式
標準ライブラリの isoformat() メソッドを使うのが最も確実
print(dt.isoformat())
出力例: 2023-10-27T10:30:45.123456
タイムゾーン情報を含める (Awareオブジェクトの場合)
import zoneinfo # Python 3.9+
tokyo_tz = zoneinfo.ZoneInfo(“Asia/Tokyo”)
dt_aware = dt.replace(tzinfo=tokyo_tz)
print(dt_aware.strftime(“%Y-%m-%d %H:%M:%S %Z%z”))
出力例: 2023-10-27 10:30:45 JST+0900
%Z はシステムやタイムゾーン定義によって表示されない場合がある
%z はオフセットを正確に示すため、こちらを優先することが推奨されることが多い
“`
strptime
: 文字列からオブジェクトへ
datetime
クラスのメソッドとして呼び出します。引数として、パースしたい日時文字列と、その文字列のフォーマットを指定します。
“`python
date_string = “2023-10-27 10:30:00”
format_string = “%Y-%m-%d %H:%M:%S”
文字列から datetime オブジェクトへパース
try:
dt_parsed = datetime.datetime.strptime(date_string, format_string)
print(f”パースされた datetime: {dt_parsed}”)
# 出力: パースされた datetime: 2023-10-27 10:30:00
except ValueError as e:
print(f”パースエラー: {e}”)
フォーマットと文字列が一致しないと ValueError が発生する
wrong_string = “10/27/2023″
try:
datetime.datetime.strptime(wrong_string, format_string)
except ValueError as e:
print(f”パースエラー (フォーマット不一致): {e}”)
# 出力例: パースエラー (フォーマット不一致): time data ’10/27/2023′ does not match format ‘%Y-%m-%d %H:%M:%S’
マイクロ秒を含む文字列のパース
dt_micro_string = “2023-10-27 10:30:45.123456”
dt_micro_parsed = datetime.datetime.strptime(dt_micro_string, “%Y-%m-%d %H:%M:%S.%f”)
print(f”マイクロ秒を含むパース: {dt_micro_parsed}”)
出力: マイクロ秒を含むパース: 2023-10-27 10:30:45.123456
タイムゾーンオフセットを含む文字列のパース
‘%z’ コードは、+HHMM または -HHMM 形式のオフセットをパースできる
dt_offset_string = “2023-10-27 10:30:00+0900”
dt_offset_parsed = datetime.datetime.strptime(dt_offset_string, “%Y-%m-%d %H:%M:%S%z”)
print(f”オフセットを含むパース: {dt_offset_parsed}”)
出力: オフセットを含むパース: 2023-10-27 10:30:00+09:00 (Awareオブジェクトになる)
注意: %Z コードは、タイムゾーン名をパースできる場合とできない場合がある
例えば “EST”, “EDT”, “JST”, “UTC” などは多くの環境でパース可能だが、”Asia/Tokyo” のようなフルネームは通常パースできない
安全のため、可能な限り %z オフセットを使用するか、タイムゾーン名を別途扱う
dt_tzname_string = “2023-10-27 10:30:00 JST”
try:
dt_tzname_parsed = datetime.datetime.strptime(dt_tzname_string, “%Y-%m-%d %H:%M:%S %Z”)
print(f”タイムゾーン名を含むパース: {dt_tzname_parsed}”)
# 環境によってはエラーになるか、tzinfo=None になる可能性がある
# 多くの場合、Naiveオブジェクトが返される
except ValueError as e:
print(f”パースエラー (%Z使用時): {e}”) # エラーになる可能性が高い
Naive datetime string -> Aware datetime object
タイムゾーン情報を含まない文字列をパースした場合、得られるオブジェクトは Naive になる。
これを特定のタイムゾーンのAwareオブジェクトとして扱いたい場合、パース後に tzinfo を付与する。
例: “2023-10-27 18:30:00” は東京時間での時刻とする場合
naive_dt_string = “2023-10-27 18:30:00”
naive_dt_parsed = datetime.datetime.strptime(naive_dt_string, “%Y-%m-%d %H:%M:%S”)
print(f”パースされた Naive: {naive_dt_parsed}”)
これを東京時間のAwareオブジェクトに変換
import zoneinfo # Python 3.9+
tokyo_tz = zoneinfo.ZoneInfo(“Asia/Tokyo”)
dt_tokyo_aware = naive_dt_parsed.replace(tzinfo=tokyo_tz) # 単にtzinfoを設定
print(f”東京時間の Aware (replace): {dt_tokyo_aware}”)
出力例: 東京時間の Aware (replace): 2023-10-27 18:30:00+09:00
pytz を使う場合は localize() が便利
import pytz
tokyo_tz_pytz = pytz.timezone(“Asia/Tokyo”)
dt_tokyo_aware_pytz = tokyo_tz_pytz.localize(naive_dt_parsed) # 夏時間などを考慮してtzinfoとオフセットを設定
print(f”東京時間の Aware (pytz localize): {dt_tokyo_aware_pytz}”)
出力例: 東京時間の Aware (pytz localize): 2023-10-27 18:30:00+09:00
どちらの方法を使うかは、Pythonのバージョンやタイムゾーンライブラリの選択によるが、
Naive -> Aware の変換は、その Naive オブジェクトが「どのタイムゾーンの時刻として解釈されるべきか」を明確にして行うことが重要。
“`
strptime
は、入力文字列が指定されたフォーマットと厳密に一致しないとValueError
を発生させます。柔軟なパースが必要な場合は、他のライブラリ(例: dateutil.parser.parse
)を検討することもできます。
また、strptime
はデフォルトではNaiveオブジェクトを返します。タイムゾーン情報を含む文字列(例: "+0900"
)を%z
でパースした場合に限り、Awareオブジェクトが返されます。
よくある利用例
datetime
モジュールを使ったよくある処理の例をいくつか紹介します。
現在時刻の取得と表示
“`python
now = datetime.datetime.now() # Naive ローカル時間
print(f”現在のローカル時間 (Naive): {now}”)
print(now.strftime(“%Y年%m月%d日 %H時%M分%S秒”))
Aware UTCを取得 (Python 3.11以降)
utc_now = datetime.datetime.now(datetime.UTC)
print(f”現在のUTC (Aware): {utc_now}”)
現在の特定タイムゾーンの時間を取得 (Python 3.9以降 + tzdata)
try:
import zoneinfo
tokyo_tz = zoneinfo.ZoneInfo(“Asia/Tokyo”)
now_tokyo = datetime.datetime.now(tokyo_tz)
print(f”現在の東京時間 (Aware): {now_tokyo}”)
london_tz = zoneinfo.ZoneInfo("Europe/London")
now_london = datetime.datetime.now(london_tz)
print(f"現在のロンドン時間 (Aware): {now_london}")
except ImportError:
print(“zoneinfo または tzdata が利用できません。”)
“`
特定の日時の作成
“`python
2024年1月1日 0時0分0秒
new_year = datetime.datetime(2024, 1, 1)
print(new_year)
自分の誕生日
my_birthday = datetime.date(1990, 5, 15)
print(my_birthday)
特定のタイムゾーンでの日時 (Python 3.9以降 + tzdata)
import zoneinfo
paris_tz = zoneinfo.ZoneInfo(“Europe/Paris”)
dt_paris = datetime.datetime(2024, 7, 14, 14, 0, 0, tzinfo=paris_tz) # パリ祭
print(dt_paris)
“`
日付・時刻の加算・減算
“`python
today = datetime.date.today()
10日後
ten_days_later = today + datetime.timedelta(days=10)
print(f”今日から10日後: {ten_days_later}”)
3週間前
three_weeks_ago = today – datetime.timedelta(weeks=3)
print(f”今日から3週間前: {three_weeks_ago}”)
now = datetime.datetime.now()
5時間30分後
later = now + datetime.timedelta(hours=5, minutes=30)
print(f”現在から5時間30分後: {later}”)
2時間15分30秒前
earlier = now – datetime.timedelta(hours=2, minutes=15, seconds=30)
print(f”現在から2時間15分30秒前: {earlier}”)
“`
日時間の差の計算
“`python
dt1 = datetime.datetime(2023, 10, 27, 10, 0, 0)
dt2 = datetime.datetime(2023, 11, 15, 14, 30, 0)
time_difference = dt2 – dt1
print(f”{dt2} と {dt1} の差: {time_difference}”)
print(f”差の日数: {time_difference.days}”)
print(f”差の合計秒数: {time_difference.total_seconds()}”)
date1 = datetime.date(2023, 1, 1)
date2 = datetime.date(2024, 1, 1)
date_difference = date2 – date1
print(f”{date2} と {date1} の差: {date_difference}”)
print(f”差の日数: {date_difference.days}”)
“`
年齢の計算
“`python
from datetime import date, timedelta
def calculate_age(birth_date: date) -> int:
“””誕生日から現在の年齢を計算する”””
today = date.today()
# 今年の誕生日がまだ来ていなければ年齢は (今年 – 生まれた年 – 1)
# 今年の誕生日が既に過ぎているか今日であれば年齢は (今年 – 生まれた年)
# 今日 >= 今年の誕生日
# 年の差 (today.year – birth_date.year) から、今年の誕生日が来たか来てないかの1年を引く
age = today.year – birth_date.year – ((today.month, today.day) < (birth_date.month, birth_date.day))
return age
my_birthdate = date(1990, 5, 15)
age = calculate_age(my_birthdate)
print(f”私の生年月日: {my_birthdate}”)
print(f”現在の年齢: {age} 歳”)
例: 2024年5月14日に実行した場合 (誕生日前日) -> 33歳
例: 2024年5月15日に実行した場合 (誕生日当日) -> 34歳
例: 2024年5月16日に実行した場合 (誕生日翌日) -> 34歳
“`
特定の曜日を求める
“`python
def find_next_weekday(start_date: date, weekday: int) -> date:
“””
start_date 以降で、指定された曜日に最も近い日付を求める。
weekday: 0 (月曜日) – 6 (日曜日)
“””
current_weekday = start_date.weekday()
days_ahead = weekday – current_weekday
if days_ahead < 0:
days_ahead += 7 # 次の週へ
# もし開始日が既に目的の曜日であれば、次の週の同じ曜日を返す (もし開始日自身を含めたいなら days_ahead == 0 の判定を修正)
# ここでは開始日自身を含めない実装とする
if days_ahead == 0:
days_ahead = 7
return start_date + timedelta(days=days_ahead)
start_date = datetime.date(2023, 10, 27) # 金曜日 (weekday=4)
start_date 以降で最も近い次の月曜日 (weekday=0)
next_monday = find_next_weekday(start_date, 0)
print(f”{start_date} 以降で最も近い次の月曜日: {next_monday}”) # 2023-10-30
start_date 以降で最も近い次の土曜日 (weekday=5)
next_saturday = find_next_weekday(start_date, 5)
print(f”{start_date} 以降で最も近い次の土曜日: {next_saturday}”) # 2023-11-04 (開始日自身は含めないため)
“`
発展的なトピックと注意点
NaiveオブジェクトとAwareオブジェクトの比較
NaiveオブジェクトとAwareオブジェクトを比較しようとすると、以下のルールが適用されます。
- Naive == Naive: 時刻が完全に一致する場合のみTrue。
- Aware == Aware: 時刻が完全に一致かつタイムゾーンが同じか、異なるタイムゾーンでも同じ時点を指している場合にTrue。
- Naive vs. Aware: TypeErrorが発生します。 NaiveオブジェクトとAwareオブジェクトは直接比較できません。これは、Naiveオブジェクトがどこの時刻か不明確であるため、Awareオブジェクトと比較しても意味がないからです。
“`python
dt_naive = datetime.datetime(2023, 10, 27, 10, 0, 0)
import zoneinfo # Python 3.9+
tokyo_tz = zoneinfo.ZoneInfo(“Asia/Tokyo”)
london_tz = zoneinfo.ZoneInfo(“Europe/London”)
dt_tokyo = datetime.datetime(2023, 10, 27, 10, 0, 0, tzinfo=tokyo_tz) # 東京時間 10:00 (+09:00)
dt_london = datetime.datetime(2023, 10, 27, 2, 0, 0, tzinfo=london_tz) # ロンドン時間 2:00 (+01:00)
Naive vs Naive
dt_naive_same = datetime.datetime(2023, 10, 27, 10, 0, 0)
print(f”Naive == Naive (同じ時刻): {dt_naive == dt_naive_same}”) # True
dt_naive_diff = datetime.datetime(2023, 10, 27, 11, 0, 0)
print(f”Naive == Naive (違う時刻): {dt_naive == dt_naive_diff}”) # False
Aware vs Aware
print(f”Aware == Aware (同じ時点 – 東京10:00とロンドン2:00): {dt_tokyo == dt_london}”) # False (東京10:00はUTC 1:00, ロンドン2:00はUTC 1:00 なので同じ時点)
print(f”Aware == Aware (同じタイムゾーン違う時刻): {dt_tokyo == dt_tokyo.replace(hour=11)}”) # False
Naive vs Aware (エラーになる)
try:
print(f”Naive == Aware: {dt_naive == dt_tokyo}”)
except TypeError as e:
print(f”エラー: {e}”) # 出力: エラー: can’t compare offset-naive and offset-aware datetimes
“`
NaiveオブジェクトをAwareオブジェクトと比較したり、Awareオブジェクトと計算したりする場合は、必ずNaiveオブジェクトをAwareオブジェクトに変換してから行う必要があります。
夏時間(DST)とfold
属性
夏時間の開始時や終了時には、時刻が重複したり存在しなくなったりする場合があります。例えば、夏時間終了時に時計が1時間戻る場合、1時台が2回出現します。
Python 3.6で導入されたfold
属性(PEP 495)は、このような夏時間遷移時の曖昧さを解消するために使用されます。fold
属性は0または1の値を取り、同じローカル時刻が複数回出現する場合に、それが「夏時間前の時刻(fold=0
)」なのか「夏時間後の時刻(fold=1
)」なのかを区別します。
多くの一般的なケースではfold
属性を意識する必要はありませんが、夏時間遷移直前の特定の時刻を正確に表現したい場合などに必要となることがあります。zoneinfo
やpytz
は、夏時間遷移時の時刻を扱う際にfold
属性を適切に設定することがあります。
“`python
夏時間終了の例 (仮に 2023年10月29日 2:00 に時計が 1:00 に戻るヨーロッパの地域を想定)
import zoneinfo # Python 3.9+
cet_tz = zoneinfo.ZoneInfo(“Europe/Paris”)
# 夏時間終了直前の 2:30 (CEST +02:00)
dt_before_fold = datetime.datetime(2023, 10, 29, 2, 30, 0, tzinfo=cet_tz)
print(f”夏時間前 (CEST): {dt_before_fold}, fold: {dt_before_fold.fold}”) # fold: 0
# 夏時間終了で時刻が戻った後の 1:30 (CET +01:00)
# 同じローカル時刻 1:30 が2回出現するが、fold=1 で区別される
# この時刻を直接生成するには、時刻とタイムゾーンを指定して datetime オブジェクトを作り、
# 必要に応じて fold=1 を指定する必要がある。
# または、夏時間終了後の時点から時間を遡る計算などで fold=1 のオブジェクトが得られる。
dt_after_fold = datetime.datetime(2023, 10, 29, 1, 30, 0, tzinfo=cet_tz).replace(fold=1) # replaceでfoldを設定
print(f”夏時間後 (CET): {dt_after_fold}, fold: {dt_after_fold.fold}”) # fold: 1
“`
夏時間開始時に時刻がスキップされる場合も同様に、存在しない時刻を扱おうとするとエラーになることがあります。タイムゾーンライブラリは通常、このようなケースを自動で処理(例: 次の存在する時刻に調整)しますが、意図しない挙動にならないよう注意が必要です。
ISO 8601形式
ISO 8601は、日付と時刻の表現に関する国際規格です。YYYY-MM-DD HH:MM:SS.ffffff+HH:MM
のような形式が一般的です。datetime
オブジェクトには、この形式で文字列を生成するisoformat()
メソッドと、この形式の文字列をパースできるfromisoformat()
クラスメソッド(Python 3.7以降)があります。
“`python
dt = datetime.datetime(2023, 10, 27, 10, 30, 45, 123456)
ISO 8601形式で文字列化
print(dt.isoformat())
出力: 2023-10-27T10:30:45.123456 (時間部分と日付部分の区切りはT)
import zoneinfo # Python 3.9+
tokyo_tz = zoneinfo.ZoneInfo(“Asia/Tokyo”)
dt_aware = dt.replace(tzinfo=tokyo_tz)
print(dt_aware.isoformat())
出力: 2023-10-27T10:30:45.123456+09:00
ISO 8601形式の文字列からパース (Python 3.7以降)
iso_string = “2023-10-27T10:30:45.123456″
dt_from_iso = datetime.datetime.fromisoformat(iso_string)
print(f”ISO文字列からパース (Naive): {dt_from_iso}”)
出力: ISO文字列からパース (Naive): 2023-10-27 10:30:45.123456
iso_aware_string = “2023-10-27T10:30:45.123456+09:00″
dt_aware_from_iso = datetime.datetime.fromisoformat(iso_aware_string)
print(f”ISO Aware文字列からパース (Aware): {dt_aware_from_iso}”)
出力: ISO Aware文字列からパース (Aware): 2023-10-27 10:30:45.123456+09:00
``
fromisoformat()は、オフセット情報が含まれていればAwareオブジェクトを、含まれていなければNaiveオブジェクトを返します。国際的な日時データ交換にはISO 8601形式が推奨されるため、
isoformat()と
fromisoformat()`は非常に便利です。
まとめ:datetimeを使いこなすために
Pythonのdatetime
モジュールは、日付、時刻、時間差を扱うための基本的な機能を提供します。この記事では、その主要なクラス、オブジェクトの生成方法、属性へのアクセス、日時計算、文字列変換、そして特に重要なタイムゾーンの扱いに焦点を当てて解説しました。
datetime
モジュールを効果的に使いこなすための最も重要なポイントは以下の通りです。
- NaiveとAwareの区別を理解する: タイムゾーン情報を持つAwareオブジェクトと持たないNaiveオブジェクトの違いを常に意識してください。
- 原則Awareオブジェクトを使用する: 特に異なるタイムゾーンや夏時間を考慮する必要があるシステムでは、Naiveオブジェクトの使用を避け、Awareオブジェクトで日時を管理してください。
- タイムゾーンライブラリを使用する: Python 3.9以降であれば標準ライブラリの
zoneinfo
を、それ以前のバージョンではpytz
などの外部ライブラリを使用して、正確なタイムゾーン処理を行ってください。固定オフセットのタイムゾーン以外ではdatetime.timezone
は限定的にしか使えません。 - 内部処理はUTCのAwareオブジェクトで: 異なる地域からのデータを扱う場合、システム内部ではUTCのAwareオブジェクトに変換して処理・保存するのが良いプラクティスです。表示する際にユーザーのローカルタイムゾーンに変換します。
- 文字列変換は
strftime
とstrptime
で: 日時オブジェクトと文字列間の変換には、これらのメソッドを適切に使用します。特にstrptime
でパースした結果がNaiveオブジェクトであることに注意し、必要に応じてAwareに変換してください。また、%z
コードでのオフセットパースはAwareオブジェクトを返しますが、%Z
でのタイムゾーン名パースは不安定な場合があります。
datetime
モジュールは、これらの点を踏まえれば、Pythonでの日時処理の大部分をカバーできる強力なツールとなります。この記事が、あなたがdatetime
モジュールを深く理解し、自信を持ってコードを書くための一助となれば幸いです。