Python'da farkında olmayan bir tarih saati saatini nasıl bilinçlendirirsiniz?


507

Yapmam gerekenler

Diğer saat dilimine duyarlı datetime nesneleri ile karşılaştırmak için bir saat dilimi eklemeniz gereken bir saat dilimi farkında olmayan datetime nesnesi var. Uygulamamı bu eski durum için habersiz saat dilimine dönüştürmek istemiyorum.

Ne Denedim

İlk olarak, sorunu göstermek için:

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

İlk olarak astimezon denedim:

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

Aslında bir dönüşüm yapmaya çalıştığı için bunun başarısız olması çok şaşırtıcı değil. Değiştir daha iyi bir seçim gibi görünüyordu ( Python'a göre: "Saat dilimi farkında" bir datetime.today () değeri nasıl elde edilir? ):

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

Ancak gördüğünüz gibi, yerine koymak tzinfo'yu ayarlıyor gibi görünüyor, ancak nesneyi farkında kılmıyor. Ayrıştırmadan önce bir zaman dilimi için giriş dizesini doktora geri düşmeye hazır alıyorum (ayrıştırmak için dateutil kullanıyorum, bu önemliyse), ama bu inanılmaz derecede küstah görünüyor.

Ayrıca, bunu hem python 2.6 hem de python 2.7'de aynı sonuçlarla denedim.

bağlam

Bazı veri dosyaları için ayrıştırıcı yazıyorum. Tarih dizesinin saat dilimi göstergesi olmadığında desteklemem gereken eski bir biçim var. Veri kaynağını zaten düzelttim, ancak hala eski veri biçimini desteklemem gerekiyor. Eski verilerin bir defalık dönüşümü, çeşitli işletme BS nedenleri için bir seçenek değildir. Genel olarak, varsayılan bir saat dilimini zor kodlama fikrinden hoşlanmıyorum, bu durumda en iyi seçenek gibi görünüyor. Söz konusu tüm eski verilerin UTC'de olduğunu makul bir şekilde biliyorum, bu yüzden bu durumda varsayılan olarak kabul etme riskini kabul etmeye hazırım.


1
unaware.replace()nesne yerinde Nonedeğiştiriliyorsa geri dönecektir unaware. REPL, burada .replace()yeni bir datetimenesne döndürdüğünü gösterir .
jfs

3
Buraya geldiğimde ihtiyacım olan şey:import datetime; datetime.datetime.now(datetime.timezone.utc)
Martin Thoma

1
@MartinThoma tzDaha okunabilir olabilmek için adlandırılmış arg'u kullanacağım :datetime.datetime.now(tz=datetime.timezone.utc)
Acumenus

Yanıtlar:


594

Genel olarak, saf bir tarih-saat dilimine duyarlı hale getirmek için localize yöntemini kullanın :

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 saat dilimi için, kullanmak için gerçekten gerekli değildir, localizeçünkü işleyecek yaz saati uygulaması yoktur:

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

İşler. ( .replaceyeni bir datetime döndürür; değiştirmez unaware.)


10
Kendimi aptal gibi hissediyorum. Değiştir yeni bir tarih / saat döndürür. Belgelerde de orada olduğunu söylüyor ve bunu tamamen özledim. Teşekkürler, tam da aradığım şey buydu.
Mark Tozzi

2
"Değiştir yeni bir tarih / saat döndürür." Evet. REPL'nin size verdiği ipucu, size döndürülen değeri göstermesidir. :)
Karl Knechtel - evden uzakta

teşekkürler, dt_aware den dt_unware = datetime.datetime (* (dt_aware.timetuple () [: 6])),
Sérgio

4
saat dilimi UTC değilse yapıcıyı doğrudan aware = datetime(..., tz)kullanmayın: .localize()bunun yerine kullanın.
jfs

1
Yerel saatin belirsiz olabileceğinden bahsetmek gerekir. tz.localize(..., is_dst=None)olmadığını ileri sürüyor.
jfs

166

Bu örneklerin tümü harici bir modül kullanır, ancak bu SO yanıtında da gösterildiği gibi yalnızca datetime modülünü kullanarak aynı sonucu elde edebilirsiniz :

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'

Daha az bağımlılık ve pytz sorunu yok.

NOT: Bunu python3 ve python2 ile kullanmak istiyorsanız, bunu saat dilimi içe aktarma için de kullanabilirsiniz (UTC için sabit kodlanmış):

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
pytzSorunları önlemek için çok iyi bir cevap , biraz aşağı kaydırdım sevindim! pytzUzaktaki sunucularımla gerçekten uğraşmak istemedim :)
Tregoreg

7
Not from datetime import timezonePY3 ancak py2.7 çalışır.
7yl4r

11
Sen unutmamalıdır dt.replace(tzinfo=timezone.utc)o değiştirmez, yeni datetime döndüren dtbir yerde. (Bunu göstermek için düzenleyeceğim).
Blairg23

2
Timezone.utc yerine dize olarak farklı bir saat dilimi nasıl sağlayabilirsiniz (örn. "America / Chicago")?
bumpkin

