Konwertuj ciąg danych UTC na lokalny czas danych

206

Nigdy nie musiałem przeliczać czasu na i od UTC. Niedawno otrzymałem prośbę, aby moja aplikacja była świadoma strefy czasowej, a ja pracuję w kółko. Wiele informacji na temat konwersji czasu lokalnego na UTC, które uważam za dość elementarne (być może robię to również źle), ale nie mogę znaleźć żadnych informacji na temat łatwego konwertowania czasu UTC na strefę czasową użytkowników końcowych.

Krótko mówiąc, aplikacja na Androida wysyła mi (aplikację silnika) dane, w których obrębie znajduje się znacznik czasu. Aby zapisać ten znacznik czasu na czas utc, którego używam:

datetime.utcfromtimestamp(timestamp)

To wydaje się działać. Gdy moja aplikacja przechowuje dane, są one zapisywane z wyprzedzeniem 5 godzin (mam EST -5)

Dane są przechowywane na BigTable aplikacji, a po ich odzyskaniu są wyświetlane jako ciąg znaków:

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

Jak przekonwertować ten ciąg na DateTime we właściwej strefie czasowej użytkowników?

Jakie jest zalecane miejsce do przechowywania informacji o strefie czasowej użytkowników? (Jak zazwyczaj przechowujesz informacje tz, tj .: „-5: 00” lub „EST” itp.?) Jestem pewien, że odpowiedź na moje pierwsze pytanie może zawierać parametr odpowiadający odpowiedziom na drugie.

MattoTodd
źródło

Odpowiedzi:

402

Jeśli nie chcesz udostępniać własnych tzinfoobiektów, sprawdź bibliotekę python-dateutil . Zapewnia tzinfoimplementacje na bazie bazy danych zoneinfo (Olson) , dzięki czemu można odwoływać się do reguł strefy czasowej pod nieco kanoniczną nazwą.

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)

Edytuj Rozszerzony przykład, aby pokazać strptimeużycie

Edytuj 2 Naprawiono użycie interfejsu API, aby pokazać lepszą metodę punktu wejścia

Edycja 3 Dołączone metody automatycznego wykrywania stref czasowych (Yarin)

Joe Holloway
źródło
1
W moim poprzednim komentarzu byłem w stanie zaimportować dateutil.tzi użyć tz.tzutc()i tz.tzlocal()jako obiektów strefy czasowej szukałem. Wygląda na to, że baza danych stref czasowych w moim systemie jest dobra (zalogowałem się /usr/share/zoneinfo). Nie jestem pewien, co było grane.
Ben Kreeger
1
@Benjamin Jesteś na dobrej drodze. tzModuł jest poprawny punkt wejścia do korzystania z tej biblioteki. Zaktualizowałem swoją odpowiedź, aby to odzwierciedlić. dateutil.zoneinfoModuł I pokazywał wcześniej jest używane wewnętrznie przez tzmoduł jako cofać, jeśli nie można zlokalizować-tych systemowych zoneinfo DB. Jeśli zajrzysz do biblioteki, zobaczysz, że tarball DB strefyinfo w pakiecie, którego używa, jeśli nie może znaleźć DB twojego systemu. Mój stary przykład próbował trafić bezpośrednio w tę bazę danych i domyślam się, że miałeś problemy z ładowaniem tej prywatnej bazy danych (nie ma w jakiś sposób ścieżki Pythona?)
Joe Holloway
1
@JFSebastian zostało to naprawione w # 225
Rohmer
2
@ briankip Zależy to od stopnia zaawansowania twojej aplikacji, ale w ogólnych aplikacjach tak, to problem. Przede wszystkim w zależności od pory roku liczba godzin dodawania / odejmowania może się zmieniać, ponieważ niektóre strefy czasowe honorują czas letni, a inne nie. Po drugie, reguły stref czasowych są dowolnie zmieniane w miarę upływu czasu, więc jeśli pracujesz z datą / godziną z przeszłości, musisz zastosować reguły obowiązujące w danym momencie, a nie w chwili obecnej. Dlatego najlepiej jest używać interfejsu API, który wykorzystuje bazę danych Olson TZ za kulisami.
Joe Holloway
44

