แปลงสตริงวันที่และเวลา UTC เป็นวันที่และเวลาท้องถิ่น


206

ฉันไม่เคยต้องแปลงเวลาเป็นและจาก UTC เมื่อเร็ว ๆ นี้มีคำขอให้แอปของฉันตระหนักถึงเขตเวลาและฉันใช้ตัวเองเป็นวงกลม ข้อมูลจำนวนมากเกี่ยวกับการแปลงเวลาท้องถิ่นเป็น UTC ซึ่งฉันพบว่าค่อนข้างธรรมดา (บางทีฉันก็ทำผิดเช่นกัน) แต่ฉันไม่สามารถหาข้อมูลใด ๆ ในการแปลงเวลา UTC เป็นเขตเวลาของผู้ใช้ปลายทางได้อย่างง่ายดาย

โดยสรุปแล้วแอพสำหรับ Android ส่งข้อมูล (แอพพลิเคชั่น) ให้ฉันและภายในข้อมูลนั้นเป็นเวลาประทับ ในการจัดเก็บการประทับเวลานั้นเป็นเวลา utc ที่ฉันใช้:

datetime.utcfromtimestamp(timestamp)

ดูเหมือนว่าจะทำงานได้ เมื่อแอพของฉันเก็บข้อมูลมันจะถูกจัดเก็บล่วงหน้า 5 ชั่วโมง (ฉัน EST -5)

ข้อมูลถูกจัดเก็บใน BigTable ของ appengine และเมื่อดึงข้อมูลออกมาจะเป็นสตริงดังนี้:

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

ฉันจะแปลงสตริงนี้เป็น DateTime ในเขตเวลาที่ผู้ใช้แก้ไขได้อย่างไร

นอกจากนี้พื้นที่เก็บข้อมูลที่แนะนำสำหรับข้อมูลเขตเวลาของผู้ใช้คืออะไร (โดยทั่วไปคุณจะเก็บข้อมูล tz เช่น: "-5: 00" หรือ "EST" ฯลฯ ฯลฯ ) ฉันแน่ใจว่าคำตอบสำหรับคำถามแรกของฉันอาจมีพารามิเตอร์ที่คำตอบที่สอง



คำตอบนี้แสดงวิธีการแก้ปัญหาในวิธีที่ง่าย
Juan

คำตอบ:


402

หากคุณไม่ต้องการให้tzinfoวัตถุของคุณเองตรวจสอบห้องสมุดpython-dateutil มันมีtzinfoการนำไปใช้งานด้านบนของฐานข้อมูล zoneinfo (Olson)เช่นที่คุณสามารถอ้างถึงกฎของเขตเวลาด้วยชื่อมาตรฐาน

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)

แก้ไขตัวอย่างที่ขยายเพื่อแสดงstrptimeการใช้งาน

แก้ไขการใช้ API คงที่2เพื่อแสดงวิธีเข้าจุดที่ดีขึ้น

แก้ไขวิธีตรวจหาอัตโนมัติรวม3โซนสำหรับเขตเวลา (Yarin)


1
จากความคิดเห็นก่อนหน้าของฉันฉันสามารถนำเข้าdateutil.tzและใช้tz.tzutc()และtz.tzlocal()เป็นวัตถุโซนเวลาที่ฉันกำลังมองหา ดูเหมือนว่าฐานข้อมูลโซนเวลาในระบบของฉันดี (ฉันเช็คอิน/usr/share/zoneinfo) ไม่แน่ใจว่าเกิดอะไรขึ้น
Ben Kreeger

