Cara membuat zona waktu datetime yang tidak disadari sadar dengan python


508

Apa yang harus saya lakukan

Saya memiliki objek waktu-waktu tidak sadar zona waktu, yang saya perlu menambahkan zona waktu agar dapat membandingkannya dengan objek waktu-waktu sadar-waktu-zona waktu lainnya. Saya tidak ingin mengonversi seluruh aplikasi saya ke zona waktu tanpa mengetahui untuk kasus warisan yang satu ini.

Apa yang saya coba

Pertama, untuk menunjukkan masalahnya:

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

Pertama, saya mencoba 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
>>>

Tidak terlalu mengejutkan bahwa ini gagal, karena sebenarnya mencoba melakukan konversi. Ganti sepertinya pilihan yang lebih baik (sesuai Python: Cara mendapatkan nilai datetime.today () yaitu "timezone aware"? ):

>>> 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
>>> 

Tapi seperti yang Anda lihat, ganti tampaknya mengatur tzinfo, tetapi tidak membuat objek sadar. Saya sedang bersiap-siap untuk kembali ke doctoring string input untuk memiliki zona waktu sebelum parsing (saya menggunakan dateutil untuk parsing, jika itu penting), tapi itu tampaknya sangat muram.

Juga, saya sudah mencoba ini di kedua python 2.6 dan python 2.7, dengan hasil yang sama.

Konteks

Saya menulis parser untuk beberapa file data. Ada format lama yang saya perlukan untuk mendukung di mana string tanggal tidak memiliki indikator zona waktu. Saya sudah memperbaiki sumber data, tetapi saya masih perlu mendukung format data lawas. Konversi satu kali data legacy bukan pilihan untuk berbagai alasan BS bisnis. Sementara secara umum, saya tidak suka gagasan hard-coding zona waktu default, dalam hal ini sepertinya pilihan terbaik. Saya tahu dengan keyakinan yang masuk akal bahwa semua data lawas yang dimaksud adalah dalam UTC, jadi saya siap untuk menerima risiko gagal bayar itu dalam hal ini.


1
unaware.replace()akan kembali Nonejika itu memodifikasi unawareobjek di tempat. REPL menunjukkan bahwa .replace()mengembalikan datetimeobjek baru di sini.
jfs

3
Apa yang saya butuhkan ketika saya datang ke sini:import datetime; datetime.datetime.now(datetime.timezone.utc)
Martin Thoma

1
@ MartinThoma Saya akan menggunakan nama tzarg agar lebih mudah dibaca:datetime.datetime.now(tz=datetime.timezone.utc)
Acumenus

Jawaban:


595

Secara umum, untuk membuat zona waktu sadar-naif yang naif, gunakan metode pelokalan :

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

Untuk zona waktu UTC, itu tidak perlu digunakan localizekarena tidak ada perhitungan waktu musim panas untuk menangani:

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

bekerja. ( .replacemengembalikan datetime baru; tidak diubah unaware.)


10
Yah, saya merasa konyol. Ganti mengembalikan datetime baru. Ia mengatakan itu di dokumen juga, dan aku benar-benar ketinggalan. Terima kasih, itulah tepatnya yang saya cari.
Mark Tozzi

2
"Ganti mengembalikan datetime baru." Ya. Petunjuk bahwa REPL memberi Anda adalah itu menunjukkan Anda nilai yang dikembalikan. :)
Karl Knechtel - jauh dari rumah

terima kasih, saya telah menggunakan dari dt_aware ke unware dt_unware = datetime.datetime (* (dt_aware.timetuple () [: 6])),
Sérgio

4
jika zona waktu bukan UTC maka jangan gunakan konstruktor secara langsung :, aware = datetime(..., tz)gunakan .localize()saja.
jfs

1
Perlu disebutkan bahwa waktu setempat mungkin ambigu. tz.localize(..., is_dst=None)menegaskan bahwa itu bukan.
jfs

167

Semua contoh ini menggunakan modul eksternal, tetapi Anda dapat mencapai hasil yang sama hanya menggunakan modul datetime, seperti juga disajikan dalam jawaban SO ini :

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'

Lebih sedikit ketergantungan dan tidak ada masalah pytz.

CATATAN: Jika Anda ingin menggunakan ini dengan python3 dan python2, Anda dapat menggunakan ini juga untuk impor zona waktu (hardcode untuk 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
Jawaban yang sangat bagus untuk mencegah pytzmasalah, saya senang saya gulir ke bawah sedikit! Memang tidak ingin menangani dengan pytzserver jauh saya :)
Tregoreg

7
Perhatikan bahwa from datetime import timezonebekerja di py3 tetapi tidak py2.7.
7yl4r

11
Anda harus mencatat bahwa dt.replace(tzinfo=timezone.utc)mengembalikan datetime baru, itu tidak berubah dtpada tempatnya. (Saya akan mengedit untuk menunjukkan ini).
Blairg23

2
Bagaimana mungkin Anda, alih-alih menggunakan timezone.utc, memberikan zona waktu yang berbeda sebagai string (mis. "Amerika / Chicago")?
bumpkin

2
@bumpkin lebih baik terlambat daripada tidak, kurasa:tz = pytz.timezone('America/Chicago')
Florian

83

Saya telah menggunakan dari dt_aware ke dt_unaware