Oto odporna metoda, która nie zależy od żadnych zewnętrznych bibliotek:

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

Pozwala to uniknąć problemów z synchronizacją w przykładzie DelboyJay. I mniej problemów z terminem w poprawce Erika van Oostena.

Jako interesujący przypis, obliczone powyżej przesunięcie strefy czasowej może różnić się od następującego pozornie równoważnego wyrażenia, prawdopodobnie ze względu na zmiany reguł dotyczących czasu letniego:

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

Aktualizacja: ten fragment ma słabość korzystania z przesunięcia UTC bieżącego czasu, które mogą różnić się od przesunięcia UTC wejściowej daty i godziny. Zobacz komentarze do tej odpowiedzi dla innego rozwiązania.

Aby obejść różne czasy, chwyć epokę od upływu czasu. Oto, co robię:

def utc2local (utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp (epoch) - datetime.utcfromtimestamp (epoch)
    return utc + offset
David Foster
źródło
Działa to dobrze i nie wymaga niczego poza czasem i datetime.
Mike_K,
6
@Mike_K: ale jest niepoprawny. Nie obsługuje DST ani stref czasowych, które miały inne przesunięcia utc w przeszłości z innych powodów. Pełne wsparcie bez pytzdb-db jest niemożliwe, patrz PEP-431. Chociaż możesz napisać tylko rozwiązanie stdlib, które działa w takich przypadkach na systemach, które mają już historyczną strefę czasową db np. Linux, OS X, zobacz moją odpowiedź .
jfs
@JFSebastian: Mówisz, że ten kod nie działa ogólnie, ale także działa na OS X i Linux. Czy masz na myśli, że ten kod nie działa w systemie Windows?
David Foster,
2
@DavidFoster: kod może zawieść również w systemie Linux i OS X. Różnica między twoimi a moimi rozwiązaniami tylko dla stdlib polega na tym, że używasz aktualnego przesunięcia, które może różnić się od przesunięcia utc w danym utc_datetimemomencie.
jfs
1
Dobra decyzja. To bardzo subtelna różnica. Przesunięcie UTC może również zmieniać się z czasem. westchnienie
David Foster,
23

Zobacz dokumentację datetime dotyczącą obiektów tzinfo . Musisz zaimplementować strefy czasowe, które chcesz wesprzeć. Są to przykłady na dole dokumentacji.

Oto prosty przykład:

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)

Wynik

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
Mark Tolonen
źródło
Dzięki za zaakceptowanie! Zaktualizowałem go nieco, aby przetłumaczyć konkretny przykładowy czas. Analiza czasu tworzy coś, co lekarze nazywają czasem „naiwnym”. Użyj, replaceaby ustawić strefę czasową na GMT i ustawić ją jako „świadomą”, a następnie użyj, astimezoneaby przekonwertować na inną strefę czasową.
Mark Tolonen,
Przepraszamy za zamianę na odpowiedź Joe. Technicznie twoja odpowiedź wyjaśnia dokładnie, jak to zrobić przy użyciu surowego pytona (co dobrze wiedzieć), ale biblioteka, którą zasugerował, znacznie szybciej mnie rozwiązuje.
MattoTodd
4
Nie wygląda na to, że automatycznie obsługuje czas letni.
Gringo Suave
@GringoSuave: nie miało to na celu. Zasady tego są skomplikowane i często się zmieniają.
Mark Tolonen
19

Jeśli chcesz uzyskać poprawny wynik nawet dla czasu, który odpowiada dwuznacznemu czasowi lokalnemu (np. Podczas przejścia DST) i / lub lokalne przesunięcie utc jest różne w różnych momentach w lokalnej strefie czasowej, użyj pytzstref czasowych:

#!/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)
jfs
źródło
10

Ta odpowiedź powinna być pomocna, jeśli nie chcesz używać innych modułów oprócz datetime.

datetime.utcfromtimestamp(timestamp)zwraca datetimeobiekt naiwny (nieświadomy). Ci świadomi są świadomi strefy czasowej, a naiwni nie. Chcesz wiedzieć, jeśli chcesz konwertować między strefami czasowymi (np. Między czasem UTC i czasem lokalnym).