1
@Benjamin คุณอยู่ในเส้นทางที่ถูกต้อง tzโมดูลเป็นจุดเริ่มต้นที่ถูกต้องในการใช้ห้องสมุดนี้ ฉันได้อัปเดตคำตอบเพื่อแสดงถึงสิ่งนี้แล้ว dateutil.zoneinfoโมดูลผมได้แสดงให้เห็นก่อนหน้านี้ถูกนำมาใช้ภายในโดยtzโมดูลเป็นหลังฤดูใบไม้ร่วงถ้ามันไม่สามารถหาระบบ zoneinfo DB หากคุณมองเข้าไปในห้องสมุดคุณจะเห็นว่ามี tarball DB zoneinfo อยู่ในแพ็คเกจที่ใช้ถ้าไม่พบ DB ของระบบของคุณ ตัวอย่างเก่าของฉันพยายามที่จะตีฐานข้อมูลนั้นโดยตรงและฉันเดาว่าคุณกำลังมีปัญหาในการโหลดฐานข้อมูลส่วนบุคคลนั้น (ไม่ได้อยู่บนเส้นทางของงูหลามอย่างใด?)
Joe Holloway

2
jfs

1
@JFSebastian นี้ได้รับการแก้ไขใน# 225
Rohmer

2
@Briankip ขึ้นอยู่กับความซับซ้อนของแอปพลิเคชันของคุณ แต่ในแอปพลิเคชันทั่วไปใช่มันเป็นปัญหา ก่อนอื่นขึ้นอยู่กับว่าช่วงเวลาใดของปีจำนวนชั่วโมงที่จะเพิ่ม / ลบอาจเปลี่ยนแปลงได้เนื่องจากโซนเวลาบางส่วนให้เกียรติ DST และอื่น ๆ ไม่ได้ ประการที่สองกฎของเขตเวลามีการเปลี่ยนแปลงโดยพลการเมื่อเวลาผ่านไปดังนั้นหากคุณทำงานกับวันที่ / เวลาที่ผ่านมาคุณต้องใช้กฎที่ใช้ได้ ณ เวลานั้นไม่ใช่เวลาปัจจุบัน นั่นเป็นเหตุผลที่ดีที่สุดที่จะใช้ API ที่ใช้ประโยชน์จากฐานข้อมูล Olson TZ ที่อยู่เบื้องหลัง
Joe Holloway

44

ต่อไปนี้เป็นวิธีการยืดหยุ่นที่ไม่ได้ขึ้นอยู่กับไลบรารีภายนอก:

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

นี่เป็นการหลีกเลี่ยงปัญหาเรื่องเวลาในตัวอย่างของ DelboyJay และประเด็นเรื่องเวลาที่น้อยลงในการแก้ไขของ Erik van Oosten

ในเชิงอรรถที่น่าสนใจเขตเวลาที่คำนวณจากเขตเวลาที่คำนวณไว้ข้างต้นอาจแตกต่างจากนิพจน์ที่เหมือนกันดังต่อไปนี้ซึ่งอาจเนื่องมาจากการเปลี่ยนแปลงกฎการประหยัดเวลากลางวัน:

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

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

หากต้องการหลีกเลี่ยงช่วงเวลาที่แตกต่างกันให้คว้าเวลายุคจากเวลาที่ผ่านไปนี่คือสิ่งที่ฉันทำ:

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

สิ่งนี้ใช้ได้ดีและไม่ต้องใช้อะไรนอกจากเวลาและวันที่
Mike_K

6
@Mike_K: แต่มันไม่ถูกต้อง ไม่รองรับ DST หรือเขตเวลาที่มีออฟเซ็ต utc ต่างกันในอดีตด้วยเหตุผลอื่น การสนับสนุนอย่างเต็มที่โดยไม่มีpytzฐานข้อมูลเหมือนเป็นไปไม่ได้ดู PEP-431 แม้ว่าคุณสามารถเขียนวิธีการแก้ปัญหา STDLIB อย่างเดียวว่างานในกรณีดังกล่าวในระบบที่มีอยู่แล้วเขตประวัติศาสตร์ฐานข้อมูลเช่น, Linux OS X ดูคำตอบของฉัน
jfs

