Convertir la chaîne de datetime UTC en datetime local

207

Je n'ai jamais eu à convertir l'heure vers et depuis UTC. J'ai récemment reçu une demande pour que mon application soit au fuseau horaire, et je me tourne en rond. Beaucoup d'informations sur la conversion de l'heure locale en UTC, que j'ai trouvé assez élémentaire (peut-être que je fais aussi mal), mais je ne trouve aucune information sur la conversion facile de l'heure UTC en fuseau horaire des utilisateurs finaux.

En un mot, et l'application Android m'envoie des données (appengine app) et dans ces données est un horodatage. Pour stocker cet horodatage en heure UTC que j'utilise:

datetime.utcfromtimestamp(timestamp)

Cela semble fonctionner. Lorsque mon application stocke les données, elles sont stockées avec 5 heures d'avance (je suis EST -5)

Les données sont stockées sur BigTable d'Appengine, et lorsqu'elles sont récupérées, elles apparaissent sous forme de chaîne comme ceci:

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

Comment convertir cette chaîne en DateTime dans le fuseau horaire correct des utilisateurs?

En outre, quel est le stockage recommandé pour les informations de fuseau horaire d'un utilisateur? (Comment stockez-vous généralement les informations tz, c'est-à-dire: "-5: 00" ou "EST", etc., etc.?) Je suis sûr que la réponse à ma première question peut contenir un paramètre et les réponses la seconde.

MattoTodd
la source

Réponses:

403

Si vous ne souhaitez pas fournir vos propres tzinfoobjets, consultez la bibliothèque python-dateutil . Il fournit des tzinfoimplémentations au-dessus d'une base de données zoneinfo (Olson) de sorte que vous pouvez faire référence aux règles de fuseau horaire par un nom quelque peu canonique.

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)

Modifier l' exemple développé pour l'afficherstrptime utilisation

Modifier 2 Correction de l'utilisation de l'API pour afficher une meilleure méthode de point d'entrée

Modifier 3 méthodes de détection automatique incluses pour les fuseaux horaires (Yarin)

Joe Holloway
la source
1
Sur mon commentaire précédent, j'ai pu importer dateutil.tzet utiliser tz.tzutc()et tz.tzlocal()comme fuseau horaire les objets que je cherchais. Il semble que la base de données de fuseau horaire sur mon système soit bonne (je me suis enregistré /usr/share/zoneinfo). Je ne sais pas ce qui se passait.
Ben Kreeger
1
@Benjamin Vous êtes sur la bonne voie. Le tzmodule est le point d'entrée correct à utiliser pour cette bibliothèque. J'ai mis à jour ma réponse pour refléter cela. Le dateutil.zoneinfomodule que je montrais précédemment est utilisé en interne par le tzmodule comme solution de secours s'il ne peut pas localiser la base de données zoneinfo du système. Si vous regardez à l'intérieur de la bibliothèque, vous verrez qu'il y a un tarball DB zoneinfo dans le paquet qu'il utilise s'il ne trouve pas la base de données de votre système. Mon ancien exemple essayait de frapper directement cette base de données et je suppose que vous rencontriez des problèmes pour charger cette base de données privée (n'est-ce pas sur le chemin python en quelque sorte?)
Joe Holloway
1
@JFSebastian ceci est corrigé dans # 225
Rohmer
2
@briankip Cela dépend de la sophistication de votre application, mais dans les applications générales, oui, c'est un problème. Tout d'abord, en fonction de la période de l'année, le nombre d'heures à ajouter / soustraire peut changer, car certains fuseaux horaires respectent l'heure d'été et d'autres non. Deuxièmement, les règles de fuseau horaire sont arbitrairement modifiées au fil du temps, donc si vous travaillez avec une date / heure passée, vous devez appliquer les règles qui étaient valides à ce moment-là, pas l'heure actuelle. C'est pourquoi il est préférable d'utiliser une API qui exploite la base de données Olson TZ dans les coulisses.
Joe Holloway
45

