Chuyển đổi chuỗi thời gian UTC thành datetime cục bộ


206

Tôi chưa bao giờ phải chuyển đổi thời gian đến và từ UTC. Gần đây có một yêu cầu để ứng dụng của tôi được biết múi giờ và tôi đã tự chạy theo vòng tròn. Rất nhiều thông tin về việc chuyển đổi giờ địa phương sang UTC, điều mà tôi thấy khá sơ đẳng (có thể tôi cũng đang làm sai), nhưng tôi không thể tìm thấy bất kỳ thông tin nào về việc dễ dàng chuyển đổi thời gian UTC sang múi giờ của người dùng cuối.

Tóm lại, và ứng dụng Android sẽ gửi cho tôi dữ liệu (ứng dụng appengine) và trong dữ liệu đó là dấu thời gian. Để lưu trữ dấu thời gian đó đến thời gian utc tôi đang sử dụng:

datetime.utcfromtimestamp(timestamp)

Điều đó dường như đang làm việc. Khi ứng dụng của tôi lưu trữ dữ liệu, nó sẽ được lưu trữ trước 5 giờ (Tôi là EST -5)

Dữ liệu đang được lưu trữ trên BigTable của appengine và khi được truy xuất, nó xuất hiện dưới dạng một chuỗi như vậy:

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

Làm cách nào để chuyển đổi chuỗi này thành DateTime trong múi giờ chính xác của người dùng?

Ngoài ra, lưu trữ được đề xuất cho thông tin múi giờ của người dùng là gì? (Làm thế nào để bạn thường lưu trữ thông tin tz, ví dụ: "-5: 00" hoặc "EST", v.v.?) Tôi chắc chắn câu trả lời cho câu hỏi đầu tiên của tôi có thể chứa tham số câu trả lời thứ hai.



Câu trả lời này cho thấy làm thế nào để giải quyết điều này một cách đơn giản.
juan

Câu trả lời:


402

Nếu bạn không muốn cung cấp các tzinfođối tượng của riêng mình , hãy xem thư viện python-dateutil . Nó cung cấp các tzinfotriển khai trên cơ sở dữ liệu của Zoneinfo (Olson) để bạn có thể tham khảo các quy tắc múi giờ theo một tên có phần chính tắc.

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)

Chỉnh sửa ví dụ mở rộng để hiển thị strptimecách sử dụng

Chỉnh sửa 2 Sử dụng API cố định để hiển thị phương pháp điểm vào tốt hơn

Chỉnh sửa 3 Bao gồm các phương pháp tự động phát hiện cho múi giờ (Yarin)


1
Theo nhận xét trước đây của tôi, tôi đã có thể nhập dateutil.tzvà sử dụng tz.tzutc()tz.tzlocal()như các đối tượng múi giờ mà tôi đang tìm kiếm. Có vẻ như cơ sở dữ liệu múi giờ trên hệ thống của tôi là tốt (tôi đã đăng ký /usr/share/zoneinfo). Không chắc chắn những gì đã lên.
Ben Kreeger

1
@Benjamin Bạn đang đi đúng hướng. Các tzmô-đun là điểm mấu chốt chính xác được sử dụng cho thư viện này. Tôi đã cập nhật câu trả lời của mình để phản ánh điều này. Các dateutil.zoneinfomô-đun Tôi đã thấy trước đây được sử dụng trong nội bộ của các tzmô-đun như một mùa thu trở lại nếu nó không thể xác định vị trí của hệ thống zoneinfo DB. Nếu bạn nhìn vào bên trong thư viện, bạn sẽ thấy rằng có một tarball DB khu vực trong gói mà nó sử dụng nếu nó không thể tìm thấy DB của hệ thống của bạn. Ví dụ cũ của tôi đã cố gắng tấn công DB trực tiếp và tôi đoán bạn đang gặp sự cố khi tải DB riêng đó (không phải trên đường python bằng cách nào đó?)
Joe Holloway


1
@JFSebastian cái này được sửa trong # 225
Rohmer

2
@briankip Nó phụ thuộc vào mức độ phức tạp của ứng dụng của bạn, nhưng trong các ứng dụng nói chung, vâng, đó là một vấn đề. Trước hết tùy thuộc vào thời gian trong năm, số giờ để cộng / trừ có thể thay đổi do một số múi giờ tôn vinh DST và các giờ khác không. Thứ hai, quy tắc múi giờ được thay đổi tùy ý theo thời gian, vì vậy nếu bạn làm việc với ngày / giờ trước đó, bạn cần áp dụng các quy tắc hợp lệ tại thời điểm đó, không phải thời gian hiện tại. Đó là lý do tại sao tốt nhất nên sử dụng API tận dụng cơ sở dữ liệu Olson TZ đằng sau hậu trường.
Joe Holloway

