Chuyển đổi một datetime UTC python thành datetime cục bộ chỉ sử dụng thư viện chuẩn python?


189

Tôi có một cá thể datetime python được tạo bằng datetime.utcnow () và tồn tại trong cơ sở dữ liệu.

Để hiển thị, tôi muốn chuyển đổi thể hiện datetime được lấy từ cơ sở dữ liệu sang datetime cục bộ bằng cách sử dụng múi giờ cục bộ mặc định (nghĩa là, như datetime được tạo bằng datetime.now ()).

Làm cách nào tôi có thể chuyển đổi datetime UTC thành datetime cục bộ chỉ bằng thư viện chuẩn python (ví dụ: không phụ thuộc pytz)?

Có vẻ như một giải pháp sẽ là sử dụng datetime.astimezone (tz), nhưng làm thế nào bạn có được múi giờ địa phương mặc định?


Định dạng nào là thời gian tồn tại cho cơ sở dữ liệu? Nếu đó là định dạng tiêu chuẩn, có thể bạn không cần thực hiện bất kỳ chuyển đổi nào.
Apalala

1
Câu trả lời này cho thấy một cách đơn giản để sử dụng pytz .
juan

Câu trả lời:


275

Trong Python 3.3+:

from datetime import datetime, timezone

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

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

Sử dụng pytz(cả 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

Thí dụ

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

Đầu ra

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
Con trăn 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

Lưu ý: phải tính đến DST và sự thay đổi gần đây của bù utc cho múi giờ MSK.

Tôi không biết liệu các giải pháp không pytz có hoạt động trên Windows hay không.


1
'bình thường hóa' làm gì?
avi

4
@avi: nói chung: pytz: Tại sao cần chuẩn hóa khi chuyển đổi giữa các múi giờ? . Lưu ý: Không cần thiết với việc triển khai hiện tại .normalize()vì múi giờ nguồn cho .astimezone()là UTC.
JFS

1
Tôi nhận được TypeError: replace() takes no keyword argumentskhi thử giải pháp pytz.
Craig

@Craig mã hoạt động như là, như đầu ra trong câu trả lời chứng minh. Tạo ví dụ mã tối thiểu hoàn chỉnh dẫn đến TypeError, đề cập đến các phiên bản Python, pytz mà bạn sử dụng và đăng nó dưới dạng câu hỏi Stack Overflow riêng biệt.
jfs

Mẹo nhanh cho giải pháp Python 3.3+. Để đi theo hướng ngược lại (Bản địa của UTC), cách này hoạt động: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
jasonrhaas

50

Bạn không thể làm điều đó chỉ với thư viện tiêu chuẩn vì thư viện tiêu chuẩn không có bất kỳ múi giờ nào. Bạn cần pytz hoặc 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())

Hoặc tốt, bạn có thể làm điều đó mà không cần pytz hoặc dateutil bằng cách thực hiện các múi giờ của riêng bạn. Nhưng điều đó sẽ là ngớ ngẩn.


Cảm ơn, tôi sẽ giữ nó trong tay nếu tôi cần một giải pháp đầy đủ. Dateutil có hỗ trợ Python 3.1 (nói Python 2.3+ nhưng đáng ngờ)?
Nitro Zark


pytz thực sự xử lý thay đổi DST tốt hơn.
Lennart Regebro

2
Cập nhật: pytz và dateutil đều hỗ trợ Python 3 hiện nay.
Lennart Regebro

1
@JFSebastian: dateutil không thể phân biệt giữa hai lần 1:30 xảy ra trong quá trình chuyển đổi DST. Nếu bạn cần có khả năng phân biệt giữa hai lần đó, cách giải quyết nó là sử dụng pytz, có thể xử lý nó.
Lennart Regebro

8

Bạn không thể làm điều đó với thư viện tiêu chuẩn. Sử dụng mô-đun pytz, bạn có thể chuyển đổi bất kỳ đối tượng datetime ngây thơ / nhận thức nào sang bất kỳ múi giờ nào khác. Hãy xem một số ví dụ bằng Python 3.

Các đối tượng ngây thơ được tạo thông qua phương thức lớp utcnow()

Để chuyển đổi một đối tượng ngây thơ sang bất kỳ múi giờ nào khác, trước tiên, bạn phải chuyển đổi nó thành đối tượng datetime nhận thức . Bạn có thể sử dụng replacephương thức để chuyển đổi một đối tượng datetime ngây thơ thành một đối tượng datetime nhận thức . Sau đó, để chuyển đổi một đối tượng datetime nhận thức sang bất kỳ múi giờ nào khác, bạn có thể sử dụng astimezonephương thức.

Biến pytz.all_timezonescung cấp cho bạn danh sách tất cả các múi giờ có sẵn trong mô-đun 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)

Các đối tượng ngây thơ được tạo thông qua phương thức lớp now()

