Làm thế nào để làm cho múi giờ không biết thời gian nhận thức trong python


508

Tôi cần làm gì

Tôi có một đối tượng datetime không biết múi giờ, mà tôi cần thêm một múi giờ để có thể so sánh nó với các đối tượng datetime nhận biết múi giờ khác. Tôi không muốn chuyển đổi toàn bộ ứng dụng của mình sang múi giờ mà không biết về trường hợp kế thừa này.

Những gì tôi đã thử

Đầu tiên, để chứng minh vấn đề:

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

Đầu tiên, tôi đã thử 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
>>>

Điều này không có gì đáng ngạc nhiên khi điều này thất bại, vì nó thực sự đang cố gắng thực hiện một chuyển đổi. Thay thế có vẻ như là một lựa chọn tốt hơn (theo Python: Làm thế nào để có được giá trị của datetime.today () là "nhận biết múi giờ"? ):

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

Nhưng như bạn có thể thấy, thay thế dường như để đặt tzinfo, nhưng không làm cho đối tượng nhận biết. Tôi đã sẵn sàng quay trở lại để kiểm tra chuỗi đầu vào để có múi giờ trước khi phân tích cú pháp (Tôi đang sử dụng dateutil để phân tích cú pháp, nếu điều đó quan trọng), nhưng điều đó có vẻ vô cùng khó hiểu.

Ngoài ra, tôi đã thử điều này trong cả python 2.6 và python 2.7, với cùng kết quả.

Bối cảnh

Tôi đang viết một trình phân tích cú pháp cho một số tệp dữ liệu. Có một định dạng cũ tôi cần hỗ trợ khi chuỗi ngày không có chỉ báo múi giờ. Tôi đã sửa nguồn dữ liệu, nhưng tôi vẫn cần hỗ trợ định dạng dữ liệu cũ. Chuyển đổi một lần dữ liệu kế thừa không phải là một lựa chọn cho các lý do BS kinh doanh khác nhau. Mặc dù nói chung, tôi không thích ý tưởng mã hóa cứng một múi giờ mặc định, nhưng trong trường hợp này có vẻ như đó là lựa chọn tốt nhất. Tôi biết với sự tự tin hợp lý rằng tất cả các dữ liệu kế thừa được đề cập là trong UTC, vì vậy tôi đã sẵn sàng chấp nhận rủi ro vỡ nợ trong trường hợp này.


1
unaware.replace()sẽ trở lại Nonenếu nó đang sửa đổi unawaređối tượng tại chỗ. REPL cho thấy .replace()trả về một datetimeđối tượng mới ở đây.
jfs

3
Những gì tôi cần khi tôi đến đây:import datetime; datetime.datetime.now(datetime.timezone.utc)
Martin Thoma

1
@MartinThoma Tôi sẽ sử dụng tzarg được đặt tên để dễ đọc hơn:datetime.datetime.now(tz=datetime.timezone.utc)
Acumenus

Câu trả lời:


594

Nói chung, để nhận biết múi giờ thời gian ngây thơ, hãy sử dụng phương thức bản địa hóa :

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

Đối với múi giờ UTC, không thực sự cần thiết phải sử dụng localizevì không có tính toán thời gian tiết kiệm ánh sáng ban ngày để xử lý:

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

làm. ( .replacetrả về một datetime mới; nó không sửa đổi unaware.)


10
Chà, tôi cảm thấy thật ngớ ngẩn. Thay thế trả về một datetime mới. Nó nói rằng ngay trong tài liệu cũng vậy, và tôi hoàn toàn bỏ lỡ điều đó. Cảm ơn, đó chính xác là những gì tôi đang tìm kiếm.
Đánh dấu vào

2
"Thay thế trả về một datetime mới." Vâng. Gợi ý mà REPL cung cấp cho bạn là nó hiển thị cho bạn giá trị được trả về. :)
Karl Knechtel - xa nhà

cảm ơn, tôi đã sử dụng từ dt_kn để unware dt_unware = datetime.datetime (* (dt_kn.timetuple () [: 6])),
Sérgio

4
nếu múi giờ không phải là UTC thì đừng sử dụng trực tiếp hàm tạo : aware = datetime(..., tz), .localize()thay vào đó hãy sử dụng .
JFS

1
Điều đáng nói là giờ địa phương có thể mơ hồ. tz.localize(..., is_dst=None)khẳng định là không phải.
jfs

167

Tất cả các ví dụ này sử dụng một mô-đun bên ngoài, nhưng bạn có thể đạt được kết quả tương tự chỉ bằng mô-đun datetime, như được trình bày trong câu trả lời SO này :

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'

Ít phụ thuộc hơn và không có vấn đề pytz.

LƯU Ý: Nếu bạn muốn sử dụng điều này với python3 và python2, bạn cũng có thể sử dụng điều này cho việc nhập múi giờ (mã hóa cứng cho 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
Câu trả lời rất tốt để ngăn chặn các pytzvấn đề, tôi rất vui vì tôi đã cuộn xuống một chút! pytzThật sự không muốn giải quyết với các máy chủ từ xa của tôi :)
Tregoreg

7
Lưu ý rằng from datetime import timezonehoạt động trong py3 nhưng không phải py2.7.
7yl4r

11
Bạn nên lưu ý rằng dt.replace(tzinfo=timezone.utc)trả về một datetime mới, nó không sửa đổi dttại chỗ. (Tôi sẽ chỉnh sửa để hiển thị điều này).
Blairg23

