แปลง python UTC datetime เป็น local time โดยใช้ไลบรารี่มาตรฐานของไพ ธ อนเท่านั้น?


189

ฉันมีอินสแตนซ์ของ python datetime ที่สร้างขึ้นโดยใช้ datetime.utcnow () และคงอยู่ในฐานข้อมูล

สำหรับการแสดงผลฉันต้องการแปลงอินสแตนซ์วันที่และเวลาที่ดึงมาจากฐานข้อมูลเป็นดาต้าไทม์ท้องถิ่นโดยใช้เขตเวลาเริ่มต้นในท้องถิ่น (เช่นราวกับว่าวันที่และเวลาถูกสร้างขึ้นโดยใช้ datetime.now ())

ฉันจะแปลง UTC วันที่และเวลาเป็นเวลาท้องถิ่นโดยใช้เพียงห้องสมุดมาตรฐานหลาม (เช่นไม่มีการพึ่งพา pytz)

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


รูปแบบเวลาใดที่คงอยู่กับฐานข้อมูล หากเป็นรูปแบบมาตรฐานอาจเป็นไปได้ว่าคุณไม่จำเป็นต้องทำการแปลงใด ๆ
Apalala

1
นี้คำตอบที่แสดงให้เห็นว่าเป็นวิธีที่ง่ายของการใช้pytz
Juan

คำตอบ:


275

ใน Python 3.3+:

from datetime import datetime, timezone

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

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

การใช้งานpytz(ทั้ง 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

ตัวอย่าง

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

เอาท์พุต

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

หมายเหตุ: คำนึงถึง DST และการเปลี่ยนแปลงล่าสุดของ utc offset สำหรับเขตเวลา MSK

ฉันไม่ทราบว่าโซลูชันที่ไม่ใช่ pytz ทำงานบน Windows ได้หรือไม่


1
'ทำให้ปกติ' ทำอะไร
avi

4
@avi: โดยทั่วไป: pytz: เหตุใดจึงจำเป็นต้องทำให้เป็นมาตรฐานเมื่อทำการแปลงระหว่างเขตเวลา . หมายเหตุ: ไม่จำเป็นสำหรับการใช้งานในปัจจุบัน.normalize()เนื่องจากเขตเวลาของแหล่งที่มา.astimezone()คือ UTC
jfs

1
ฉันได้รับTypeError: replace() takes no keyword argumentsเมื่อพยายามใช้โซลูชัน pytz
Craig

@Craig รหัสทำงานตามที่เป็นอยู่ในผลลัพธ์ในคำตอบแสดงให้เห็นถึง สร้างตัวอย่างรหัสขั้นต่ำแบบสมบูรณ์ที่นำไปสู่ ​​TypeError พูดถึง Python รุ่น pytz ที่คุณใช้และโพสต์เป็นคำถาม Stack Overflow แยกต่างหาก
jfs

เคล็ดลับอย่างรวดเร็วสำหรับโซลูชัน Python 3.3+ หากต้องการไปในทิศทางตรงกันข้าม (Native to UTC) งานนี้: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
jasonrhaas

50

คุณไม่สามารถทำได้ด้วยไลบรารีมาตรฐานเท่านั้นเนื่องจากไลบรารีมาตรฐานไม่มีเขตเวลาใด ๆ คุณต้องpytzหรือ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())

หรือคุณสามารถทำได้โดยไม่ต้องใช้ pytz หรือ dateutil โดยใช้เขตเวลาของคุณเอง แต่นั่นจะโง่


ขอบคุณฉันจะเก็บไว้ที่มือถ้าฉันต้องการโซลูชั่นที่สมบูรณ์ Dateutil รองรับ Python 3.1 (พูดว่า Python 2.3+ แต่น่าสงสัย)
Nitro Zark


pytz จัดการกับการเปลี่ยนแปลง DST ได้ดีขึ้น
Lennart Regebro

2
Update: pytz และ dateutil รองรับ Python 3 ทุกวันนี้
Lennart Regebro

1
@JFSebastian: dateutil ไม่สามารถแยกความแตกต่างระหว่าง 1:30 ครั้งที่เกิดขึ้นระหว่างการเปลี่ยน DST หากคุณจำเป็นต้องแยกแยะความแตกต่างระหว่างสองครั้งนี้การแก้ไขปัญหาโดยใช้ pytz ซึ่งสามารถจัดการได้
Lennart Regebro

8

คุณไม่สามารถทำได้ด้วยห้องสมุดมาตรฐาน การใช้ โมดูลpytzคุณสามารถแปลงวัตถุ datetime ที่ไร้เดียงสา / รับรู้เป็นโซนเวลาอื่นได้ ให้ดูตัวอย่างโดยใช้ Python 3

วัตถุไร้เดียงสาที่สร้างขึ้นด้วยวิธีการเรียน utcnow()

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

ตัวแปรpytz.all_timezonesจะแสดงรายการเขตเวลาทั้งหมดที่มีอยู่ในโมดูล 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)

วัตถุไร้เดียงสาที่สร้างขึ้นด้วยวิธีการเรียน now()