@JFSebastian: คุณบอกว่ารหัสนี้ใช้งานไม่ได้ แต่ก็บอกว่ามันใช้งานได้กับ OS X และ Linux คุณหมายความว่ารหัสนี้ใช้ไม่ได้กับ Windows หรือไม่
David Foster

2
@DavidFoster: รหัสของคุณอาจล้มเหลวบน Linux และ OS X เช่นกัน ความแตกต่างระหว่างโซลูชันของคุณกับโซลูชัน stdlib เท่านั้นคือคุณใช้ออฟเซ็ตปัจจุบันที่อาจแตกต่างจากออฟเซ็ต utc ในutc_datetimeเวลา
jfs

1
โทรดีมาก นั่นคือความแตกต่างที่ลึกซึ้งมาก UTC ออฟเซ็ตสามารถเปลี่ยนแปลงได้ตลอดเวลาเช่นกัน ถอนหายใจ
David Foster

23

ดูdatetimeเอกสารเกี่ยวกับtzinfoวัตถุ คุณต้องใช้เขตเวลาที่คุณต้องการสนับสนุนตัวเอง นี่คือตัวอย่างที่ด้านล่างของเอกสาร

นี่คือตัวอย่างง่ายๆ:

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)

เอาท์พุต

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

ขอบคุณสำหรับการยอมรับ! ฉันอัปเดตเล็กน้อยเพื่อแปลเวลาตัวอย่างเฉพาะของคุณ การแยกเวลาสร้างเอกสารที่เรียกว่าเวลา "ไร้เดียงสา" ใช้replaceเพื่อตั้งค่าเขตเวลาเป็น GMT และทำให้เขตเวลา "ทราบ" แล้วใช้astimezoneเพื่อแปลงเป็นเขตเวลาอื่น
Mark Tolonen

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

4
ดูเหมือนจะไม่จัดการเวลาตามฤดูกาลโดยอัตโนมัติ
Gringo Suave

@GringoSuave: มันไม่ได้ตั้งใจ กฎสำหรับสิ่งนั้นมีความซับซ้อนและเปลี่ยนแปลงบ่อย
Mark Tolonen

19

หากคุณต้องการได้ผลลัพธ์ที่ถูกต้องแม้ในเวลาที่สอดคล้องกับเวลาท้องถิ่นที่ไม่ชัดเจน (เช่นระหว่างการเปลี่ยน DST) และ / หรือออฟเซ็ต utc ท้องถิ่นแตกต่างกันในแต่ละช่วงเวลาในเขตเวลาท้องถิ่นของคุณให้ใช้pytzเขตเวลา:

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

คำตอบนี้จะเป็นประโยชน์หากคุณไม่ต้องการใช้โมดูลอื่นนอกเหนือจากdatetimeนี้

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

หากคุณไม่ใช่วันที่เริ่มต้นด้วยอินสแตนซ์ แต่คุณยังสามารถสร้างdatetimeวัตถุที่ไร้เดียงสาในเวลา UTC คุณอาจต้องการลองใช้รหัส Python 3.x เพื่อแปลง:

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

โปรดระวังอย่าสันนิษฐานว่าหากเขตเวลาของคุณเป็น MDT ในปัจจุบันซึ่งการประหยัดเวลากลางวันไม่ทำงานกับรหัสด้านบนเนื่องจากมันพิมพ์ MST คุณจะทราบว่าถ้าคุณเปลี่ยนเดือนเป็นสิงหาคมมันจะพิมพ์ MDT

อีกวิธีที่ง่ายในการรับdatetimeวัตถุที่รับรู้(เช่นใน Python 3.x) คือการสร้างมันด้วยเขตเวลาที่ระบุให้เริ่มด้วย นี่คือตัวอย่างการใช้ 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))

ถ้าคุณใช้ Python 2.x คุณอาจต้อง subclass datetime.tzinfoและใช้สิ่งนั้นเพื่อช่วยคุณสร้างdatetimeวัตถุที่รับรู้เนื่องจากdatetime.timezoneไม่มีอยู่ใน Python 2.x


