วิธีทำให้เขตเวลาที่ไม่รู้จักกับวันที่และเวลาใน Python


507

ฉันต้องทำอะไร

ฉันมีวัตถุ datetime ที่ไม่ทราบเขตเวลาซึ่งฉันต้องเพิ่มเขตเวลาเพื่อที่จะสามารถเปรียบเทียบกับวัตถุ datetime ที่รับรู้เขตเวลาอื่นได้ ฉันไม่ต้องการแปลงแอปพลิเคชันทั้งหมดของฉันเป็นเขตเวลาโดยไม่รู้ตัวสำหรับกรณีแบบดั้งเดิมนี้

สิ่งที่ฉันได้ลอง

ก่อนอื่นเพื่อแสดงปัญหา:

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

ไม่น่าแปลกใจเลยที่ความล้มเหลวนี้เกิดขึ้นเนื่องจากเป็นความพยายามในการทำ Conversion แทนที่ดูเหมือนจะเป็นทางเลือกที่ดีกว่า (ตาม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
>>> 

แต่อย่างที่คุณเห็นแทนที่ดูเหมือนจะตั้งค่า tzinfo แต่ไม่ทำให้วัตถุทราบ ฉันพร้อมที่จะถอยกลับไปหาหมอสายป้อนให้มีเขตเวลาก่อนที่จะแยกมัน (ฉันใช้ dateutil สำหรับการแยกถ้าเรื่อง) แต่ที่ดูเหมือนว่าไร้สาระอย่างไม่น่าเชื่อ

นอกจากนี้ฉันได้ลองใน python 2.6 และ python 2.7 ด้วยผลลัพธ์เดียวกัน

บริบท

ฉันกำลังเขียนโปรแกรมแยกวิเคราะห์สำหรับไฟล์ข้อมูลบางไฟล์ มีรูปแบบเก่าที่ฉันต้องการสนับสนุนโดยที่สตริงวันที่ไม่มีตัวบ่งชี้เขตเวลา ฉันได้แก้ไขแหล่งข้อมูลแล้ว แต่ฉันยังต้องการสนับสนุนรูปแบบข้อมูลดั้งเดิม การแปลงครั้งเดียวของข้อมูลดั้งเดิมไม่ใช่ตัวเลือกสำหรับเหตุผลทางธุรกิจที่หลากหลาย โดยทั่วไปแล้วฉันไม่ชอบความคิดในการเข้ารหัสเขตเวลาเริ่มต้นอย่างหนักในกรณีนี้ดูเหมือนว่าจะเป็นตัวเลือกที่ดีที่สุด ฉันรู้ด้วยความมั่นใจอย่างสมเหตุสมผลว่าข้อมูลดั้งเดิมที่เป็นปัญหาทั้งหมดอยู่ใน UTC ดังนั้นฉันจึงเตรียมที่จะยอมรับความเสี่ยงของการผิดนัดในกรณีนี้


1
unaware.replace()จะกลับมาNoneถ้ามันแก้ไขunawareวัตถุในสถานที่ REPL แสดงว่า.replace()ส่งคืนdatetimeวัตถุใหม่ที่นี่
jfs

3
สิ่งที่ฉันต้องการเมื่อฉันมาที่นี่:import datetime; datetime.datetime.now(datetime.timezone.utc)
Martin Thoma

1
@MartinThoma ฉันจะใช้tzarg ที่มีชื่อเพื่อให้อ่านง่ายขึ้น:datetime.datetime.now(tz=datetime.timezone.utc)
คิวเมนตุส

คำตอบ:


594

โดยทั่วไปหากต้องการทราบเขตเวลาที่ไม่ระบุชื่อวันที่และเวลาให้ใช้วิธีการทำให้เป็นภาษาท้องถิ่น :

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ส่งคืน datetime ใหม่ซึ่งไม่ได้แก้ไขunaware)


10
ฉันรู้สึกโง่ แทนที่ส่งคืน datetime ใหม่ มันบอกว่าตรงนั้นในเอกสารด้วยและฉันก็พลาดมันไป ขอบคุณนั่นคือสิ่งที่ฉันกำลังมองหา
Mark Tozzi

2
"แทนที่จะส่งคืน datetime ใหม่" อ๋อ คำใบ้ที่ REPL ให้คุณคือมันแสดงให้คุณเห็นถึงค่าที่ส่งคืน :)
Karl Knechtel - ออกจากบ้าน

ขอบคุณฉันได้ใช้จาก dt_aware เพื่อ unware dt_unware = datetime.datetime (* (dt_aware.timetuple () [: 6])),
Sérgio