เนื่องจากnowเมธอดส่งคืนวันที่และเวลาปัจจุบันดังนั้นคุณต้องทำให้เขตเวลาของวัตถุวันที่และเวลาทราบก่อน localize ฟังก์ชั่นแปลงไร้เดียงสาวัตถุ datetime เป็นเขตตระหนักถึงวัตถุ datetime จากนั้นคุณสามารถใช้astimezoneวิธีการแปลงเป็นเขตเวลาอื่น

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

ฉันคิดว่าฉันคิดออก: คำนวณจำนวนวินาทีตั้งแต่ยุคแล้วแปลงเป็น timzeone ท้องถิ่นโดยใช้ time.localtime แล้วแปลงเวลา struct กลับเป็น 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 )

มันใช้ DST ฤดูร้อน / ฤดูหนาวอย่างถูกต้อง:

>>> 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
ฮึ. แต่นั่นก็น่าจะใช้ได้กับ Unix อย่างน้อยที่สุด สำหรับ Windows อาจไม่ถูกต้องหากกฎการปรับเวลาตามฤดูกาลของคุณเปลี่ยนไปนับตั้งแต่วันที่แปลง ทำไมคุณไม่ต้องการใช้ห้องสมุด?
Lennart Regebro

ฉันต้องการแปลง utc / local เท่านั้นดังนั้นการลากไลบรารี่ดังกล่าวจึงดูเกินเลยไป เนื่องจากฉันเป็นผู้ใช้คนเดียวของแอปพลิเคชันฉันจึงสามารถใช้ชีวิตได้ด้วยข้อ จำกัด บางประการ นอกจากนี้ยังหลีกเลี่ยงการจัดการปัญหาที่เกี่ยวข้องกับการพึ่งพา (สนับสนุน Python 3, Windows? quality ... ) ซึ่งจะมีค่าใช้จ่ายสูงกว่าการทำงานกับสคริปต์ขนาดเล็กของฉัน
Nitro Zark

1
ดี. ถ้าคุณต้องการ Python3 แสดงว่าคุณโชคไม่ดี แต่มิฉะนั้นการค้นคว้าและการแก้ปัญหาของคุณเองนั้นเกินความจริงเมื่อเทียบกับการใช้ห้องสมุด
Lennart Regebro

1
+1 (เพื่อชดเชย downvote: อาจเป็นข้อกำหนดที่ถูกต้องในการค้นหาวิธีแก้ปัญหา stdlib เท่านั้น) ด้วย caveat @Lennart ที่กล่าวถึง ระบุdateutil ที่อาจล้มเหลวทั้ง Unix และ Windows btw คุณสามารถดึงข้อมูลutctotimestamp(utc_dt) -> (seconds, microseconds)จากโค้ดของคุณเพื่อความชัดเจนดูการใช้งาน Python 2.6-3.x
jfs

1
pytz (และ dateutil) ทำงานบน Python 3 ตั้งแต่เวลานี้ดังนั้นปัญหา Python3 / Windows / Quality จึงไม่ใช่ปัญหาอีกต่อไป
Lennart Regebro

6

สร้างความคิดเห็นของ Alexei สิ่งนี้ควรใช้กับ 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

ไลบรารี Python มาตรฐานไม่ได้มาพร้อมกับtzinfoการใช้งานใด ๆเลย ฉันมักจะคิดว่านี่เป็นข้อบกพร่องที่น่าประหลาดใจของโมดูลวันที่และเวลา

เอกสารสำหรับการเรียน tzinfoจะมาพร้อมกับตัวอย่างที่มีประโยชน์บางอย่าง มองหาบล็อกรหัสขนาดใหญ่ที่ส่วนท้ายของส่วน


1
ฉันเดาเกี่ยวกับสิ่งที่ชอบของการใช้งานหลักเพราะฐานข้อมูลเขตเวลาจำเป็นต้องได้รับการปรับปรุงอย่างสม่ำเสมอเนื่องจากบางประเทศไม่มีกฎตายตัวเพื่อกำหนดช่วงเวลา DST หากฉันไม่เข้าใจผิด JVM จะต้องได้รับการอัปเดตเพื่อให้ได้ฐานข้อมูลเขตเวลาล่าสุด ...
Nitro Zark

@Nitro Zark อย่างน้อยพวกเขาควรจะมีหนึ่งสำหรับ UTC osโมดูลจะมีให้สำหรับเวลาท้องถิ่นขึ้นอยู่กับการทำงานของระบบปฏิบัติการ
Mark Ransom

