Mengonversi datetime python UTC ke datetime lokal hanya menggunakan pustaka standar python?

189

Saya punya contoh python datetime yang dibuat menggunakan datetime.utcnow () dan bertahan di database.

Untuk tampilan, saya ingin mengonversi instance datetime yang diambil dari database ke datetime lokal menggunakan zona waktu lokal default (yaitu, seolah-olah datetime dibuat menggunakan datetime.now ()).

Bagaimana saya bisa mengubah datetime UTC ke datetime lokal hanya menggunakan pustaka standar python (mis., Tidak ada ketergantungan pytz)?

Tampaknya salah satu solusinya adalah menggunakan datetime.astimezone (tz), tetapi bagaimana Anda mendapatkan zona waktu lokal default?

Nitro Zark
sumber
Dalam format apa waktu disimpan ke database? Jika ini merupakan format standar, Anda mungkin tidak perlu melakukan konversi apa pun.
Apalala
1
Jawaban ini menunjukkan cara sederhana menggunakan pytz .
juan

Jawaban:

275

Dengan Python 3.3+:

from datetime import datetime, timezone

def utc_to_local(utc_dt):
    return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)

Dalam Python 2/3:

import calendar
from datetime import datetime, timedelta

def utc_to_local(utc_dt):
    # get integer timestamp to avoid precision lost
    timestamp = calendar.timegm(utc_dt.timetuple())
    local_dt = datetime.fromtimestamp(timestamp)
    assert utc_dt.resolution >= timedelta(microseconds=1)
    return local_dt.replace(microsecond=utc_dt.microsecond)

Menggunakan pytz(keduanya Python 2/3):

import pytz

local_tz = pytz.timezone('Europe/Moscow') # use your local timezone name here
# NOTE: pytz.reference.LocalTimezone() would produce wrong result here

## You could use `tzlocal` module to get local timezone on Unix and Win32
# from tzlocal import get_localzone # $ pip install tzlocal

# # get local timezone    
# local_tz = get_localzone()

def utc_to_local(utc_dt):
    local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz)
    return local_tz.normalize(local_dt) # .normalize might be unnecessary

Contoh

def aslocaltimestr(utc_dt):
    return utc_to_local(utc_dt).strftime('%Y-%m-%d %H:%M:%S.%f %Z%z')

print(aslocaltimestr(datetime(2010,  6, 6, 17, 29, 7, 730000)))
print(aslocaltimestr(datetime(2010, 12, 6, 17, 29, 7, 730000)))
print(aslocaltimestr(datetime.utcnow()))

Keluaran

Python 3.3
2010-06-06 21:29:07.730000 MSD+0400
2010-12-06 20:29:07.730000 MSK+0300
2012-11-08 14:19:50.093745 MSK+0400
Python 2
2010-06-06 21:29:07.730000 
2010-12-06 20:29:07.730000 
2012-11-08 14:19:50.093911 
pytz
2010-06-06 21:29:07.730000 MSD+0400
2010-12-06 20:29:07.730000 MSK+0300
2012-11-08 14:19:50.146917 MSK+0400

Catatan: memperhitungkan DST dan perubahan utc offset terbaru untuk zona waktu MSK.

Saya tidak tahu apakah solusi non-pytz bekerja di Windows.

jfs
sumber
1
apa yang 'normalisasi' lakukan?
avi
4
@avi: secara umum: pytz: Mengapa diperlukan normalisasi saat mengkonversi antara zona waktu? . Catatan: Tidak perlu dengan implementasi saat ini .normalize()karena zona waktu sumber .astimezone()adalah UTC.
jfs
1
Saya dapatkan TypeError: replace() takes no keyword argumentsketika mencoba solusi pytz.
Craig
@Craig kodenya berfungsi apa adanya, seperti yang ditunjukkan pada jawaban. Buat contoh kode minimal lengkap yang mengarah ke TypeError, sebutkan Python, versi pytz yang Anda gunakan dan posting sebagai pertanyaan Stack Overflow terpisah.
jfs
Kiat cepat untuk solusi Python 3.3+. Untuk pergi ke arah yang berlawanan (Asli ke UTC), ini bekerja: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
jasonrhaas
50

