Pythonで認識されない日時タイムゾーンを認識する方法


508

私は何をする必要がありますか

私はタイムゾーン非対応の日時オブジェクトを持っていますが、他のタイムゾーン対応日時オブジェクトと比較できるように、それにタイムゾーンを追加する必要があります。この1つのレガシーケースでは、アプリケーション全体を知らないタイムゾーンに変換したくありません。

私が試したこと

まず、問題を示すために:

Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import pytz
>>> unaware = datetime.datetime(2011,8,15,8,15,12,0)
>>> unaware
datetime.datetime(2011, 8, 15, 8, 15, 12)
>>> aware = datetime.datetime(2011,8,15,8,15,12,0,pytz.UTC)
>>> aware
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> aware == unaware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes

最初に、私はastimezoneを試しました:

>>> unaware.astimezone(pytz.UTC)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: astimezone() cannot be applied to a naive datetime
>>>

これが実際に変換しようとしているので、これが失敗したことは驚くに値しません。置換はより良い選択のように見えました(Pythonのように:「タイムゾーンに対応」しているdatetime.today()の値を取得する方法?):

>>> unaware.replace(tzinfo=pytz.UTC)
datetime.datetime(2011, 8, 15, 8, 15, 12, tzinfo=<UTC>)
>>> unaware == aware
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't compare offset-naive and offset-aware datetimes
>>> 

しかし、ご覧のとおり、replaceはtzinfoを設定しているように見えますが、オブジェクトを認識していません。入力文字列を解析して、解析する前にタイムゾーンを取得する準備を整えています(重要な場合は、解析にdateutilを使用しています)が、信じられないほど不器用に思えます。

また、これをpython 2.6とpython 2.7の両方で試したところ、同じ結果が得られました。

環境

一部のデータファイルのパーサーを作成しています。日付文字列にタイムゾーンインジケータがない場合にサポートする必要がある古い形式があります。データソースは修正済みですが、レガシデータ形式をサポートする必要があります。従来のデータの1回限りの変換は、さまざまなビジネスBSの理由からオプションではありません。一般に、デフォルトのタイムゾーンをハードコーディングするという考えは好きではありませんが、この場合は最良のオプションのようです。問題のレガシーデータはすべてUTCであることを私は合理的な自信を持って知っているので、この場合はデフォルトに設定するリスクを受け入れる用意があります。


1
unaware.replace()オブジェクトをインプレースでNone変更している場合に戻りunawareます。REPLは、ここで.replace()新しいdatetimeオブジェクトを返すことを示しています。
jfs

3
私がここに来たときに必要なもの:import datetime; datetime.datetime.now(datetime.timezone.utc)
マーティン・トーマ

1
@MartinThoma名前付きtz引数を使用して読みやすくしますdatetime.datetime.now(tz=datetime.timezone.utc)
。– Acumenus

回答:


594

一般に、単純な日時をタイムゾーンに対応させるには、localizeメソッドを使用します。

import datetime
import pytz

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
aware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0, pytz.UTC)

now_aware = pytz.utc.localize(unaware)
assert aware == now_aware

UTCタイムゾーンの場合、localize処理する夏時間の計算がないため、実際に使用する必要はありません。

now_aware = unaware.replace(tzinfo=pytz.UTC)

動作します。(.replace新しい日時を返しますunaware。変更しません。)


10
まあ、ばかげた気分です。Replaceは新しい日時を返します。それはまさにドキュメントにもあると言い、私はそれを完全に逃しました。ありがとう、それがまさに私が探していたものです。
マークトッツィ2011

2
「Replaceは新しい日時を返します。」うん。REPLが与えるヒントは、戻り値を示しているということです。:)
Karl Knechtel-自宅を離れて

おかげで、私はdt_awareからunware dt_unware = datetime.datetimeの([:6] *(dt_aware.timetuple()))に使用していた、
セルジオ

4
タイムゾーンがUTCでない場合は、コンストラクタを直接使用しないでください。代わりaware = datetime(..., tz)に使用.localize()してください。
jfs 2014年

1
現地時間があいまいな場合があることに言及する価値があります。tz.localize(..., is_dst=None)そうではないと断言します。
jfs 2014年

167

これらの例はすべて外部モジュールを使用していますが、このSO回答にも示されているように、datetimeモジュールだけを使用して同じ結果を得ることができます

from datetime import datetime
from datetime import timezone

dt = datetime.now()
dt.replace(tzinfo=timezone.utc)

print(dt.replace(tzinfo=timezone.utc).isoformat())
'2017-01-12T22:11:31+00:00'