Voici une méthode résiliente qui ne dépend d'aucune bibliothèque externe:

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

Cela évite les problèmes de synchronisation dans l'exemple de DelboyJay. Et le moindre problème de timing dans l'amendement d'Erik van Oosten.

Comme note de bas de page intéressante, le décalage de fuseau horaire calculé ci-dessus peut différer de l'expression apparemment équivalente suivante, probablement en raison des modifications des règles d'heure d'été:

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

Mettre à jour: cet extrait a la faiblesse d'utiliser le décalage UTC de l'heure actuelle, qui peut différer du décalage UTC du datetime d'entrée. Voir les commentaires sur cette réponse pour une autre solution.

Pour contourner les différents moments, saisissez le temps de l'époque à partir du temps passé. Voici ce que je fais:

def utc2local (utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp (epoch) - datetime.utcfromtimestamp (epoch)
    return utc + offset
David Foster
la source
Cela fonctionne bien et ne nécessite rien d'autre que l'heure et l'heure.
Mike_K
6
@Mike_K: mais c'est incorrect. Il ne prend pas en charge l'heure d'été ou les fuseaux horaires qui avaient un décalage utc différent dans le passé pour d'autres raisons. Un support complet sans pytzdb similaire est impossible, voir PEP-431. Bien que vous puissiez écrire une solution stdlib uniquement qui fonctionne dans de tels cas sur des systèmes qui ont déjà un fuseau horaire historique, par exemple Linux, OS X, voyez ma réponse .
jfs
@JFSebastian: Vous dites que ce code ne fonctionne pas en général, mais dites également qu'il fonctionne sur OS X et Linux. Voulez-vous dire que ce code ne fonctionne pas sur Windows?
David Foster
2
@DavidFoster: votre code peut également échouer sur Linux et OS X. La différence entre la vôtre et la mienne est la solution stdlib uniquement, car la vôtre utilise un décalage actuel qui peut être différent du décalage utc à l' utc_datetimeépoque.
jfs
1
Bon appel. C'est une différence très subtile. Le décalage UTC peut également changer avec le temps. soupir
David Foster
23

Voir la documentation datetime sur tzinfo objets . Vous devez implémenter vous-même les fuseaux horaires que vous souhaitez prendre en charge. Voici des exemples au bas de la documentation.

Voici un exemple simple:

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)

Production

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
la source
Merci de l'acceptation! Je l'ai mis à jour légèrement pour traduire votre exemple d'heure spécifique. L'analyse du temps crée ce que les docs appellent un temps "naïf". Utilisez replacepour définir le fuseau horaire sur GMT et le rendre "conscient", puis utilisez astimezonepour convertir en un autre fuseau horaire.
Mark Tolonen
Désolé d'avoir échangé pour la réponse de Joe. Techniquement, votre réponse explique exactement comment le faire avec du python brut (ce qui est bon à savoir), mais la bibliothèque qu'il a suggérée me permet de trouver une solution beaucoup plus rapidement.
MattoTodd
4
Il ne semble pas qu'il gère automatiquement l'heure d'été.
Gringo Suave
@GringoSuave: ce n'était pas censé le faire. Les règles pour cela sont compliquées et changent souvent.
Mark Tolonen
19

Si vous souhaitez obtenir le résultat correct même pour l'heure qui correspond à une heure locale ambiguë (par exemple, lors d'une transition DST) et / ou le décalage utc local est différent à différents moments de votre fuseau pytzhoraire local, utilisez des fuseaux horaires:

#!/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
la source
10

Cette réponse devrait être utile si vous ne souhaitez pas utiliser d'autres modules en plus datetime.