Anda tidak dapat melakukannya hanya dengan perpustakaan standar karena perpustakaan standar tidak memiliki zona waktu. Anda membutuhkan pytz atau dateutil .

>>> from datetime import datetime
>>> now = datetime.utcnow()
>>> from dateutil import tz
>>> HERE = tz.tzlocal()
>>> UTC = tz.gettz('UTC')

The Conversion:
>>> gmt = now.replace(tzinfo=UTC)
>>> gmt.astimezone(HERE)
datetime.datetime(2010, 12, 30, 15, 51, 22, 114668, tzinfo=tzlocal())

Atau, Anda dapat melakukannya tanpa pytz atau dateutil dengan menerapkan zona waktu Anda sendiri. Tapi itu konyol.

Lennart Regebro
sumber
Terima kasih, saya akan menyimpannya jika saya membutuhkan solusi lengkap. Apakah dateutil mendukung Python 3.1 (kata Python 2.3+ tetapi mencurigakan)?
Nitro Zark
pytz memang menangani pergantian DST dengan lebih baik.
Lennart Regebro
2
Pembaruan: pytz dan dateutil keduanya mendukung Python 3 saat ini.
Lennart Regebro
1
@ JFSebastian: dateutil tidak dapat membedakan antara dua 1:30 kali yang terjadi selama pergantian DST. Jika Anda harus dapat membedakan antara dua kali, solusinya menggunakan pytz, yang dapat mengatasinya.
Lennart Regebro
8

Anda tidak dapat melakukannya dengan perpustakaan standar. Menggunakan modul pytz Anda dapat mengonversi objek datetime naif / sadar ke zona waktu lainnya. Mari kita lihat beberapa contoh menggunakan Python 3.

Objek naif dibuat melalui metode kelas utcnow()

Untuk mengonversi objek naif ke zona waktu lainnya, pertama-tama Anda harus mengubahnya menjadi objek sadar waktu. Anda bisa menggunakan replacemetode untuk mengonversi objek datetime naif ke objek datetime sadar . Kemudian untuk mengonversi objek datetime sadar ke zona waktu lain, Anda dapat menggunakan astimezonemetode.

Variabel pytz.all_timezonesmemberi Anda daftar semua zona waktu yang tersedia di modul pytz.

import datetime,pytz

dtobj1=datetime.datetime.utcnow()   #utcnow class method
print(dtobj1)

dtobj3=dtobj1.replace(tzinfo=pytz.UTC) #replace method

dtobj_hongkong=dtobj3.astimezone(pytz.timezone("Asia/Hong_Kong")) #astimezone method
print(dtobj_hongkong)

Objek naif dibuat melalui metode kelas now()

Karena nowmetode mengembalikan tanggal dan waktu saat ini, jadi Anda harus membuat zona waktu objek datetime disadari terlebih dahulu. The localize fungsi mengkonversi naif objek datetime menjadi objek datetime zona-sadar. Kemudian Anda dapat menggunakan astimezonemetode ini untuk mengubahnya menjadi zona waktu lain.

dtobj2=datetime.datetime.now()

mytimezone=pytz.timezone("Europe/Vienna") #my current timezone
dtobj4=mytimezone.localize(dtobj2)        #localize function

dtobj_hongkong=dtobj4.astimezone(pytz.timezone("Asia/Hong_Kong")) #astimezone method
print(dtobj_hongkong)
N Randhawa
sumber
6

Saya pikir saya sudah mengetahuinya: menghitung jumlah detik sejak zaman, kemudian mengkonversi ke timzeone lokal menggunakan time.localtime, dan kemudian mengubah struct waktu kembali menjadi datetime ...

EPOCH_DATETIME = datetime.datetime(1970,1,1)
SECONDS_PER_DAY = 24*60*60

def utc_to_local_datetime( utc_datetime ):
    delta = utc_datetime - EPOCH_DATETIME
    utc_epoch = SECONDS_PER_DAY * delta.days + delta.seconds
    time_struct = time.localtime( utc_epoch )
    dt_args = time_struct[:6] + (delta.microseconds,)
    return datetime.datetime( *dt_args )