4
ถ้าเขตเวลาไม่ใช่ UTC อย่าใช้ตัวสร้างโดยตรง: aware = datetime(..., tz)ใช้.localize()แทน
jfs

1
เป็นมูลค่าการกล่าวขวัญว่าเวลาท้องถิ่นอาจไม่ชัดเจน tz.localize(..., is_dst=None)ยืนยันว่ามันไม่ใช่
jfs

166

ตัวอย่างทั้งหมดเหล่านี้ใช้โมดูลภายนอก แต่คุณสามารถบรรลุผลลัพธ์เดียวกันโดยใช้เพียงโมดูล datetime ตามที่แสดงในคำตอบ SO นี้ :

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 timezoneทำงานใน py3 แต่ไม่ใช่ py2.7
7yl4r

11
คุณควรทราบว่าdt.replace(tzinfo=timezone.utc)จะส่งกลับวันที่และเวลาใหม่ก็ไม่ได้ปรับเปลี่ยนdtในสถานที่ (ฉันจะแก้ไขเพื่อแสดงสิ่งนี้)
Blairg23

2
วิธีที่คุณอาจแทนที่จะใช้ timezone.utc จัดเตรียมเขตเวลาอื่นเป็นสตริง (เช่น "America / Chicago")
bumpkin

2
@bumpkin ดีกว่าไม่สายฉันเดาว่า:tz = pytz.timezone('America/Chicago')
Florian

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 เป็น UTC เพราะ UTC ไม่มีการเปลี่ยน DST
jfs

@JF Sebastian มีการแสดงความคิดเห็นครั้งแรก
Sérgio

1
ฉันซาบซึ้งที่คุณแสดงทั้งสองทิศทางของการแปลง
Christian Long

42

ฉันใช้คำสั่งนี้ใน Django เพื่อแปลงเวลาที่ไม่รู้ว่าเป็นการรับรู้:

from django.utils import timezone

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

2
ฉันชอบโซลูชันนี้ (+1) แต่ขึ้นอยู่กับ Django ซึ่งไม่ใช่สิ่งที่พวกเขากำลังมองหา (-1) =)
mkoistinen

3
คุณไม่ได้โต้แย้งครั้งที่สอง อาร์กิวเมนต์เริ่มต้น (ไม่มี) จะหมายถึงเขตเวลาท้องถิ่นถูกนำมาใช้โดยปริยาย เช่นเดียวกันกับ DST (ซึ่งเป็นอาร์กิวเมนต์ที่สาม
_

14

ฉันเห็นด้วยกับคำตอบก่อนหน้าและไม่เป็นไรถ้าคุณสามารถเริ่มใน UTC แต่ฉันคิดว่ามันเป็นสถานการณ์ที่พบได้ทั่วไปสำหรับผู้ที่ทำงานกับค่าที่รับรู้ tz ที่มีวันที่และเวลาที่ไม่มีเขตเวลาท้องถิ่น UTC

หากคุณเพิ่งจะไปโดยใช้ชื่อหนึ่งคงอนุมานแทนที่ () จะสามารถใช้งานได้และผลิตวัตถุตระหนักถึง datetime ที่ถูกต้อง กรณีนี้ไม่ได้.

แทนที่ (tzinfo = ... ) น่าจะเป็นแบบสุ่มในพฤติกรรมของมัน ดังนั้นจึงไร้ประโยชน์ อย่าใช้สิ่งนี้!

จำกัด เป็นฟังก์ชั่นที่ถูกต้องที่จะใช้ ตัวอย่าง:

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:00เช่น ดูstackoverflow.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.replaceNone


1
ถ้าคุณต้องการให้มัน UTC:.replace(tzinfo=dateutil.tz.UTC)
มาร์ตินโทมา

2
หนึ่งนำเข้าน้อยลงและเพียง:.replace(tzinfo=datetime.timezone.utc)
kubanczyk

9

codifies นี้ @ 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
ขอบคุณสิ่งนี้ช่วยฉัน "สร้างตราสินค้า" วัตถุ datetime แบบดิบในฐานะ "UTC" โดยไม่มีระบบก่อนสมมติว่ามันเป็นเวลาท้องถิ่นแล้วคำนวณค่าใหม่!
Nikhil VJ

3

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'

รายการ Wikipedia ของโซนเวลาที่มี


มีแบ็คพอร์ทที่อนุญาตให้ใช้ในPython 3.6 ถึง 3.8 :

sudo pip install backports.zoneinfo

แล้ว:

from backports.zoneinfo import ZoneInfo

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

ค่อนข้างใหม่สำหรับ 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.