datetime.utcfromtimestamp(timestamp) renvoie un naïf datetime objet (pas un objet conscient). Ceux qui sont conscients connaissent le fuseau horaire, et les naïfs ne le sont pas. Vous en voulez un si vous voulez convertir entre les fuseaux horaires (par exemple entre UTC et l'heure locale).

Si vous n'êtes pas celui qui instancie la date pour commencer, mais vous pouvez toujours créer un datetimeobjet naïf en temps UTC, vous voudrez peut-être essayer ce code Python 3.x pour le convertir:

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

Veillez à ne pas supposer par erreur que si votre fuseau horaire est actuellement MDT, l'heure d'été ne fonctionne pas avec le code ci-dessus car il imprime MST. Vous noterez que si vous changez le mois en août, il imprimera MDT.

Un autre moyen simple d'obtenir un datetimeobjet conscient (également en Python 3.x) est de le créer avec un fuseau horaire spécifié pour commencer. Voici un exemple, en utilisant 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))

Si vous utilisez Python 2.x, vous devrez probablement sous datetime.tzinfo- classer et utiliser cela pour vous aider à créer un datetimeobjet conscient , car il datetime.timezonen'existe pas dans Python 2.x.

Brōtsyorfuzthrāx
la source
8

Si vous utilisez Django, vous pouvez utiliser la timezone.localtimeméthode:

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
la source
2

Vous pouvez utiliser la flèche

from datetime import datetime
import arrow

now = datetime.utcnow()

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

vous pouvez vous nourrir arrow.get()de tout. horodatage, chaîne iso, etc.

d21d3q
la source
1

Je reporte traditionnellement cela au frontend - envoie les heures du backend sous forme d'horodatages ou d'un autre format datetime en UTC, puis laisse le client déterminer le décalage du fuseau horaire et restitue ces données dans le fuseau horaire approprié.

Pour une webapp, c'est assez facile à faire en javascript - vous pouvez comprendre le décalage de fuseau horaire du navigateur assez facilement en utilisant des méthodes intégrées, puis rendre correctement les données du backend.

Matt Billenstein
la source
3
Cela fonctionne bien dans de nombreux cas où vous localisez simplement l'affichage. Parfois, cependant, vous devrez effectuer une logique métier en fonction du fuseau horaire de l'utilisateur et vous voudrez pouvoir effectuer la conversion au niveau du serveur d'applications.
Joe Holloway
1

Vous pouvez utiliser calendar.timegmpour convertir votre temps en secondes depuis l'époque Unix et time.localtimepour reconvertir:

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)

Donne Fri Jan 21 05:37:21 2011(parce que je suis dans le fuseau horaire UTC + 03: 00).

myaut
la source
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

par exemple, mon fuseau horaire est ' +08: 00 '. entrée utc = 2018-10-17T00: 00: 00.111Z , alors j'obtiendrai la sortie = 2018-10-17T08: 00: 00 + 08: 00

Eureka Bing
la source
1
Vous devriez donner une meilleure description de votre problème en texte clair, où vous expliquez au reste des utilisateurs ce que vous essayez de réaliser et quel est votre problème
m33n
1

À partir de la réponse ici , vous pouvez utiliser le module de temps pour convertir d'utc en heure locale définie sur votre ordinateur:

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)
franksands
la source
0

Voici une version rapide et sale qui utilise les paramètres des systèmes locaux pour calculer le décalage horaire. REMARQUE: cela ne fonctionnera pas si vous devez convertir un fuseau horaire dans lequel votre système actuel ne fonctionne pas. J'ai testé cela avec les paramètres britanniques sous le fuseau horaire 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
la source
il devrait être datetime.fromtimestamp(ts): horodatage posix (secondes flottantes) -> objet datetime dans l'heure locale (cela fonctionne bien si le système d'exploitation se souvient des décalages utc passés pour le fuseau horaire local, c'est-à-dire sur Unix mais pas sur Windows pour les dates dans le passé)). Sinon, pytz pourrait être utilisé.
jfs
Puis-je suggérer de faire ce qui suit: offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) j'ai obtenu des différences microsecondes étranges sans cela.
Erik van Oosten
@ErikvanOosten: avez-vous essayé datetime.fromtimestamp(ts)au lieu de la réponse?
jfs
0

Consolider la réponse de franksands en une méthode pratique.

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
la source