Bởi vì nowphương thức trả về ngày và giờ hiện tại, do đó bạn phải làm cho múi giờ đối tượng datetime nhận biết trước. Các localize chức năng chuyển đổi một ngây thơ đối tượng datetime vào một đối tượng datetime timezone-aware. Sau đó, bạn có thể sử dụng astimezonephương thức để chuyển đổi nó thành múi giờ khác.

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)

6

Tôi nghĩ rằng tôi đã tìm ra nó: tính số giây kể từ epoch, sau đó chuyển đổi thành một khung thời gian cục bộ bằng time.localtime, và sau đó chuyển đổi cấu trúc thời gian trở lại thành 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 )

Nó áp dụng chính xác DST mùa hè / mùa đông:

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

1
Ừ Nhưng điều đó nên hoạt động trên Unix, ít nhất. Đối với Windows, điều này có thể không chính xác nếu quy tắc tiết kiệm ánh sáng ban ngày của bạn thay đổi kể từ ngày được chuyển đổi. Tại sao bạn không muốn sử dụng một thư viện?
Lennart Regebro

Tôi chỉ cần chuyển đổi utc / local nên việc kéo một thư viện như vậy dường như là quá mức cần thiết. Vì tôi là người dùng duy nhất của ứng dụng nên tôi có thể sống với một số hạn chế. Điều này cũng tránh việc quản lý các vấn đề liên quan đến sự phụ thuộc (hỗ trợ Python 3?, Windows? Chất lượng ...) sẽ tốn kém hơn so với làm việc với tập lệnh nhỏ của tôi.
Nitro Zark

1
Tốt. nếu bạn cần Python3, thì bạn đã hết may mắn. Nhưng nếu không thì việc nghiên cứu và đưa ra giải pháp của riêng bạn là quá mức so với việc sử dụng thư viện.
Lennart Regebro

1
+1 (để bù cho downvote: có thể là yêu cầu hợp lệ để tìm kiếm các giải pháp chỉ có stdlib) với cảnh báo @Lennart đã đề cập. Cho rằng dateutil có thể thất bại trên cả Unix và Windows. btw, bạn có thể trích xuất utctotimestamp(utc_dt) -> (seconds, microseconds)từ mã của bạn cho rõ ràng, thấy Python 2.6-3.x thực hiện
JFS

1
Một pytz (và dateutil) hoạt động trên Python 3 từ bây giờ, vì vậy các vấn đề về Python3 / Windows / Chất lượng không còn là vấn đề nữa.
Lennart Regebro

6

Dựa trên nhận xét của Alexei. Điều này cũng sẽ làm việc cho DST.

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)

4

Thư viện Python chuẩn không đi kèm với bất kỳ tzinfotriển khai nào cả. Tôi luôn coi đây là một thiếu sót đáng ngạc nhiên của mô đun datetime.

Các tài liệu cho lớp tzinfo không đi kèm với một số ví dụ hữu ích. Tìm khối mã lớn ở cuối phần.


1
Tôi đoán về việc triển khai cốt lõi giống như vậy là vì cơ sở dữ liệu múi giờ cần được cập nhật thường xuyên vì một số quốc gia không có quy tắc cố định để xác định các giai đoạn DST. Nếu tôi không nhầm, ví dụ JVM cần được cập nhật để có được cơ sở dữ liệu múi giờ cuối cùng ...
Nitro Zark

@Nitro Zark, ít nhất họ nên có một cái cho UTC. Các osmô-đun có thể được cung cấp một cho giờ địa phương dựa trên các chức năng hệ điều hành.
Đánh dấu tiền chuộc

1
Tôi đã kiểm tra danh sách các tính năng mới trong 3.2 và họ đã thêm múi giờ UTC vào 3.2 ( docs.python.org/dev/whatsnew/3.2.html#datetime ). Dường như không có múi giờ địa phương mặc dù ...
Nitro Zark

2

Python 3.9 thêm zoneinfomô-đun để bây giờ có thể thực hiện như sau (chỉ 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

Trung Âu là 1 hoặc 2 giờ trước UTC, cũng vậy local_aware:

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

như str:

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

Windows không có cơ sở dữ liệu múi giờ hệ thống, vì vậy ở đây cần có thêm gói:

pip install tzdata  

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

bạn thậm chí không cần zoneinfoở đây - xem phần đầu tiên của câu trả lời của jfs (phần Python3.3 +) ;-)
MrFuppes

@MrFuppes Tôi nghĩ ZoneInfo('localtime')rõ ràng hơn nhiều tz=None(sau tất cả, người ta có thể mong đợi tz=Noneloại bỏ múi giờ hoặc trả lại UTC thay vì giờ địa phương).
xjcl

@MrFuppes Cộng với câu hỏi có thể liên quan đến việc OP có trang web và muốn hiển thị dấu thời gian của người dùng trong giờ địa phương của họ. Vì vậy, bạn sẽ phải chuyển đổi sang múi giờ rõ ràng của người dùng thay vì giờ địa phương.
xjcl

0

Một cách đơn giản (nhưng có thể thiếu sót) hoạt động trong Python 2 và 3:

import time
import datetime

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

Ưu điểm của nó là không quan trọng để viết một hàm nghịch đảo


1
Điều này cho kết quả sai, vì time.timezonekhông xem xét tiết kiệm ánh sáng ban ngày.
Guyarad

0

Cách dễ nhất tôi đã tìm thấy là lấy thời gian bù cho vị trí của bạn , sau đó trừ đi từ giờ.

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

Điều này làm việc cho tôi, trong Python 3.5.2.


0

Đây là một cách khác để thay đổi múi giờ theo định dạng datetime (Tôi biết tôi đã lãng phí năng lượng của mình vào việc này nhưng tôi đã không thấy trang này vì vậy tôi không biết làm thế nào) mà không cần min. và giây vì tôi không cần nó cho dự án của mình:

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)