Ini menerapkan DST musim panas / musim dingin dengan benar:

>>> utc_to_local_datetime( datetime.datetime(2010, 6, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 6, 6, 19, 29, 7, 730000)
>>> utc_to_local_datetime( datetime.datetime(2010, 12, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 12, 6, 18, 29, 7, 730000)
Nitro Zark
sumber
1
Ugh. Tapi itu harusnya bekerja pada Unix, setidaknya. Untuk Windows itu bisa salah jika aturan daylight saving Anda telah berubah sejak tanggal dikonversi. Mengapa Anda tidak ingin menggunakan perpustakaan?
Lennart Regebro
Saya hanya perlu konversi utc / lokal sehingga menyeret perpustakaan seperti itu tampaknya berlebihan. Karena saya satu-satunya pengguna aplikasi ini saya dapat hidup dengan beberapa batasan. Ini juga menghindari pengelolaan masalah terkait ketergantungan (mendukung Python 3 ?, Windows? Kualitas ...) yang akan lebih mahal daripada bekerja pada skrip kecil saya.
Nitro Zark
1
Baik. jika Anda membutuhkan Python3, maka Anda kurang beruntung. Tetapi jika tidak meneliti dan membuat solusi Anda sendiri adalah berlebihan dibandingkan dengan menggunakan perpustakaan.
Lennart Regebro
1
+1 (untuk mengimbangi downvote: mungkin persyaratan yang sah untuk mencari solusi stdlib-only) dengan peringatan @Lennart yang disebutkan. Mengingat dateutil mungkin gagal pada Unix dan Windows. btw, Anda dapat mengekstraksi utctotimestamp(utc_dt) -> (seconds, microseconds)dari kode Anda untuk kejelasan, lihat implementasi Python 2.6-3.x
jfs
1
Sebuah pytz (dan dateutil) bekerja pada Python 3 sejak beberapa waktu sekarang, sehingga masalah Python3 / Windows / Quality tidak menjadi masalah lagi.
Lennart Regebro
6

Membangun komentar Alexei. Ini harus bekerja untuk DST juga.

import time
import datetime

def utc_to_local(dt):
    if time.localtime().tm_isdst:
        return dt - datetime.timedelta(seconds = time.altzone)
    else:
        return dt - datetime.timedelta(seconds = time.timezone)
John Kerns
sumber
4

Pustaka Python standar tidak datang dengan tzinfoimplementasi sama sekali. Saya selalu menganggap ini sebagai kekurangan mengejutkan dari modul datetime.

The dokumentasi untuk kelas tzinfo tidak datang dengan beberapa contoh yang berguna. Cari blok kode besar di bagian akhir.

Mark tebusan
sumber
1
Dugaan saya tentang implementasi inti seperti itu karena basis data zona waktu perlu diperbarui secara berkala karena beberapa negara tidak memiliki aturan tetap untuk menentukan periode DST. Jika saya tidak salah, JVM misalnya perlu diperbarui untuk mendapatkan database zona waktu terakhir ...
Nitro Zark
@Nitro Zark, setidaknya mereka seharusnya punya satu untuk UTC. The osmodul bisa memberikan satu untuk waktu setempat berdasarkan fungsi sistem operasi.
Mark Ransom
1
Saya sedang memeriksa daftar fitur baru di 3.2 dan mereka menambahkan zona waktu UTC di 3.2 ( docs.python.org/dev/whatsnew/3.2.html#datetime ). Sepertinya tidak ada zona waktu lokal ...
Nitro Zark
2

Python 3.9 menambahkan zoneinfomodul sehingga sekarang dapat dilakukan sebagai berikut (hanya stdlib):

from zoneinfo import ZoneInfo
from datetime import datetime

utc_unaware = datetime(2020, 10, 31, 12)  # loaded from database
utc_aware = utc_unaware.replace(tzinfo=ZoneInfo('UTC'))  # make aware
local_aware = utc_aware.astimezone(ZoneInfo('localtime'))  # convert

Eropa Tengah adalah 1 atau 2 jam lebih awal dari UTC, demikian local_awarejuga:

datetime.datetime(2020, 10, 31, 13, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='localtime'))

sebagai str:

2020-10-31 13:00:00+01:00

Windows tidak memiliki basis data zona waktu sistem, jadi di sini diperlukan paket tambahan:

pip install tzdata  

Ada backport yang memungkinkan penggunaan dalam Python 3.6 hingga 3.8 :

sudo pip install backports.zoneinfo

Kemudian:

from backports.zoneinfo import ZoneInfo
xjcl
sumber
Anda bahkan tidak perlu di zoneinfosini - lihat bagian pertama dari jawaban jfs (bagian Python3.3 +) ;-)
MrFuppes
@ McFuppes Saya pikir ZoneInfo('localtime')jauh lebih jelas daripada tz=None(setelah semua, orang mungkin berharap tz=Noneuntuk menghapus zona waktu atau mengembalikan UTC daripada waktu lokal).
xjcl
@MrFuppes Plus pertanyaannya mungkin melibatkan OP memiliki situs web dan ingin menunjukkan cap waktu pengguna di waktu lokal mereka. Untuk itu Anda harus mengonversi ke zona waktu eksplisit pengguna daripada waktu lokal.
xjcl
0

Cara sederhana (tapi mungkin cacat) yang bekerja di Python 2 dan 3:

import time
import datetime

def utc_to_local(dt):
    return dt - datetime.timedelta(seconds = time.timezone)

Keuntungannya adalah sepele untuk menulis fungsi terbalik

Alexei Averchenko
sumber
1
Ini memberikan hasil yang salah, karena time.timezonetidak mempertimbangkan penghematan siang hari.
guyarad
0

Cara termudah yang saya temukan adalah mendapatkan waktu offset di mana Anda berada, kemudian kurangi dari jam tersebut.

def format_time(ts,offset):
    if not ts.hour >= offset:
        ts = ts.replace(day=ts.day-1)
        ts = ts.replace(hour=ts.hour-offset)
    else:
        ts = ts.replace(hour=ts.hour-offset)
    return ts

Ini berfungsi untuk saya, dalam Python 3.5.2.

Produksi Zld
sumber
0

Berikut adalah cara lain untuk mengubah zona waktu dalam format datetime (saya tahu saya membuang energi untuk ini, tetapi saya tidak melihat halaman ini jadi saya tidak tahu caranya) tanpa min. dan dtk. karena saya tidak membutuhkannya untuk proyek saya:

def change_time_zone(year, month, day, hour):
      hour = hour + 7 #<-- difference
      if hour >= 24:
        difference = hour - 24
        hour = difference
        day += 1
        long_months = [1, 3, 5, 7, 8, 10, 12]
        short_months = [4, 6, 9, 11]
        if month in short_months:
          if day >= 30:
            day = 1
            month += 1
            if month > 12:
              year += 1
        elif month in long_months:
          if day >= 31:
            day = 1
            month += 1
            if month > 12:
              year += 1
        elif month == 2:
          if not year%4==0:
            if day >= 29:
              day = 1
              month += 1
              if month > 12:
                year += 1
          else:
            if day >= 28:
              day = 1
              month += 1
              if month > 12:
                year += 1
      return datetime(int(year), int(month), int(day), int(hour), 00)
Tamu
sumber
Gunakan timedelta untuk beralih di antara zona waktu. Yang Anda butuhkan hanyalah offset dalam jam di antara zona waktu. Tidak harus mengutak-atik batas untuk semua 6 elemen dari objek datetime. timedelta menangani tahun kabisat, abad kabisat, dll, juga, dengan mudah. Anda harus 'dari datetime import timedelta'. Kemudian jika offset adalah variabel dalam jam: timeout = timein + timedelta (jam = offset), di mana timein dan timeout adalah objek datetime. mis. timein + timedelta (jam = -8) mengkonversi dari GMT ke PST.
Karl Rudnick
0

Ini adalah cara yang mengerikan untuk melakukannya tetapi tidak membuat definisi. Memenuhi persyaratan untuk tetap dengan perpustakaan Python3 dasar.

# Adjust from UST to Eastern Standard Time (dynamic)
# df.my_localtime should already be in datetime format, so just in case
df['my_localtime'] = pd.to_datetime.df['my_localtime']

df['my_localtime'] = df['my_localtime'].dt.tz_localize('UTC').dt.tz_convert('America/New_York').astype(str)
df['my_localtime'] = pd.to_datetime(df.my_localtime.str[:-6])
Arthur D. Howland
sumber
Meskipun ini menarik dan saya akan menggunakan metode ini tidak hanya menggunakan stdlib. Menggunakan Pandas untuk melakukan konversi pandas.pydata.org/pandas-docs/version/0.25/reference/api/…
Tim Hughes
-3

Gunakan timedelta untuk beralih di antara zona waktu. Yang Anda butuhkan hanyalah offset dalam jam di antara zona waktu. Tidak harus mengutak-atik batas untuk semua 6 elemen dari objek datetime. timedelta menangani tahun kabisat, abad kabisat, dll, juga, dengan mudah. Anda harus terlebih dahulu

from datetime import datetime, timedelta

Maka jika offsetzona waktu delta dalam jam:

timeout = timein + timedelta(hours = offset)

di mana timein dan timeout adalah objek datetime. misalnya

timein + timedelta(hours = -8)

mengkonversi dari GMT ke PST.

Lantas, bagaimana cara menentukannya offset? Ini adalah fungsi sederhana asalkan Anda hanya memiliki beberapa kemungkinan untuk konversi tanpa menggunakan objek waktu-waktu yang "sadar" zona waktu yang dilakukan dengan baik oleh beberapa jawaban lain. Sedikit manual, tetapi terkadang kejelasan yang terbaik.

def change_timezone(timein, timezone, timezone_out):
    '''
    changes timezone between predefined timezone offsets to GMT
    timein - datetime object
    timezone - 'PST', 'PDT', 'GMT' (can add more as needed)
    timezone_out - 'PST', 'PDT', 'GMT' (can add more as needed)
    ''' 
    # simple table lookup        
    tz_offset =  {'PST': {'GMT': 8, 'PDT': 1, 'PST': 0}, \
                  'GMT': {'PST': -8, 'PDT': -7, 'GMT': 0}, \
                  'PDT': {'GMT': 7, 'PST': -1, 'PDT': 0}}
    try:
        offset = tz_offset[timezone][timezone_out]
    except:
        msg = 'Input timezone=' + timezone + ' OR output time zone=' + \
            timezone_out + ' not recognized'
        raise DateTimeError(msg)

    return timein + timedelta(hours = offset)

Setelah melihat banyak jawaban dan bermain-main dengan kode paling ketat yang dapat saya pikirkan (untuk saat ini) tampaknya yang terbaik bahwa semua aplikasi, di mana waktu adalah penting dan zona waktu campuran harus diperhitungkan, harus melakukan upaya nyata untuk membuat semua objek waktu "sadar". Maka tampaknya jawaban yang paling sederhana adalah:

timeout = timein.astimezone(pytz.timezone("GMT"))

untuk mengkonversi ke GMT misalnya. Tentu saja, untuk mengkonversi ke / dari zona waktu lain yang Anda inginkan, lokal atau sebaliknya, cukup gunakan string zona waktu yang sesuai dengan pytz (dari pytz.all_timezones). Waktu musim panas juga diperhitungkan.

Karl Rudnick
sumber
1
Ini / bukan / ide yang bagus. DST tidak bergeser pada tanggal yang sama untuk seluruh dunia. Gunakan fungsi datetime untuk melakukan ini.
Gabe
@ gabe - Memahami bahwa tabel itu bukan ide yang baik, meskipun mungkin cocok untuk seseorang yang hanya mencakup zona waktu AS. Seperti yang saya katakan di akhir, cara terbaik adalah SELALU membuat objek datetime "sadar" sehingga Anda dapat dengan mudah menggunakan fungsi datetime.
Karl Rudnick