Convertir un datetime UTC Python en datetime local en utilisant uniquement la bibliothèque standard Python

189

J'ai une instance de datetime python qui a été créée à l'aide de datetime.utcnow () et qui a persisté dans la base de données.

Pour l'affichage, je voudrais convertir l'instance datetime récupérée de la base de données en datetime locale en utilisant le fuseau horaire local par défaut (c'est-à-dire, comme si le datetime avait été créé en utilisant datetime.now ()).

Comment puis-je convertir le datetime UTC en datetime local en utilisant uniquement la bibliothèque standard Python (par exemple, aucune dépendance pytz)?

Il semble qu'une solution serait d'utiliser datetime.astimezone (tz), mais comment obtiendriez-vous le fuseau horaire local par défaut?

Nitro Zark
la source
Dans quel format l'heure a-t-elle persisté dans la base de données? S'il s'agit d'un format standard, il se peut que vous n'ayez pas besoin de faire de conversion.
Apalala
1
Cette réponse montre une manière simple d'utiliser pytz .
juan

Réponses:

275

Dans Python 3.3+:

from datetime import datetime, timezone

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

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

Utilisation pytz(à la fois de 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

Exemple

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

Production

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

Remarque: il prend en compte l'heure d'été et le récent changement de décalage utc pour le fuseau horaire MSK.

Je ne sais pas si les solutions non pytz fonctionnent sous Windows.

jfs
la source
1
que fait «normaliser»?
avi
4
@avi: en général: pytz: Pourquoi la normalisation est-elle nécessaire lors de la conversion entre les fuseaux horaires? . Remarque: Cela n'est pas nécessaire avec l'implémentation actuelle de .normalize()car le fuseau horaire source .astimezone()est UTC.
jfs
1
Je reçois TypeError: replace() takes no keyword argumentsen essayant la solution pytz.
Craig
@Craig, le code fonctionne tel quel, comme le montre la sortie de la réponse. Créez un exemple de code minimal complet qui mène à TypeError, mentionnez vos versions Python, pytz que vous utilisez et postez-le en tant que question Stack Overflow distincte.
jfs
Astuce rapide pour la solution Python 3.3+. Pour aller dans la direction opposée (Native to UTC), cela fonctionne: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
jasonrhaas
50

Vous ne pouvez pas le faire uniquement avec la bibliothèque standard car la bibliothèque standard n'a pas de fuseau horaire. Vous avez besoin de pytz ou 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())

Ou bien, vous pouvez le faire sans pytz ni dateutil en implémentant vos propres fuseaux horaires. Mais ce serait idiot.

Lennart Regebro
la source
Merci, je garderai cela à portée de main si jamais j'ai besoin d'une solution complète. Est-ce que dateutil prend en charge Python 3.1 (dit Python 2.3+ mais c'est suspect)?
Nitro Zark
pytz gère en effet mieux les changements d'heure d'été.
Lennart Regebro
2
Mise à jour: pytz et dateutil prennent tous deux en charge Python 3 de nos jours.
Lennart Regebro
1
@JFSebastian: dateutil ne peut pas faire la distinction entre les deux heures de 1h30 qui se produisent lors d'un changement d'heure d'été. Si vous devez être en mesure de faire la distinction entre ces deux moments, la solution de contournement consiste à utiliser pytz, qui peut le gérer.
Lennart Regebro
8

Vous ne pouvez pas le faire avec une bibliothèque standard. En utilisant le module pytz, vous pouvez convertir n'importe quel objet datetime naïf / conscient vers n'importe quel autre fuseau horaire. Voyons quelques exemples utilisant Python 3.

Objets naïfs créés via la méthode de classe utcnow()

Pour convertir un objet naïf dans n'importe quel autre fuseau horaire, vous devez d'abord le convertir en objet datetime conscient . Vous pouvez utiliser la replaceméthode pour convertir un objet datetime naïf en un objet datetime conscient . Ensuite, pour convertir un objet datetime conscient en un autre fuseau horaire, vous pouvez utiliser astimezonemethod.

La variable pytz.all_timezonesvous donne la liste de tous les fuseaux horaires disponibles dans le module 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)

Objets naïfs créés via la méthode de classe now()

Étant donné que la nowméthode renvoie la date et l'heure actuelles, vous devez d'abord rendre compte du fuseau horaire de l'objet datetime. La localize fonction convertit un objet datetime naïf en un objet datetime sensible au fuseau horaire. Ensuite, vous pouvez utiliser la astimezoneméthode pour le convertir en un autre fuseau horaire.

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)
N Randhawa
la source
6

Je pense que je l'ai compris: calcule le nombre de secondes depuis l'époque, puis convertit en un timzeone local en utilisant time.localtime, puis convertit la structure de temps en une date / heure ...

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 )