Sử dụng timedelta để chuyển đổi giữa các múi giờ. Tất cả những gì bạn cần là sự bù giờ tính theo giờ giữa các múi giờ. Không phải nghịch ngợm với các ranh giới cho tất cả 6 yếu tố của một đối tượng datetime. timedelta xử lý năm nhuận, bước nhảy vọt thế kỷ, vv, quá, dễ dàng. Bạn phải 'từ thời gian nhập dữ liệu timedelta'. Sau đó, nếu phần bù là một biến số theo giờ: timeout = timein + timedelta (hours = offset), trong đó timein và timeout là các đối tượng datetime. ví dụ: timein + timedelta (hours = -8) chuyển đổi từ GMT sang PST.
Karl Rudnick

0

Đây là một cách khủng khiếp để làm điều đó nhưng nó tránh tạo ra một định nghĩa. Nó đáp ứng yêu cầu gắn bó với thư viện Python3 cơ bản.

# 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])

Mặc dù điều này rất thú vị và tôi sẽ sử dụng phương pháp này nhưng nó không chỉ sử dụng stdlib. Nó đang sử dụng Pandas để thực hiện chuyển đổi pandas.pydata.org/pandas-docs/version/0.25/reference/api/ Kẻ
Tim Hughes

-3

Sử dụng timedelta để chuyển đổi giữa các múi giờ. Tất cả những gì bạn cần là sự bù giờ tính theo giờ giữa các múi giờ. Không phải nghịch ngợm với các ranh giới cho tất cả 6 yếu tố của một đối tượng datetime. timedelta xử lý năm nhuận, bước nhảy vọt thế kỷ, vv, quá, dễ dàng. Bạn phải là đầu tiên

from datetime import datetime, timedelta

Sau đó, nếu offsetlà đồng bằng múi giờ tính bằng giờ:

timeout = timein + timedelta(hours = offset)

trong đó timein và timeout là các đối tượng datetime. ví dụ

timein + timedelta(hours = -8)

chuyển đổi từ GMT sang PST.

Vậy, làm thế nào để xác định offset? Đây là một hàm đơn giản với điều kiện bạn chỉ có một vài khả năng chuyển đổi mà không sử dụng các đối tượng datetime là múi giờ "nhận biết" mà một số câu trả lời khác làm tốt. Một chút thủ công, nhưng đôi khi rõ ràng là tốt nhất.

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)

Sau khi xem xét rất nhiều câu trả lời và chơi xung quanh với mã chặt chẽ nhất mà tôi có thể nghĩ ra (bây giờ), có vẻ như tốt nhất là tất cả các ứng dụng, trong đó thời gian là quan trọng và phải có thời gian hỗn hợp, nên nỗ lực thực sự để tạo ra tất cả các đối tượng thời gian "Nhận thức". Sau đó, có vẻ như câu trả lời đơn giản nhất là:

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

để chuyển đổi sang GMT chẳng hạn. Tất nhiên, để chuyển đổi sang / từ bất kỳ múi giờ nào bạn muốn, cục bộ hoặc nếu không, chỉ cần sử dụng chuỗi múi giờ thích hợp mà pytz hiểu (từ pytz.all_timezones). Thời gian tiết kiệm ánh sáng ban ngày sau đó cũng được tính đến.


1
Đây không phải là ý tưởng tốt. DST không thay đổi vào cùng một ngày cho toàn thế giới. Sử dụng các hàm datetime để làm điều này.
Gabe

@gabe - Hiểu rằng bảng không phải là một ý tưởng tốt, mặc dù nó có thể phù hợp với ai đó chỉ bao gồm các múi giờ của Hoa Kỳ. Như tôi đã nói ở cuối, cách tốt nhất là LUÔN LUÔN làm cho các đối tượng datetime "nhận biết" để bạn có thể dễ dàng sử dụng các hàm datetime.
Karl Rudnick
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.