依存関係が少なく、pytzの問題はありません。

注:これをpython3およびpython2で使用したい場合は、タイムゾーンのインポート(UTCにハードコードされている)にも使用できます。

try:
    from datetime import timezone
    utc = timezone.utc
except ImportError:
    #Hi there python2 user
    class UTC(tzinfo):
        def utcoffset(self, dt):
            return timedelta(0)
        def tzname(self, dt):
            return "UTC"
        def dst(self, dt):
            return timedelta(0)
    utc = UTC()

10
pytz問題を防ぐための非常に良い答えです。少し下にスクロールしてよかったです。pytz私のリモートサーバーに実際に取り組みたくありませんでした:)
Tregoreg

7
from datetime import timezonePY3なくpy2.7で動作します。
7yl4r 2017

11
dt.replace(tzinfo=timezone.utc)新しい日時を返すことに注意してくださいdt。変更は行われません。(これを表示するために編集します)。
Blairg23 2017年

2
timezone.utcを使用する代わりに、異なるタイムゾーンを文字列として提供するにはどうすればよいですか(例:「アメリカ/シカゴ」)。
バンプキン'26

2
@bumpkinは遅くなるより遅くなると思います:tz = pytz.timezone('America/Chicago')
フロリアン

82

私はdt_awareからdt_unawareまで使用していました

dt_unaware = dt_aware.replace(tzinfo=None)

およびdt_unwareからdt_awareへ

from pytz import timezone
localtz = timezone('Europe/Lisbon')
dt_aware = localtz.localize(dt_unware)

しかし、前に答えることも良い解決策です。


2
存在しないまたはあいまいな現地時間を表すlocaltz.localize(dt_unware, is_dst=None)場合に例外を発生させるために使用できますdt_unware(注:localtzUTCがDST遷移を持たないため、以前の回答のリビジョンではUTCがなかったため、そのような問題はありませんでした
jfs

@JFセバスチャンは、最初のコメントが適用
セルジオ

1
変換の両方向を示していただきありがとうございます。
クリスチャンロング

42

私はDjangoでこのステートメントを使用して、認識されていない時間を認識されている時間に変換します。

from django.utils import timezone

dt_aware = timezone.make_aware(dt_unaware, timezone.get_current_timezone())

2
私はこのソリューションが好きです(+1)が、Djangoに依存しているため、彼らが探していたものではありません(-1)。=)
mkoistinen 2015

3
あなたは実際には2番目の引数ではありません。デフォルトの引数(なし)は、ローカルタイムゾーンを暗黙的に使用されることを意味します。DSTと同じ(3番目の引数です)
Oli

14

私は以前の回答に同意します。UTCから始めてもよい場合は問題ありません。しかし、UTC以外のローカルタイムゾーンを持つdatetimeを持つ tz対応の値で作業することも、一般的なシナリオだと思います

名前だけで行くとしたら、おそらくreplace()が適用可能であり、適切な日時対応オブジェクトを生成すると推測できます。これはそうではありません。

replace(tzinfo = ...)の動作はランダムに見えます。したがって、それは役に立たない。これは使用しないでください。

localizeは使用する正しい関数です。例:

localdatetime_aware = tz.localize(datetime_nonaware)

または、より完全な例:

import pytz
from datetime import datetime
pytz.timezone('Australia/Melbourne').localize(datetime.now())

現在の現地時間のタイムゾーン対応の日時値を取得します。

datetime.datetime(2017, 11, 3, 7, 44, 51, 908574, tzinfo=<DstTzInfo 'Australia/Melbourne' AEDT+11:00:00 DST>)

3
これにはより多くの投票が必要replace(tzinfo=...)です。UTC以外のタイムゾーンで実行しようとすると、日時が不正になります。私は例えばの-07:53代わりに得た-08:00stackoverflow.com/a/13994611/1224827を
Blairg23

replace(tzinfo=...)予期せぬ振る舞いの再現可能な例を挙げられますか?
xjcl

11

およびのdateutil.tz.tzlocal()使用におけるタイムゾーンを取得するために使用します。datetime.datetime.now()datetime.datetime.astimezone()

from datetime import datetime
from dateutil import tz

unlocalisedDatetime = datetime.now()

localisedDatetime1 = datetime.now(tz = tz.tzlocal())
localisedDatetime2 = datetime(2017, 6, 24, 12, 24, 36, tz.tzlocal())
localisedDatetime3 = unlocalisedDatetime.astimezone(tz = tz.tzlocal())
localisedDatetime4 = unlocalisedDatetime.replace(tzinfo = tz.tzlocal())