2
@bumpkin hiç olmadığı kadar iyi geç, sanırım:tz = pytz.timezone('America/Chicago')
Florian

82

Ben dt_aware dt_unaware kullanmak vardı

dt_unaware = dt_aware.replace(tzinfo=None)

ve dt_unware - dt_aware arasında

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

ama daha önce cevap vermek de iyi bir çözümdür.


2
mevcut olmayan veya belirsiz yerel saati temsil localtz.localize(dt_unware, is_dst=None)ediyorsa bir istisna oluşturmak için kullanabilirsiniz dt_unware(not: localtzUTC'nin DST geçişleri olmadığı için UTC'nin nerede olduğu önceki cevabınızda böyle bir sorun yoktu
jfs

@JF Sebastian, ilk yorum uygulandı
Sérgio

1
Dönüşümün her iki yönünü de gösterdiğiniz için teşekkür ederiz.
Christian Long

42

Ben farkında olmayan bir zamanı farkında bir dönüştürmek için Django bu ifadeyi kullanın:

from django.utils import timezone

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

2
Bu çözümü beğendim (+1), ancak aradıkları şey Django'ya bağımlıdır (-1). =)
mkoistinen

3
Aslında ikinci argüman değilsiniz. Varsayılan argüman (Yok) yerel saat dilimi dolaylı kullanılır anlamına gelecektir. DST ile aynı (üçüncü argüman_
Oli

14

Önceki cevaplara katılıyorum ve UTC'de başlamaya hazırsanız sorun yok. Ancak, insanların yerel olmayan bir yerel saat dilimi olan bir datetime değerine sahip bir tz farkında değeri ile çalışması da yaygın bir senaryodur .

Eğer sadece isme gidecek olursanız, muhtemelen replace () uygulanacaktır ve doğru datetime duyarlı nesneyi üretecektir. Olay bu değil.

replace (tzinfo = ...) davranışında rastgele görünüyor . Bu nedenle işe yaramaz. Bunu kullanmayın!

localize, kullanılacak doğru işlevdir. Misal:

localdatetime_aware = tz.localize(datetime_nonaware)

Veya daha eksiksiz bir örnek:

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

geçerli yerel saatin saat diliminde farkında bir tarih / saat değeri veriyor:

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

3
Bunun daha fazla oy alması gerekiyor, replace(tzinfo=...)UTC dışında bir saat diliminde yapmaya çalışmak datetime zamanınızı kirletecek . Bende -07:53yerine -08:00mesela. Bkz. Stackoverflow.com/a/13994611/1224827
Blairg23

replace(tzinfo=...)Beklenmedik davranışlara sahip olmanın tekrarlanabilir bir örneğini verebilir misiniz ?
xjcl

11

dateutil.tz.tzlocal()Aşağıdakileri kullanımınızda saat dilimini almak için kullanın datetime.datetime.now()ve 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())

Not datetime.astimezoneöncelikle dönüştürür datetimearayarak aynıdır saat dilimine, içine daha sonra UTC nesneyi datetime.replaceorijinal Saat diliminin varlık ile None.


1
UTC yapmak istiyorsanız:.replace(tzinfo=dateutil.tz.UTC)
Martin Thoma

2
Bir ithalat daha az ve sadece:.replace(tzinfo=datetime.timezone.utc)
kubanczyk

9

Bu @ Sérgio ve @ unutbu'nun cevaplarını kodlar . Bir pytz.timezonenesne veya bir IANA Zaman Dilimi dizesiyle "çalışır" .

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) 

Bu, datetime.localize()(veya .inform()veya .awarify()) yapması gereken gibi görünüyor , tz argümanı için hem dizeleri hem de saat dilimi nesnelerini kabul edin ve saat dilimi belirtilmemişse varsayılan olarak UTC'ye ayarlayın.


1
Teşekkürler, bu bana bir ham datetime nesnesini "UTC" olarak "marka" yardımcı oldu, sistem ilk önce yerel saat kabul ve daha sonra değerleri yeniden hesaplamak olmadan!
Nikhil VJ

3

Python 3.9 zoneinfomodülü ekler, böylece artık sadece standart kütüphane gereklidir!

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

Bir saat dilimi ekleyin:

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

Sistemin yerel saat dilimini ekleyin:

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

Daha sonra düzgün bir şekilde diğer zaman dilimlerine dönüştürülür:

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

Kullanılabilir zaman dilimlerinin Wikipedia listesi


Python 3.6 ila 3.8'de kullanıma izin veren bir backport var :

sudo pip install backports.zoneinfo

Sonra:

from backports.zoneinfo import ZoneInfo

0

Unutbu'nun cevabı formatında; Daha sezgisel sözdizimi ile böyle şeyleri işleyen bir yardımcı program modülü yaptım. Pip ile monte edilebilir.

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

sadece saat dilimini bilinçli bir tarihte yapmak isteyenler için

import datetime
import pytz

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

0

Python için oldukça yeni ve aynı sorunla karşılaştım. Bu çözümü oldukça basit buluyorum ve benim için iyi çalışıyor (Python 3.6):

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

0

Zaman dilimleri arasında geçiş yapma

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.