Ubah string datetime UTC ke datetime lokal

206

Saya belum pernah mengonversi waktu ke dan dari UTC. Baru-baru ini ada permintaan agar aplikasi saya mengetahui zona waktu, dan saya telah menjalankan diri saya dalam lingkaran. Banyak informasi tentang mengonversi waktu lokal ke UTC, yang menurut saya cukup mendasar (mungkin saya juga melakukan hal yang salah), tetapi saya tidak dapat menemukan informasi tentang cara dengan mudah mengubah waktu UTC ke zona waktu pengguna akhir.

Singkatnya, dan aplikasi android mengirimkan saya (appengine app) data dan di dalam data itu ada cap waktu. Untuk menyimpan stempel waktu ke waktu utc yang saya gunakan:

datetime.utcfromtimestamp(timestamp)

Itu sepertinya berhasil. Ketika aplikasi saya menyimpan data, itu disimpan sebagai 5 jam ke depan (Saya EST -5)

Data sedang disimpan di BigTable appengine, dan ketika diambil keluar sebagai string seperti:

"2011-01-21 02:37:21"

Bagaimana cara mengubah string ini menjadi DateTime di zona waktu pengguna yang benar?

Juga, penyimpanan apa yang disarankan untuk informasi zona waktu pengguna? (Bagaimana Anda biasanya menyimpan info tz yaitu: "-5: 00" atau "EST" dll?) Saya yakin jawaban untuk pertanyaan pertama saya mungkin berisi parameter jawaban kedua.

MattoTodd
sumber
Jawaban ini menunjukkan bagaimana menyelesaikannya dengan cara yang sederhana.
juan

Jawaban:

402

Jika Anda tidak ingin menyediakan objek Anda sendiri tzinfo, lihat pustaka python-dateutil . Ini memberikan tzinfoimplementasi di atas database zoneinfo (Olson) sehingga Anda bisa merujuk ke aturan zona waktu dengan nama yang agak kanonik.

from datetime import datetime
from dateutil import tz

# METHOD 1: Hardcode zones:
from_zone = tz.gettz('UTC')
to_zone = tz.gettz('America/New_York')

# METHOD 2: Auto-detect zones:
from_zone = tz.tzutc()
to_zone = tz.tzlocal()

# utc = datetime.utcnow()
utc = datetime.strptime('2011-01-21 02:37:21', '%Y-%m-%d %H:%M:%S')

# Tell the datetime object that it's in UTC time zone since 
# datetime objects are 'naive' by default
utc = utc.replace(tzinfo=from_zone)

# Convert time zone
central = utc.astimezone(to_zone)

Edit contoh yang diperluas untuk menunjukkan strptimepenggunaan

Sunting 2 Memperbaiki penggunaan API untuk menunjukkan metode entri yang lebih baik

Sunting 3 Termasuk metode deteksi otomatis untuk zona waktu (Yarin)

Joe Holloway
sumber
1
Pada komentar saya sebelumnya, saya dapat mengimpor dateutil.tzdan menggunakan tz.tzutc()dan tz.tzlocal()sebagai objek zona waktu yang saya cari. Sepertinya basis data zona waktu pada sistem saya baik (saya check-in /usr/share/zoneinfo). Tidak yakin apa yang terjadi.
Ben Kreeger
1
@Benjamin Anda berada di jalur yang benar. The tzmodul adalah titik masuk yang benar untuk menggunakan untuk perpustakaan ini. Saya telah memperbarui jawaban saya untuk mencerminkan ini. The dateutil.zoneinfomodul I menunjukkan sebelumnya digunakan secara internal oleh tzmodul sebagai kembali jatuh jika tidak dapat menemukan sistem zoneinfo DB. Jika Anda melihat ke dalam perpustakaan Anda akan melihat bahwa ada tarball DB zoneinfo dalam paket yang digunakannya jika tidak dapat menemukan DB sistem Anda. Contoh lama saya mencoba untuk memukul DB itu secara langsung dan saya kira Anda mengalami masalah saat memuat DB pribadi itu (bukankah pada jalur python?)
Joe Holloway
2
@MattoTodd: utc -> konversi lokal terputusdateutil , gunakan pytzsaja .
jfs
1
@JFSebastian ini sudah diperbaiki di # 225
Rohmer
2
@ briankip Tergantung pada seberapa canggih aplikasi Anda, tetapi dalam aplikasi umum, ya, itu masalah. Pertama-tama tergantung pada tahun berapa itu, jumlah jam untuk menambah / mengurangi mungkin berubah karena beberapa zona waktu menghormati DST dan lainnya tidak. Kedua, aturan zona waktu diubah secara sewenang-wenang dari waktu ke waktu sehingga jika Anda bekerja dengan tanggal / waktu yang lalu, Anda perlu menerapkan aturan yang berlaku pada saat itu, bukan waktu sekarang. Itulah mengapa lebih baik menggunakan API yang memanfaatkan basis data TZ Olson di belakang layar.
Joe Holloway
45