8

หากใช้ Django คุณสามารถใช้timezone.localtimeวิธีการ:

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

คุณสามารถใช้ลูกศร

from datetime import datetime
import arrow

now = datetime.utcnow()

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

คุณสามารถเลี้ยงarrow.get()ด้วยอะไรก็ได้ เวลาประทับสตริง iso ฯลฯ


1

ฉันเลื่อนการนี้ไปที่ส่วนหน้า - ส่งเวลาจากแบ็กเอนด์เป็นเวลาประทับหรือรูปแบบวันที่และเวลาอื่น ๆ ใน UTC จากนั้นให้ลูกค้าหาเขตเวลาชดเชยและแสดงข้อมูลนี้ในเขตเวลาที่เหมาะสม

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


3
วิธีนี้ใช้งานได้ดีในหลายกรณีที่คุณเพียงทำการแปลเป็นภาษาท้องถิ่น อย่างไรก็ตามในบางครั้งคุณจะต้องดำเนินการตามตรรกะทางธุรกิจบางอย่างตามเขตเวลาของผู้ใช้และคุณจะต้องทำการแปลงระดับแอพพลิเคชันเซิร์ฟเวอร์
Joe Holloway

1

คุณสามารถใช้calendar.timegmในการแปลงเวลาของคุณเป็นวินาทีตั้งแต่ Unix ยุคและtime.localtimeการแปลงกลับ:

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)

ให้Fri Jan 21 05:37:21 2011(เพราะฉันอยู่ใน 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

ตัวอย่างเช่นเขตเวลาของฉันคือ ' +08: 00 ' อินพุตutc = 2018-10-17T00: 00: 00.111Z , จากนั้นฉันจะรับเอาต์พุต = 2018-10-17T08: 00: 00 + 08: 00


1
คุณควรอธิบายปัญหาของคุณให้ดีขึ้นในรูปแบบข้อความธรรมดาที่ซึ่งคุณจะอธิบายให้ผู้ใช้ที่เหลือได้รับรู้ว่าคุณกำลังพยายามทำอะไรและปัญหาของคุณคืออะไร
m33n

1

จากคำตอบที่นี่คุณสามารถใช้โมดูลเวลาในการแปลงจาก utc เป็นเวลาท้องถิ่นที่ตั้งไว้ในคอมพิวเตอร์ของคุณ:

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

นี่คือรุ่นที่รวดเร็วและสกปรกที่ใช้การตั้งค่าระบบภายในเพื่อหาข้อแตกต่างของเวลา หมายเหตุ: สิ่งนี้จะไม่ทำงานหากคุณต้องการแปลงเป็นเขตเวลาที่ระบบปัจจุบันของคุณไม่ทำงานฉันได้ทดสอบสิ่งนี้กับการตั้งค่าของสหราชอาณาจักรภายใต้เขตเวลา 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

มันควรจะเป็นdatetime.fromtimestamp(ts): posix timestamp (วินาทีลอย) -> วัตถุ datetime ในเวลาท้องถิ่น (มันทำงานได้ดีถ้าระบบปฏิบัติการจำ utc ที่ผ่านมา utsets สำหรับเขตเวลาท้องถิ่นเช่นใน Unix แต่ไม่ได้อยู่บน Windows สำหรับวันที่ในอดีต)) มิฉะนั้นอาจใช้ pytz
jfs

ฉันขอแนะนำให้ทำสิ่งต่อไปนี้: offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) ฉันมีความแตกต่างไมโครวินาทีโดยไม่ใช้มัน
Erik van Oosten

@ErikvanOosten: คุณลองdatetime.fromtimestamp(ts)แทนคำตอบแล้วหรือยัง
jfs

0

รวมคำตอบจากแฟรงค์เป็นวิธีที่สะดวก

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()))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.