2
Làm thế nào bạn có thể, thay vì sử dụng timezone.utc, cung cấp một múi giờ khác dưới dạng chuỗi (ví dụ: "America / Chicago")?
gập bụng

2
@bumpkin muộn còn hơn không, tôi đoán:tz = pytz.timezone('America/Chicago')
Florian

82

Tôi đã sử dụng từ dt_war đến dt_unwar

dt_unaware = dt_aware.replace(tzinfo=None)

và dt_unware để dt_ biết

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

nhưng trả lời trước cũng là một giải pháp tốt.


2
bạn có thể sử dụng localtz.localize(dt_unware, is_dst=None)để đưa ra một ngoại lệ nếu dt_unwaređại diện cho giờ địa phương không tồn tại hoặc mơ hồ (lưu ý: không có vấn đề nào như vậy trong lần sửa đổi trước câu trả lời của bạn, đó localtzlà UTC vì UTC không có chuyển đổi DST
jfs

@JF Sebastian, bình luận đầu tiên được áp dụng
Sérgio

1
Tôi đánh giá cao bạn hiển thị cả hai hướng của chuyển đổi.
Christian Long

42

Tôi sử dụng tuyên bố này trong Django để chuyển đổi thời gian không biết sang nhận thức:

from django.utils import timezone

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

2
Tôi thích giải pháp này (+1), nhưng nó phụ thuộc vào Django, đây không phải là thứ họ đang tìm kiếm (-1). =)
mkoistinen

3
Bạn không thực sự là đối số thứ hai. Đối số mặc định (Không có) sẽ có nghĩa là múi giờ cục bộ được sử dụng hoàn toàn. Tương tự với DST (là đối số thứ
ba_

14

Tôi đồng ý với các câu trả lời trước đó và sẽ ổn nếu bạn bắt đầu ở UTC. Nhưng tôi nghĩ đó cũng là một kịch bản phổ biến để mọi người làm việc với giá trị nhận biết tz có một mốc thời gian có múi giờ địa phương không UTC.

Nếu bạn chỉ đi theo tên, một người có thể sẽ suy ra thay thế () sẽ được áp dụng và tạo ra đối tượng nhận biết đúng thời gian. Đây không phải là trường hợp.

sự thay thế (tzinfo = ...) dường như là ngẫu nhiên trong hành vi của nó . Do đó nó là vô dụng. Đừng dùng cái này!

nội địa hóa là chức năng chính xác để sử dụng. Thí dụ:

localdatetime_aware = tz.localize(datetime_nonaware)

Hoặc một ví dụ đầy đủ hơn:

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

cung cấp cho tôi giá trị thời gian nhận biết múi giờ của giờ địa phương hiện tại:

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

3
Điều này cần nhiều upvote hơn, cố gắng thực hiện replace(tzinfo=...)trên múi giờ khác với UTC sẽ làm mất thời gian của bạn. Tôi đã -07:53thay vì -08:00ví dụ. Xem stackoverflow.com/a/13994611/1224827
Blairg23

Bạn có thể đưa ra một ví dụ tái sản xuất replace(tzinfo=...)có hành vi bất ngờ?
xjcl

11

Sử dụng dateutil.tz.tzlocal()để có được múi giờ trong việc sử dụng 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())

Lưu ý rằng datetime.astimezonetrước tiên sẽ chuyển đổi datetimeđối tượng của bạn sang UTC sau đó thành múi giờ, giống như gọi datetime.replacevới thông tin múi giờ ban đầu None.


1
Nếu bạn muốn biến nó thành UTC:.replace(tzinfo=dateutil.tz.UTC)
Martin Thoma

2
Một lần nhập ít hơn và chỉ:.replace(tzinfo=datetime.timezone.utc)
kubanchot

9

Điều này mã hóa câu trả lời của @ Sérgio và @ unutbu . Nó sẽ "chỉ hoạt động" với một pytz.timezoneđối tượng hoặc chuỗi múi giờ 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) 

Điều này có vẻ giống như những gì datetime.localize()(hay .inform()hay .awarify()) nên làm, chấp nhận cả hai dây và các đối tượng múi giờ cho lập luận tz và mặc định UTC nếu không có múi giờ được xác định.


1
Cảm ơn, điều này đã giúp tôi "thương hiệu" một đối tượng datetime thô là "UTC" mà trước tiên hệ thống không cho rằng đó là giờ địa phương và sau đó tính toán lại các giá trị!
Nikhil VJ

4

Python 3.9 thêm zoneinfomô-đun để bây giờ chỉ cần thư viện chuẩn!

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

Đính kèm múi giờ:

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

Đính kèm múi giờ địa phương của hệ thống:

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

Sau đó, nó được chuyển đổi chính xác sang các múi giờ khác:

>>> 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 danh sách các múi giờ có sẵn


Có một cổng sau để cho phép sử dụng trong Python 3.6 đến 3.8 :

sudo pip install backports.zoneinfo

Sau đó:

from backports.zoneinfo import ZoneInfo

trên Windows, bạn cũng cần phảipip install tzdata
MrFuppes

0

Trong định dạng của câu trả lời của unutbu; Tôi đã tạo ra một mô-đun tiện ích xử lý những thứ như thế này, với cú pháp trực quan hơn. Có thể được cài đặt với 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

đối với những người chỉ muốn tạo một mốc thời gian nhận biết múi giờ

import datetime
import pytz

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

0

khá mới đối với Python và tôi gặp phải vấn đề tương tự. Tôi thấy giải pháp này khá đơn giản và đối với tôi nó hoạt động tốt (Python 3.6):

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

0

Thay đổi giữa các múi giờ

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.