44

Đây là một phương pháp linh hoạt không phụ thuộc vào bất kỳ thư viện bên ngoài nào:

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

Điều này tránh các vấn đề thời gian trong ví dụ của DelboyJay. Và các vấn đề thời gian ít hơn trong sửa đổi của Erik van Oosten.

Là một chú thích thú vị, phần bù múi giờ được tính toán ở trên có thể khác với biểu thức có vẻ tương đương sau đây, có thể là do thay đổi quy tắc tiết kiệm ánh sáng ban ngày:

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

Cập nhật: Đoạn mã này có điểm yếu là sử dụng bù UTC của thời điểm hiện tại, có thể khác với bù UTC của thời gian đầu vào. Xem ý kiến ​​về câu trả lời này cho một giải pháp khác.

Để đi xung quanh các thời điểm khác nhau, hãy lấy thời gian kỷ nguyên từ thời gian trôi qua. Đây là những gì tôi làm:

def utc2local (utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp (epoch) - datetime.utcfromtimestamp (epoch)
    return utc + offset

Điều này hoạt động tốt và không đòi hỏi gì ngoài thời gian và thời gian.
Mike_K

6
@Mike_K: nhưng nó không chính xác. Nó không hỗ trợ DST hoặc múi giờ có bù utc khác nhau trong quá khứ vì những lý do khác. Không thể hỗ trợ đầy đủ mà không pytzgiống như db, xem PEP-431. Mặc dù bạn có thể viết giải pháp chỉ stdlib hoạt động trong các trường hợp như vậy trên các hệ thống đã có db múi giờ lịch sử, ví dụ: Linux, OS X, hãy xem câu trả lời của tôi .
jfs 14/12/13

@JFSebastian: Bạn nói rằng mã này không hoạt động nói chung nhưng cũng nói rằng nó hoạt động trên OS X và Linux. Bạn có nghĩa là mã này không hoạt động trên Windows?
David Foster

2
@DavidFoster: mã của bạn cũng có thể bị lỗi trên Linux và OS X. Sự khác biệt giữa các giải pháp chỉ dành cho stdlib của bạn và của tôi là của bạn sử dụng phần bù hiện tại có thể khác với phần bù utc tại utc_datetimethời điểm đó.
jfs

1
Cuộc gọi tốt Đó là một sự khác biệt rất tinh tế. Giá trị bù UTC cũng có thể thay đổi theo thời gian. thở dài
David Foster

23

Xem tài liệu datetime về các đối tượng tzinfo . Bạn phải thực hiện các múi giờ bạn muốn hỗ trợ mình. Các ví dụ ở dưới cùng của tài liệu.

Đây là một ví dụ đơn giản:

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)

Đầu ra

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

Cảm ơn sự chấp nhận! Tôi cập nhật nó một chút để dịch thời gian ví dụ cụ thể của bạn. Phân tích thời gian tạo ra cái mà các tài liệu gọi là thời gian "ngây thơ". Sử dụng replaceđể đặt múi giờ thành GMT và làm cho múi giờ "nhận biết", sau đó sử dụng astimezoneđể chuyển đổi sang múi giờ khác.
Đánh dấu Tolonen

Xin lỗi vì đã tráo đổi câu trả lời của Joe. Về mặt kỹ thuật, câu trả lời của bạn giải thích chính xác cách thực hiện với python thô (điều này rất tốt để biết), nhưng thư viện mà anh ấy đề xuất đưa tôi đến một giải pháp nhanh hơn nhiều.
MattoTodd

4
Không có vẻ như nó tự động xử lý thời gian tiết kiệm ánh sáng ban ngày.
Gringo Suave

@GringoSuave: nó không có nghĩa. Các quy tắc cho đó là phức tạp và thay đổi thường xuyên.
Đánh dấu Tolonen

19

Nếu bạn muốn nhận được kết quả chính xác ngay cả trong thời gian tương ứng với giờ địa phương mơ hồ (ví dụ: trong quá trình chuyển đổi DST) và / hoặc bù utc cục bộ khác nhau ở các thời điểm khác nhau trong pytzmúi giờ địa phương của bạn, sau đó sử dụng múi giờ:

#!/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)

10

Câu trả lời này sẽ hữu ích nếu bạn không muốn sử dụng bất kỳ mô-đun nào khác bên cạnh datetime.

datetime.utcfromtimestamp(timestamp)trả về một datetimeđối tượng ngây thơ (không phải là một nhận thức). Những người nhận thức được nhận thức múi giờ, còn ngây thơ thì không. Bạn muốn có một nhận thức nếu bạn muốn chuyển đổi giữa các múi giờ (ví dụ giữa UTC và giờ địa phương).