datetime.astimezoneまず変換しますdatetime呼び出すのと同じであるタイムゾーン、に続いUTCにオブジェクトをdatetime.replace、元のタイムゾーン情報ビーイングとNone


1
UTCにしたい場合:.replace(tzinfo=dateutil.tz.UTC)
Martin Thoma

2
ワン輸入以下、単に:.replace(tzinfo=datetime.timezone.utc)
kubanczyk

9

これにより、@Sérgioと@unutbuの回答が成文化されます。これは、pytz.timezoneオブジェクトまたはIANAタイムゾーン文字列のいずれかで「機能する」だけです。

def make_tz_aware(dt, tz='UTC', is_dst=None):
    """Add timezone information to a datetime object, only if it is naive."""
    tz = dt.tzinfo or tz
    try:
        tz = pytz.timezone(tz)
    except AttributeError:
        pass
    return tz.localize(dt, is_dst=is_dst) 

これはdatetime.localize()(または.inform()または.awarify())が何をすべきかと思われ、文字列とタイムゾーンオブジェクトの両方をtz引数に受け入れ、タイムゾーンが指定されていない場合はデフォルトでUTCになります。


1
おかげで、これにより、システムが最初にローカル時刻であると想定し、次に値を再計算することなく、生の日時オブジェクトを「UTC」として「ブランド化」することができました。
Nikhil VJ

4

Python 3.9でzoneinfoモジュールが追加されたため、標準ライブラリのみが必要になりました。

from zoneinfo import ZoneInfo
from datetime import datetime
unaware = datetime(2020, 10, 31, 12)

タイムゾーンを添付します。

>>> unaware.replace(tzinfo=ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 12:00:00+09:00'

システムのローカルタイムゾーンをアタッチします。

>>> unaware.replace(tzinfo=ZoneInfo('localtime'))
datetime.datetime(2020, 10, 31, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
>>> str(_)
'2020-10-31 12:00:00+01:00'

その後、他のタイムゾーンに適切に変換されます。

>>> unaware.replace(tzinfo=ZoneInfo('localtime')).astimezone(ZoneInfo('Asia/Tokyo'))
datetime.datetime(2020, 10, 31, 20, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='Asia/Tokyo'))
>>> str(_)
'2020-10-31 20:00:00+09:00'

利用可能なタイムゾーンのウィキペディアリスト


Python 3.6から3.8で使用できるようにするバックポートがあります。

sudo pip install backports.zoneinfo

次に:

from backports.zoneinfo import ZoneInfo

1
Windowsでは、次の操作も必要ですpip install tzdata
MrFuppes

@MrFuppes先端をありがとう!私はこれを明日テストし、私の答えに答えます。Macの状況を知っていますか?
xjcl

0

unutbuの回答の形式。このようなことを処理するユーティリティモジュールを、より直感的な構文で作成しました。pipでインストールできます。

import datetime
import saturn

unaware = datetime.datetime(2011, 8, 15, 8, 15, 12, 0)
now_aware = saturn.fix_naive(unaware)

now_aware_madrid = saturn.fix_naive(unaware, 'Europe/Madrid')

0

タイムゾーン対応の日時を作成するだけの場合

import datetime
import pytz

datetime.datetime(2019, 12, 7, tzinfo=pytz.UTC)

0

Pythonはまったく新しく、同じ問題が発生しました。私はこの解決策を非常に簡単に見つけ、私にとってはうまくいきます(Python 3.6):

unaware=parser.parse("2020-05-01 0:00:00")
aware=unaware.replace(tzinfo=tz.tzlocal()).astimezone(tz.tzlocal())

0

タイムゾーン間の変更

import pytz
from datetime import datetime

other_tz = pytz.timezone('Europe/Madrid')

# From random aware datetime...
aware_datetime = datetime.utcnow().astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# 1. Change aware datetime to UTC and remove tzinfo to obtain an unaware datetime
unaware_datetime = aware_datetime.astimezone(pytz.UTC).replace(tzinfo=None)
>> 2020-05-21 06:28:26.984948

# 2. Set tzinfo to UTC directly on an unaware datetime to obtain an utc aware datetime
aware_datetime_utc = unaware_datetime.replace(tzinfo=pytz.UTC)
>> 2020-05-21 06:28:26.984948+00:00

# 3. Convert the aware utc datetime into another timezone
reconverted_aware_datetime = aware_datetime_utc.astimezone(other_tz)
>> 2020-05-21 08:28:26.984948+02:00

# Initial Aware Datetime and Reconverted Aware Datetime are equal
print(aware_datetime1 == aware_datetime2)
>> True
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.