dt_unaware = dt_aware.replace(tzinfo=None)

dan dt_unware ke dt_aware

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

tetapi jawaban sebelumnya juga merupakan solusi yang baik.


2
Anda dapat menggunakan localtz.localize(dt_unware, is_dst=None)untuk mengajukan pengecualian jika dt_unwaremewakili waktu lokal yang tidak ada atau ambigu (catatan: tidak ada masalah seperti itu dalam revisi sebelumnya atas jawaban Anda di mana localtzUTC karena UTC tidak memiliki transisi DST
jfs

@JF Sebastian, komentar pertama diterapkan
Sérgio

1
Saya menghargai Anda menunjukkan kedua arah konversi.
Christian Long

42

Saya menggunakan pernyataan ini di Django untuk mengubah waktu yang tidak disadari menjadi sadar:

from django.utils import timezone

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

2
Saya memang suka solusi ini (+1), tetapi ini tergantung pada Django, yang bukan yang mereka cari (-1). =)
mkoistinen

3
Anda sebenarnya bukan argumen kedua. The argumen default (Tidak) akan berarti zona waktu lokal digunakan secara implisit. Sama dengan DST (yang merupakan argumen
ketiga_

14

Saya setuju dengan jawaban sebelumnya, dan tidak masalah jika Anda baik-baik saja memulai di UTC. Tetapi saya pikir ini juga merupakan skenario umum bagi orang untuk bekerja dengan nilai sadar yang memiliki waktu yang memiliki zona waktu lokal non UTC.

Jika Anda hanya pergi dengan nama, seseorang mungkin akan menyimpulkan ganti () akan berlaku dan menghasilkan objek sadar datetime yang tepat. Ini bukan kasusnya.

ganti (tzinfo = ...) tampaknya acak dalam perilakunya . Karena itu tidak berguna. Jangan gunakan ini!

pelokalan adalah fungsi yang benar untuk digunakan. Contoh:

localdatetime_aware = tz.localize(datetime_nonaware)

Atau contoh yang lebih lengkap:

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

memberi saya nilai data waktu sadar zona waktu saat ini:

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

3
Ini membutuhkan lebih banyak upvote, coba lakukan replace(tzinfo=...)di zona waktu selain UTC akan merusak waktu Anda. Saya mendapatkan -07:53bukannya -08:00misalnya. Lihat stackoverflow.com/a/13994611/1224827
Blairg23

Bisakah Anda memberikan contoh yang dapat direproduksi tentang replace(tzinfo=...)perilaku yang tidak terduga?
xjcl

11

Gunakan dateutil.tz.tzlocal()untuk mendapatkan zona waktu dalam penggunaan datetime.datetime.now()dan 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())

Perhatikan bahwa datetime.astimezonepertama-tama akan mengonversi datetimeobjek Anda ke UTC kemudian menjadi zona waktu, yang sama dengan menelepon datetime.replacedengan informasi zona waktu yang asli None.


1
Jika Anda ingin membuatnya UTC:.replace(tzinfo=dateutil.tz.UTC)
Martin Thoma

2
Satu impor lebih sedikit dan hanya:.replace(tzinfo=datetime.timezone.utc)
kubanczyk

9

Ini mengkodifikasikan jawaban @ Sérgio dan @ unutbu . Ini akan "hanya bekerja" dengan pytz.timezoneobjek atau string Zona Waktu 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) 

Ini terlihat seperti apa yang datetime.localize()(atau .inform()atau .awarify()) harus dilakukan, menerima objek string dan zona waktu untuk argumen tz dan default ke UTC jika tidak ada zona waktu yang ditentukan.


1
Terima kasih, ini membantu saya "merek" objek datetime mentah sebagai "UTC" tanpa sistem pertama-tama menganggapnya sebagai waktu lokal dan kemudian menghitung ulang nilai-nilai!
Nikhil VJ

4

Python 3.9 menambahkan zoneinfomodul jadi sekarang hanya perpustakaan standar yang diperlukan!

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

Lampirkan zona waktu:

>>> 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'

Pasang zona waktu lokal sistem:

>>> 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'

Selanjutnya dikonversi dengan benar ke zona waktu lain:

>>> 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'

Daftar zona waktu Wikipedia yang tersedia


Ada backport yang memungkinkan penggunaan dalam Python 3.6 hingga 3.8 :

sudo pip install backports.zoneinfo

Kemudian:

from backports.zoneinfo import ZoneInfo

1
pada Windows, Anda juga perlupip install tzdata
MrFuppes

@ McFuppes Terima kasih atas tipnya! Saya akan menguji ini besok dan itu untuk jawaban saya. Apakah Anda tahu apa situasinya pada Mac?
xjcl

tidak, maaf, tidak ada Mac di sekitar untuk mencoba. Dugaan saya adalah seperti di Linux.
MrFuppes

0

Dalam format jawaban unutbu; Saya membuat modul utilitas yang menangani hal-hal seperti ini, dengan sintaks yang lebih intuitif. Dapat diinstal dengan 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

bagi mereka yang hanya ingin membuat waktu sadar zona waktu

import datetime
import pytz

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

0

cukup baru untuk Python dan saya mengalami masalah yang sama. Saya menemukan solusi ini cukup sederhana dan bagi saya itu berfungsi dengan baik (Python 3.6):

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

0

Berganti di antara zona waktu

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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.