Nếu bạn không bắt đầu ngày bắt đầu, nhưng bạn vẫn có thể tạo một datetimeđối tượng ngây thơ trong thời gian UTC, bạn có thể muốn thử mã Python 3.x này để chuyển đổi nó:

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

Hãy cẩn thận đừng nhầm tưởng rằng nếu múi giờ của bạn hiện là MDT thì tiết kiệm ánh sáng ban ngày không hoạt động với mã trên vì nó in MST. Bạn sẽ lưu ý rằng nếu bạn thay đổi tháng thành tháng 8, nó sẽ in MDT.

Một cách dễ dàng khác để có được một datetimeđối tượng nhận biết (cũng trong Python 3.x) là tạo nó với múi giờ được chỉ định để bắt đầu. Đây là một ví dụ, sử dụng 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))

Nếu bạn sử dụng Python 2.x, có lẽ bạn sẽ phải phân lớp datetime.tzinfovà sử dụng nó để giúp bạn tạo một datetimeđối tượng nhận thức , vì datetime.timezonekhông tồn tại trong Python 2.x.


8

Nếu sử dụng Django, bạn có thể sử dụng timezone.localtimephương pháp:

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

2

Bạn có thể sử dụng mũi tên

from datetime import datetime
import arrow

now = datetime.utcnow()

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

bạn có thể nuôi arrow.get()bất cứ thứ gì dấu thời gian, chuỗi iso vv


1

Theo truyền thống, tôi trì hoãn điều này với frontend - gửi thời gian từ phụ trợ dưới dạng dấu thời gian hoặc một số định dạng thời gian khác trong UTC, sau đó để khách hàng tìm ra phần bù múi giờ và hiển thị dữ liệu này theo múi giờ thích hợp.

Đối với một ứng dụng web, điều này khá dễ thực hiện trong javascript - bạn có thể tìm ra phần bù múi giờ của trình duyệt khá dễ dàng bằng cách sử dụng các phương thức dựng sẵn và sau đó kết xuất dữ liệu từ phụ trợ đúng cách.


3
Điều này hoạt động tốt trong nhiều trường hợp bạn chỉ cần bản địa hóa để hiển thị. Tuy nhiên, đôi khi, bạn sẽ cần thực hiện một số logic nghiệp vụ dựa trên múi giờ của người dùng và bạn sẽ muốn có thể thực hiện chuyển đổi tầng máy chủ ứng dụng.
Joe Holloway

1

Bạn có thể sử dụng calendar.timegmđể chuyển đổi thời gian của mình thành giây kể từ Unix epoch và time.localtimeđể chuyển đổi trở lại:

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)

Cung cấp Fri Jan 21 05:37:21 2011(vì tôi đang ở múi giờ UTC + 03: 00).


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

ví dụ: múi giờ của tôi là ' +08: 00 '. đầu vào utc = 2018-10-17T00: 00: 00.111Z , sau đó tôi sẽ nhận được đầu ra = 2018-10-17T08: 00: 00 + 08: 00


1
Bạn nên mô tả rõ hơn về vấn đề của mình bằng văn bản thuần túy, nơi bạn giải thích cho những người dùng còn lại bạn đang cố gắng đạt được điều gì và vấn đề của bạn là gì
m33n

1

Từ câu trả lời ở đây , bạn có thể sử dụng mô-đun thời gian để chuyển đổi từ utc sang thời gian cục bộ được đặt trong máy tính của bạn:

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)

0

Đây là một phiên bản nhanh và bẩn sử dụng các cài đặt hệ thống cục bộ để tìm ra sự khác biệt về thời gian. LƯU Ý: Điều này sẽ không hoạt động nếu bạn cần chuyển đổi sang múi giờ mà hệ thống hiện tại của bạn không chạy. Tôi đã thử nghiệm điều này với các cài đặt của Vương quốc Anh theo múi giờ 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

nó phải là datetime.fromtimestamp(ts): dấu thời gian posix (số giây trôi nổi) -> đối tượng datetime theo giờ địa phương (nó hoạt động tốt nếu hệ điều hành ghi nhớ quá khứ utc cho múi giờ cục bộ, tức là trên Unix nhưng không phải trên Windows cho các ngày trong quá khứ)). Nếu không pytz có thể được sử dụng.
jfs

Tôi có thể đề nghị làm như sau: offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) Tôi có những khác biệt kỳ lạ mà không có nó.
Erik van Oosten

@ErikvanOosten: bạn đã thử datetime.fromtimestamp(ts)thay vì câu trả lời chưa?
jfs 14/12/13

0

Hợp nhất câu trả lời từ franksands thành một phương pháp thuận tiện.

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