Berikut metode tangguh yang tidak bergantung pada pustaka eksternal apa pun:

from datetime import datetime
import time

def datetime_from_utc_to_local(utc_datetime):
    now_timestamp = time.time()
    offset = datetime.fromtimestamp(now_timestamp) - datetime.utcfromtimestamp(now_timestamp)
    return utc_datetime + offset

Ini menghindari masalah waktu dalam contoh DelboyJay. Dan masalah waktu yang lebih rendah dalam amandemen Erik van Oosten.

Sebagai catatan kaki yang menarik, offset zona waktu yang dihitung di atas dapat berbeda dari ekspresi yang tampaknya setara berikut, mungkin karena perubahan aturan penghematan siang hari:

offset = datetime.fromtimestamp(0) - datetime.utcfromtimestamp(0) # NO!

Pembaruan: Cuplikan ini memiliki kelemahan dalam menggunakan offset UTC saat ini, yang mungkin berbeda dari offset UTC pada waktu input. Lihat komentar pada jawaban ini untuk solusi lain.

Untuk menyiasati waktu yang berbeda, raih waktu dari waktu yang berlalu. Inilah yang saya lakukan:

def utc2local (utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp (epoch) - datetime.utcfromtimestamp (epoch)
    return utc + offset
David Foster
sumber
Ini bekerja dengan baik dan tidak membutuhkan apa pun selain waktu dan waktu.
Mike_K
6
@ Mike_K: tetapi tidak benar. Itu tidak mendukung DST atau zona waktu yang memiliki offset utc berbeda di masa lalu karena alasan lain. Dukungan penuh tanpa pytz-bukan db tidak mungkin, lihat PEP-431. Meskipun Anda dapat menulis solusi stdlib-only yang berfungsi dalam kasus seperti itu pada sistem yang sudah memiliki db zona waktu bersejarah misalnya, Linux, OS X, lihat jawaban saya .
jfs
@ JFSebastian: Anda mengatakan kode ini tidak berfungsi secara umum tetapi juga mengatakan itu berfungsi pada OS X dan Linux. Apakah maksud Anda kode ini tidak berfungsi di Windows?
David Foster
2
@ Davidvidoster: kode Anda mungkin gagal di Linux dan OS X juga. Perbedaan antara solusi Anda dan hanya stdlib saya adalah bahwa Anda menggunakan offset saat ini yang mungkin berbeda dari offset utc saat utc_datetimeitu.
jfs
1
Panggilan yang bagus. Itu perbedaan yang sangat halus. Offset UTC juga dapat berubah seiring waktu. desah
David Foster
23

Lihat dokumentasi datetime pada objek tzinfo . Anda harus menerapkan zona waktu yang ingin Anda dukung sendiri. Ini adalah contoh di bagian bawah dokumentasi.

Berikut ini contoh sederhana:

from datetime import datetime,tzinfo,timedelta

class Zone(tzinfo):
    def __init__(self,offset,isdst,name):
        self.offset = offset
        self.isdst = isdst
        self.name = name
    def utcoffset(self, dt):
        return timedelta(hours=self.offset) + self.dst(dt)
    def dst(self, dt):
            return timedelta(hours=1) if self.isdst else timedelta(0)
    def tzname(self,dt):
         return self.name

GMT = Zone(0,False,'GMT')
EST = Zone(-5,False,'EST')

print datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S %Z')
print datetime.now(GMT).strftime('%m/%d/%Y %H:%M:%S %Z')
print datetime.now(EST).strftime('%m/%d/%Y %H:%M:%S %Z')

t = datetime.strptime('2011-01-21 02:37:21','%Y-%m-%d %H:%M:%S')
t = t.replace(tzinfo=GMT)
print t
print t.astimezone(EST)

Keluaran

01/22/2011 21:52:09 
01/22/2011 21:52:09 GMT
01/22/2011 16:52:09 EST
2011-01-21 02:37:21+00:00
2011-01-20 21:37:21-05:00a
Mark Tolonen
sumber
Terima kasih sudah menerima! Saya memperbaruinya sedikit untuk menerjemahkan waktu contoh spesifik Anda. Mem-parsing waktu menciptakan apa yang disebut oleh dokumen sebagai waktu "naif". Gunakan replaceuntuk mengatur zona waktu menjadi GMT dan menjadikannya zona waktu "sadar", lalu gunakan astimezoneuntuk mengonversi zona waktu lain.
Mark Tolonen
Maaf untuk bertukar untuk jawaban Joe. Secara teknis jawaban Anda menjelaskan bagaimana melakukannya dengan python mentah (yang bagus untuk diketahui), tetapi perpustakaan yang ia sarankan membuat saya mendapatkan solusi yang jauh lebih cepat.
MattoTodd
4
Tidak terlihat seperti itu secara otomatis menangani waktu musim panas.
Gringo Suave
@ GringoSuave: bukan itu maksudnya. Aturan untuk itu rumit dan sering berubah.
Mark Tolonen
19

Jika Anda ingin mendapatkan hasil yang benar bahkan untuk waktu yang sesuai dengan waktu lokal yang ambigu (misalnya, selama transisi DST) dan / atau offset utc lokal berbeda pada waktu yang berbeda di zona waktu lokal Anda, maka gunakan pytzzona waktu:

#!/usr/bin/env python
from datetime import datetime
import pytz    # $ pip install pytz
import tzlocal # $ pip install tzlocal

local_timezone = tzlocal.get_localzone() # get pytz tzinfo
utc_time = datetime.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S")
local_time = utc_time.replace(tzinfo=pytz.utc).astimezone(local_timezone)
jfs
sumber
10

Jawaban ini seharusnya membantu jika Anda tidak ingin menggunakan modul lain selain itu datetime.

datetime.utcfromtimestamp(timestamp)mengembalikan datetimeobjek naif (bukan yang sadar). Orang yang sadar sadar akan zona waktu, dan naif tidak. Anda ingin yang sadar jika Anda ingin mengonversi antara zona waktu (mis. Antara UTC dan waktu lokal).

Jika Anda bukan instantiating tanggal untuk memulai, tetapi Anda masih bisa membuat datetimeobjek naif dalam waktu UTC, Anda mungkin ingin mencoba kode Python 3.x ini untuk mengubahnya:

import datetime

d=datetime.datetime.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S") #Get your naive datetime object
d=d.replace(tzinfo=datetime.timezone.utc) #Convert it to an aware datetime object in UTC time.
d=d.astimezone() #Convert it to your local timezone (still aware)
print(d.strftime("%d %b %Y (%I:%M:%S:%f %p) %Z")) #Print it with a directive of choice

Berhati-hatilah untuk tidak salah berasumsi bahwa jika zona waktu Anda saat ini MDT, penghematan siang hari tidak berfungsi dengan kode di atas karena ia mencetak MST. Anda akan perhatikan bahwa jika Anda mengubah bulan ke Agustus, itu akan mencetak MDT.

Cara mudah lain untuk mendapatkan datetimeobjek sadar (juga dalam Python 3.x) adalah membuatnya dengan zona waktu yang ditentukan untuk memulai. Berikut ini contohnya, menggunakan UTC:

import datetime, sys

aware_utc_dt_obj=datetime.datetime.now(datetime.timezone.utc) #create an aware datetime object
dt_obj_local=aware_utc_dt_obj.astimezone() #convert it to local time

#The following section is just code for a directive I made that I liked.
if sys.platform=="win32":
    directive="%#d %b %Y (%#I:%M:%S:%f %p) %Z"
else:
    directive="%-d %b %Y (%-I:%M:%S:%f %p) %Z"

print(dt_obj_local.strftime(directive))

Jika Anda menggunakan Python 2.x, Anda mungkin harus subkelas datetime.tzinfodan menggunakannya untuk membantu Anda membuat datetimeobjek sadar , karena datetime.timezonetidak ada di Python 2.x.

Brōtsyorfuzthrāx
sumber
8

Jika menggunakan Django, Anda dapat menggunakan timezone.localtimemetode ini:

from django.utils import timezone
date 
# datetime.datetime(2014, 8, 1, 20, 15, 0, 513000, tzinfo=<UTC>)

timezone.localtime(date)
# datetime.datetime(2014, 8, 1, 16, 15, 0, 513000, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
frmdstryr
sumber
2

Anda bisa menggunakan panah

from datetime import datetime
import arrow

now = datetime.utcnow()

print(arrow.get(now).to('local').format())
# '2018-04-04 15:59:24+02:00'

Anda bisa memberi makan arrow.get()dengan apa saja. timestamp, string iso dll

d21d3q
sumber
1

Secara tradisional saya menunda ini ke frontend - mengirim waktu dari backend sebagai cap waktu atau format waktu lainnya dalam UTC, kemudian membiarkan klien mengetahui offset zona waktu dan membuat data ini di zona waktu yang tepat.

Untuk webapp, ini cukup mudah dilakukan dalam javascript - Anda bisa mengetahui zona waktu peramban dengan cukup mudah menggunakan metode builtin dan kemudian membuat data dari backend dengan benar.

Matt Billenstein
sumber
3
Ini berfungsi dengan baik dalam banyak kasus di mana Anda hanya melokalisasi untuk tampilan. Namun, kadang-kadang, Anda harus melakukan beberapa logika bisnis berdasarkan zona waktu pengguna dan Anda ingin dapat melakukan konversi tingkat server aplikasi.
Joe Holloway
1

Anda dapat menggunakan calendar.timegmuntuk mengonversi waktu Anda menjadi detik sejak zaman Unix dan time.localtimeuntuk mengonversi kembali:

import calendar
import time

time_tuple = time.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S")
t = calendar.timegm(time_tuple)

print time.ctime(t)

Memberi Fri Jan 21 05:37:21 2011(karena saya di UTC + 03:00 zona waktu).

myaut
sumber
1
import datetime

def utc_str_to_local_str(utc_str: str, utc_format: str, local_format: str):
    """
    :param utc_str: UTC time string
    :param utc_format: format of UTC time string
    :param local_format: format of local time string
    :return: local time string
    """
    temp1 = datetime.datetime.strptime(utc_str, utc_format)
    temp2 = temp1.replace(tzinfo=datetime.timezone.utc)
    local_time = temp2.astimezone()
    return local_time.strftime(local_format)

utc = '2018-10-17T00:00:00.111Z'
utc_fmt = '%Y-%m-%dT%H:%M:%S.%fZ'
local_fmt = '%Y-%m-%dT%H:%M:%S+08:00'
local_string = utc_str_to_local_str(utc, utc_fmt, local_fmt)
print(local_string)   # 2018-10-17T08:00:00+08:00

misalnya, zona waktu saya adalah ' +08: 00 '. input utc = 2018-10-17T00: 00: 00.111Z , maka saya akan mendapatkan output = 2018-10-17T08: 00: 00 + 08: 00

Eureka Bing
sumber
1
Anda harus memberikan deskripsi yang lebih baik tentang masalah Anda dalam teks biasa, tempat Anda menjelaskan kepada pengguna lain apa yang ingin Anda capai dan yang merupakan masalah Anda
m33n
1

Dari jawaban di sini , Anda dapat menggunakan modul waktu untuk mengonversi dari utc ke waktu lokal yang ditetapkan di komputer Anda:

utc_time = time.strptime("2018-12-13T10:32:00.000", "%Y-%m-%dT%H:%M:%S.%f")
utc_seconds = calendar.timegm(utc_time)
local_time = time.localtime(utc_seconds)
franksands
sumber
0

Ini adalah versi cepat dan kotor yang menggunakan pengaturan sistem lokal untuk mengetahui perbedaan waktu. CATATAN: Ini tidak akan berfungsi jika Anda perlu mengonversi ke zona waktu tempat sistem Anda saat ini tidak berjalan. Saya telah menguji ini dengan pengaturan Inggris di bawah zona waktu BST

from datetime import datetime
def ConvertP4DateTimeToLocal(timestampValue):
   assert isinstance(timestampValue, int)

   # get the UTC time from the timestamp integer value.
   d = datetime.utcfromtimestamp( timestampValue )

   # calculate time difference from utcnow and the local system time reported by OS
   offset = datetime.now() - datetime.utcnow()

   # Add offset to UTC time and return it
   return d + offset
DelboyJay
sumber
seharusnya datetime.fromtimestamp(ts): posix timestamp (float seconds) -> objek datetime dalam waktu lokal (ini berfungsi baik jika OS mengingat offset utc masa lalu untuk zona waktu lokal yaitu, pada Unix tetapi tidak pada Windows untuk tanggal di masa lalu)). Kalau tidak, pytz bisa digunakan.
jfs
Bolehkah saya menyarankan untuk melakukan hal berikut: offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) Saya mendapat perbedaan mikrodetik aneh tanpa itu.
Erik van Oosten
@ErikvanOosten: sudahkah Anda mencoba datetime.fromtimestamp(ts)alih-alih jawabannya?
jfs
0

Menggabungkan jawaban dari franksand ke dalam metode yang nyaman.

import calendar
import datetime

def to_local_datetime(utc_dt):
    """
    convert from utc datetime to a locally aware datetime according to the host timezone

    :param utc_dt: utc datetime
    :return: local timezone datetime
    """
    return datetime.datetime.fromtimestamp(calendar.timegm(utc_dt.timetuple()))
Martlark
sumber