1
ฉันกำลังตรวจสอบรายการคุณสมบัติใหม่ใน 3.2 และพวกเขาเพิ่มเขตเวลา UTC ใน 3.2 ( docs.python.org/dev/whatsnew/3.2.html#datetime ) ดูเหมือนจะไม่มีเขตเวลาท้องถิ่นแม้ว่า ...
Nitro Zark

2

Python 3.9 เพิ่มzoneinfoโมดูลดังนั้นตอนนี้มันสามารถทำได้ดังนี้ (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

ยุโรปกลางคือ 1 หรือ 2 ชั่วโมงก่อนเวลา UTC ดังนั้นlocal_aware:

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

เป็นstr:

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

Windows ไม่มีฐานข้อมูลเขตเวลาระบบดังนั้นที่นี่จึงจำเป็นต้องมีแพ็คเกจเพิ่มเติม:

pip install tzdata  

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

sudo pip install backports.zoneinfo

แล้ว:

from backports.zoneinfo import ZoneInfo

คุณไม่ต้องการแม้แต่zoneinfoที่นี่ - ดูส่วนแรกของคำตอบของjfs (Python3.3 + ส่วน) ;-)
MrFuppes

@MrFuppes ฉันคิดว่าZoneInfo('localtime')มีความชัดเจนมากกว่าtz=None(หลังจากทั้งหมดหนึ่งอาจคาดว่าtz=Noneจะลบเขตเวลาหรือกลับ UTC มากกว่าเวลาท้องถิ่น)
xjcl

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

0

วิธีที่เรียบง่าย (แต่อาจมีข้อบกพร่อง) ที่ทำงานใน Python 2 และ 3:

import time
import datetime

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

ข้อดีของมันคือมันไม่สำคัญที่จะเขียนฟังก์ชันผกผัน


1
สิ่งนี้ให้ผลลัพธ์ที่ผิดเนื่องจากtime.timezoneไม่คำนึงถึงการประหยัดเวลากลางวัน
guyarad

0

วิธีที่ง่ายที่สุดที่ฉันพบคือการหาเวลาชดเชยว่าคุณอยู่ที่ไหนแล้วลบมันออกจากชั่วโมง

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

สิ่งนี้ใช้ได้กับฉันใน Python 3.5.2


0

นี่เป็นอีกวิธีหนึ่งในการเปลี่ยนเขตเวลาในรูปแบบวันที่และเวลา (ฉันรู้ว่าฉันสูญเสียพลังงานในเรื่องนี้ แต่ฉันไม่เห็นหน้านี้เลย และวินาที เพราะฉันไม่ต้องการมันสำหรับโครงการของฉัน:

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)

ใช้ timedelta เพื่อสลับระหว่างเขตเวลา สิ่งที่คุณต้องการคือการชดเชยเป็นชั่วโมงระหว่างเขตเวลา ไม่ต้องเล่นแร่แปรธาตุที่มีขอบเขตสำหรับทั้ง 6 องค์ประกอบของวัตถุ datetime timedelta จัดการกับปีอธิกสุรทินหลายศตวรรษและอื่น ๆ ได้อย่างง่ายดาย คุณจะต้อง 'จากเวลาที่นำเข้าหมดเวลาใช้งาน' จากนั้นถ้าอ็อฟเซ็ตเป็นตัวแปรในหน่วยชั่วโมง: timeout = timein + timedelta (hours = offset) โดยที่ timein และ timeout เป็นออบเจ็กต์ datetime เช่น timein + timedelta (ชั่วโมง = -8) แปลงจาก GMT เป็น PST
Karl Rudnick

0

นี่เป็นวิธีที่แย่มากที่จะทำ แต่หลีกเลี่ยงการสร้างคำจำกัดความ มันตอบสนองความต้องการที่จะติดกับห้องสมุด Python3 พื้นฐาน

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

ในขณะนี้เป็นสิ่งที่น่าสนใจและฉันจะใช้วิธีนี้มันไม่ได้ใช้แค่ stdlib ใช้ Pandas ในการแปลง pandas.pydata.org/pandas-docs/version/0.25/reference/api/…
Tim Hughes

-3

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

from datetime import datetime, timedelta

ถ้าoffsetเป็นเขตเวลาเดลต้าในหน่วยชั่วโมง:

timeout = timein + timedelta(hours = offset)

โดย timein และ timeout เป็นวัตถุ datetime เช่น

timein + timedelta(hours = -8)

แปลงจาก GMT เป็น PST

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

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)

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

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

เพื่อแปลงเป็น GMT ตัวอย่าง แน่นอนว่าการแปลงเป็น / จากเขตเวลาอื่นที่คุณต้องการไม่ว่าจะเป็นในท้องถิ่นหรืออื่น ๆ เพียงแค่ใช้สตริงเขตเวลาที่เหมาะสมที่ Pytz เข้าใจ (จาก pytz.all_timezones) เวลาออมแสงจะถูกนำมาพิจารณาด้วย


1
นี่เป็น / ไม่ / เป็นความคิดที่ดี เวลาไม่เปลี่ยนแปลงในวันเดียวกันสำหรับคนทั้งโลก ใช้ฟังก์ชัน datetime เพื่อทำสิ่งนี้
Gabe

@gabe - เข้าใจว่าตารางไม่ใช่ความคิดที่ดีแม้ว่ามันอาจเหมาะกับใครบางคนที่ครอบคลุมเฉพาะเขตเวลาของสหรัฐฯ ดังที่ฉันพูดในตอนท้ายวิธีที่ดีที่สุดคือทำให้วัตถุ datetime "ทราบ" อยู่เสมอเพื่อให้คุณสามารถใช้ฟังก์ชัน datetime ได้อย่างง่ายดาย
Karl Rudnick
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.