Il applique correctement l'heure d'été / hiver:

>>> 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)
Nitro Zark
la source
1
Pouah. Mais cela devrait fonctionner sur Unix, au moins. Pour Windows, cela peut être incorrect si vos règles d'heure d'été ont changé depuis la date de conversion. Pourquoi ne voulez-vous pas utiliser une bibliothèque?
Lennart Regebro
Je n'ai besoin que d'une conversion utc / locale, donc faire glisser une telle bibliothèque semble exagéré. Puisque je suis le seul utilisateur de l'application, je peux vivre avec certaines limitations. Cela évite également de gérer les problèmes liés aux dépendances (support Python 3?, Qualité Windows ...) qui seraient plus coûteux que de travailler sur mon petit script.
Nitro Zark
1
Bien. si vous avez besoin de Python3, vous n'avez pas de chance. Mais sinon, rechercher et créer votre propre solution est excessif par rapport à l'utilisation d'une bibliothèque.
Lennart Regebro
1
+1 (pour compenser le vote négatif: il peut être nécessaire de rechercher des solutions uniquement stdlib) avec l'avertissement @Lennart mentionné. Étant donné que dateutil peut échouer à la fois sous Unix et Windows. btw, vous pouvez extraire utctotimestamp(utc_dt) -> (seconds, microseconds)de votre code pour plus de clarté, voir l' implémentation de Python 2.6-3.x
jfs
1
Un pytz (et dateutil) fonctionne sur Python 3 depuis un certain temps maintenant, donc les problèmes Python3 / Windows / Qualité ne sont plus un problème.
Lennart Regebro
6

S'appuyant sur le commentaire d'Alexei. Cela devrait également fonctionner pour l'heure d'été.

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)
John Kerns
la source
4

La bibliothèque Python standard n'est livrée avec aucune tzinfoimplémentation. J'ai toujours considéré cela comme une lacune surprenante du module datetime.

La documentation de la classe tzinfo contient quelques exemples utiles. Recherchez le gros bloc de code à la fin de la section.

Mark Ransom
la source
1
Ma conjecture à propos de l'implémentation de base est que la base de données des fuseaux horaires doit être mise à jour régulièrement car certains pays n'ont pas de règles fixes pour déterminer les périodes d'heure d'été. Si je ne me trompe pas, la JVM par exemple doit être mise à jour pour obtenir la dernière base de données de fuseaux horaires ...
Nitro Zark
@Nitro Zark, au moins ils auraient dû en avoir un pour UTC. Le osmodule aurait pu en fournir un pour l'heure locale en fonction des fonctions du système d'exploitation.
Mark Ransom
1
Je vérifiais la liste des nouvelles fonctionnalités de la 3.2 et ils ont ajouté le fuseau horaire UTC dans la 3.2 ( docs.python.org/dev/whatsnew/3.2.html#datetime ). Il ne semble pas y avoir de fuseau horaire local cependant ...
Nitro Zark
2

Python 3.9 ajoute le zoneinfomodule, donc maintenant cela peut être fait comme suit (stdlib uniquement):

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

L'Europe centrale a 1 ou 2 heures d'avance sur UTC, donc local_aware:

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

comme str:

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

Windows n'a pas de base de données de fuseaux horaires système, donc ici un package supplémentaire est nécessaire:

pip install tzdata  

Il existe un backport pour permettre l'utilisation dans Python 3.6 à 3.8 :

sudo pip install backports.zoneinfo

Ensuite:

from backports.zoneinfo import ZoneInfo
xjcl
la source
vous n'avez même pas besoin zoneinfoici - voir la première partie de la réponse de jfs (la partie Python3.3 +) ;-)
MrFuppes
@MrFuppes je pense que ZoneInfo('localtime')c'est beaucoup plus clair que tz=None(après tout, on pourrait s'attendre tz=Noneà supprimer le fuseau horaire ou à retourner UTC plutôt que l'heure locale).
xjcl
@MrFuppes Plus la question peut impliquer OP ayant un site Web et souhaitant afficher les horodatages des utilisateurs dans leur heure locale. Pour cela, vous devrez convertir le fuseau horaire explicite de l'utilisateur plutôt que l'heure locale.
xjcl
0

Une manière simple (mais peut-être imparfaite) qui fonctionne dans Python 2 et 3:

import time
import datetime

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

Son avantage est qu'il est trivial d'écrire une fonction inverse

Alexei Averchenko
la source
1
Cela donne un résultat erroné, car time.timezonecela ne prend pas en compte l'heure d'été.
guyarad
0

Le moyen le plus simple que j'ai trouvé est d'obtenir le décalage horaire de l'endroit où vous vous trouvez, puis de le soustraire de l'heure.

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

Cela fonctionne pour moi, en Python 3.5.2.

Productions Zld
la source
0

Voici une autre façon de changer le fuseau horaire au format datetime (je sais que j'ai gaspillé mon énergie là-dessus mais je n'ai pas vu cette page donc je ne sais pas comment) sans min. et sec. car je n'en ai pas besoin pour mon projet:

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)
Client
la source
Utilisez timedelta pour basculer entre les fuseaux horaires. Tout ce dont vous avez besoin est le décalage en heures entre les fuseaux horaires. Pas besoin de jouer avec les limites des 6 éléments d'un objet datetime. timedelta gère aussi facilement les années bissextiles, les siècles bissextiles, etc. Vous devez «from datetime import timedelta». Ensuite, si le décalage est une variable en heures: timeout = timein + timedelta (hours = offset), où timein et timeout sont des objets datetime. par exemple, timein + timedelta (heures = -8) convertit de GMT en PST.
Karl Rudnick
0