Jeśli nie tworzysz daty na początek, ale nadal możesz utworzyć naiwny datetimeobiekt w czasie UTC, możesz wypróbować ten kod Python 3.x, aby go przekonwertować:

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

Uważaj, aby nie błędnie założyć, że jeśli twoja strefa czasowa to obecnie MDT, oszczędności czasu dziennego nie działają z powyższym kodem, ponieważ drukuje MST. Zauważysz, że jeśli zmienisz miesiąc na sierpień, wydrukuje MDT.

Innym łatwym sposobem na uzyskanie świadomego datetimeobiektu (również w Pythonie 3.x) jest utworzenie go ze strefą czasową określoną na początek. Oto przykład z użyciem 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))

Jeśli używasz języka Python 2.x, prawdopodobnie będziesz musiał podklasę datetime.tzinfoi użyć go, aby pomóc w utworzeniu świadomego datetimeobiektu, ponieważ datetime.timezonenie istnieje on w języku Python 2.x.

Brōtsyorfuzthrāx
źródło
8

Jeśli używasz Django, możesz użyć timezone.localtimemetody:

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>)
frmdstryr
źródło
2

Możesz użyć strzałki

from datetime import datetime
import arrow

now = datetime.utcnow()

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

możesz karmić arrow.get()czymkolwiek. znacznik czasu, ciąg ISO itp

d21d3q
źródło
1

Tradycyjnie odkładam to na frontend - wysyłam czasy z backendu jako znaczniki czasu lub inny format daty i godziny w UTC, a następnie pozwalam klientowi ustalić przesunięcie strefy czasowej i renderować te dane we właściwej strefie czasowej.

W przypadku aplikacji internetowej jest to dość łatwe w javascript - można łatwo ustalić przesunięcie strefy czasowej przeglądarki za pomocą wbudowanych metod, a następnie poprawnie wyrenderować dane z zaplecza.

Matt Billenstein
źródło
3
Działa to dobrze w wielu przypadkach, w których lokalizujesz się w celu wyświetlenia. Czasami jednak trzeba wykonać logikę biznesową w oparciu o strefę czasową użytkownika i będzie można wykonać konwersję warstwy serwera aplikacji.
Joe Holloway
1

Możesz użyć calendar.timegmdo przeliczenia czasu na sekundy od epoki Uniksa i time.localtimedo konwersji:

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)

Daje Fri Jan 21 05:37:21 2011(ponieważ jestem w strefie czasowej UTC + 03: 00).

myaut
źródło
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

na przykład moja strefa czasowa to „ +08: 00 ”. input utc = 2018-10-17T00: 00: 00.111Z , a następnie dostanę wynik = 2018-10-17T08: 00: 00 + 08: 00

Eureka Bing
źródło
1
Lepiej opisz swój problem w postaci zwykłego tekstu, w którym wyjaśnisz pozostałym użytkownikom, co próbujesz osiągnąć i jaki jest twój problem
m33n
1

Z odpowiedzi tutaj możesz skorzystać z modułu czasu do konwersji z utc na czas lokalny ustawiony na twoim komputerze:

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)
frankands
źródło
0

Oto szybka i brudna wersja, która wykorzystuje lokalne ustawienia systemu do obliczenia różnicy czasu. UWAGA: To nie zadziała, jeśli musisz przekonwertować na strefę czasową, w której obecny system nie działa. Przetestowałem to z ustawieniami brytyjskimi w strefie czasowej 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
DelboyJay
źródło
powinno być datetime.fromtimestamp(ts): znacznik czasu posix (liczba zmiennoprzecinkowa) -> obiekt datetime w czasie lokalnym (działa dobrze, jeśli system operacyjny pamięta poprzednie przesunięcia utc dla lokalnej strefy czasowej, tj. w systemie Unix, ale nie w systemie Windows dla dat w przeszłości)). W przeciwnym razie można użyć pytza.
jfs
Czy mogę zasugerować wykonanie następujących czynności: offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) Mam dziwne różnice w mikrosekundach bez tego.
Erik van Oosten
@ErikvanOosten: próbowałeś datetime.fromtimestamp(ts)zamiast odpowiedzi?
jfs
0

Skonsolidowanie odpowiedzi z franków w wygodnej metodzie.

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()))
Martlark
źródło