C'est une manière terrible de le faire mais cela évite de créer une définition. Il remplit l'exigence de s'en tenir à la bibliothèque de base 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])
Arthur D. Howland
la source
Bien que ce soit intéressant et que je vais utiliser cette méthode, il n'utilise pas uniquement le stdlib. Il utilise Pandas pour faire les conversions pandas.pydata.org/pandas-docs/version/0.25/reference/api/…
Tim Hughes
-3

Utilisez timedelta pour basculer entre les fuseaux horaires. Tout ce dont vous avez besoin est le décalage en heures entre les fuseaux horaires. Pas besoin de jouer avec les limites des 6 éléments d'un objet datetime. timedelta gère aussi facilement les années bissextiles, les siècles bissextiles, etc. Vous devez d'abord

from datetime import datetime, timedelta

Alors si offsetest le delta du fuseau horaire en heures:

timeout = timein + timedelta(hours = offset)

où timein et timeout sont des objets datetime. par exemple

timein + timedelta(hours = -8)

convertit de GMT à PST.

Alors, comment déterminer offset? Voici une fonction simple à condition que vous n'ayez que quelques possibilités de conversion sans utiliser d'objets datetime qui sont «conscients» du fuseau horaire, ce que font bien d'autres réponses. Un peu manuel, mais parfois la clarté est meilleure.

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)

Après avoir examiné les nombreuses réponses et joué avec le code le plus serré auquel je puisse penser (pour l'instant), il semble préférable que toutes les applications, où le temps est important et où les fuseaux horaires mixtes doivent être pris en compte, fassent un réel effort pour créer tous les objets datetime. "conscient". Alors il semblerait que la réponse la plus simple soit:

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

pour convertir en GMT par exemple. Bien sûr, pour convertir vers / depuis n'importe quel autre fuseau horaire de votre choix, local ou autre, utilisez simplement la chaîne de fuseau horaire appropriée que pytz comprend (à partir de pytz.all_timezones). L'heure d'été est alors également prise en compte.

Karl Rudnick
la source
1
Ce n'est pas une bonne idée. L'heure d'été ne change pas à la même date pour le monde entier. Utilisez les fonctions datetime pour ce faire.
Gabe le
@gabe - Comprenez que le tableau n'est pas une bonne idée, même s'il peut convenir à quelqu'un qui ne couvre que les fuseaux horaires américains. Comme je l'ai dit à la fin, le meilleur moyen est de TOUJOURS rendre les objets datetime "conscients" afin que vous puissiez facilement utiliser les fonctions datetime.
Karl Rudnick