web-dev-qa-db-fra.com

Fuseau horaire SQLAlchemy DateTime

Le type DateTime de SQLAlchemy permet un timezone=True argument pour enregistrer un objet datetime non naïf dans la base de données et le renvoyer comme tel. Existe-t-il un moyen de modifier le fuseau horaire du tzinfo que SQLAlchemy transmet pour qu'il puisse être, par exemple, UTC? Je me rends compte que je pourrais simplement utiliser default=datetime.datetime.utcnow; cependant, c'est un temps naïf qui accepterait avec plaisir quelqu'un passant dans un temps naïf basé sur l'heure locale, même si j'utilisais timezone=True avec elle, car elle rend l'heure locale ou UTC non naïve sans avoir de fuseau horaire de base pour la normaliser. J'ai essayé (en utilisant pytz ) de rendre l'objet datetime non naïf, mais quand je l'enregistre dans la base de données, il revient naïf.

Notez que datetime.datetime.utcnow ne fonctionne pas avec timezone=True tellement bien:

import sqlalchemy as sa
from sqlalchemy.sql import select
import datetime

metadata = sa.MetaData('postgres://user:pass@machine/db')

data_table = sa.Table('data', metadata,
    sa.Column('id',   sa.types.Integer, primary_key=True),
    sa.Column('date', sa.types.DateTime(timezone=True), default=datetime.datetime.utcnow)
)

metadata.create_all()

engine = metadata.bind
conn = engine.connect()
result = conn.execute(data_table.insert().values(id=1))

s = select([data_table])
result = conn.execute(s)
row = result.fetchone()

(1, datetime.datetime (2009, 1, 6, 0, 9, 36, 891887))

row[1].utcoffset()

datetime.timedelta (-1, 64800) # c'est mon décalage d'heure locale !!

datetime.datetime.now(tz=pytz.timezone("US/Central"))

datetime.timedelta (-1, 64800)

datetime.datetime.now(tz=pytz.timezone("UTC"))

datetime.timedelta (0) #UTC

Même si je le change pour utiliser explicitement UTC:

...

data_table = sa.Table('data', metadata,
    sa.Column('id',   sa.types.Integer, primary_key=True),
    sa.Column('date', sa.types.DateTime(timezone=True), default=datetime.datetime.now(tz=pytz.timezone('UTC')))
)

row[1].utcoffset()

...

datetime.timedelta (-1, 64800) # il n'a pas utilisé le fuseau horaire que j'ai explicitement ajouté

Ou si je laisse tomber le timezone=True:

...

data_table = sa.Table('data', metadata,
    sa.Column('id',   sa.types.Integer, primary_key=True),
    sa.Column('date', sa.types.DateTime(), default=datetime.datetime.now(tz=pytz.timezone('UTC')))
)

row[1].utcoffset() is None

...

Vrai # il n'a même pas enregistré de fuseau horaire dans la base de données cette fois

42
alif

http://www.postgresql.org/docs/8.3/interactive/datatype-datetime.html#DATATYPE-TIMEZONES

Toutes les dates et heures sensibles au fuseau horaire sont stockées en interne en UTC. Ils sont convertis en heure locale dans la zone spécifiée par le paramètre de configuration de fuseau horaire avant d'être affichés au client.

Le seul moyen de le stocker avec postgresql est de le stocker séparément.

18
iny

une solution est donnée dans cette question réponse:

vous pouvez contourner cela en stockant tous les objets d'heure (date) dans votre base de données en UTC et en convertissant les objets naïfs datetime résultants en objets avertis lors de la récupération.

le seul inconvénient est que vous perdez les informations de fuseau horaire, mais c'est probablement une bonne idée de stocker vos objets datetime dans utc, de toute façon.

si vous vous souciez des informations de fuseau horaire, je les stockerais séparément et ne convertirais l'utc en heure locale que dans la dernière instance possible (par exemple juste avant l'affichage)

ou peut-être n'avez-vous pas besoin de vous soucier après tout, et pouvez utiliser les informations de fuseau horaire local sur la machine sur laquelle vous exécutez votre programme, ou sur le navigateur de l'utilisateur s'il s'agit d'une application Web.

9
flying sheep

Une façon de résoudre ce problème consiste à toujours utiliser des champs sensibles au fuseau horaire dans la base de données. Mais notez que le même temps peut s'exprimer différemment selon le fuseau horaire, et même si ce n'est pas un problème pour les ordinateurs c'est très gênant pour nous:

2003-04-12 23:05:06 +01:00
2003-04-13 00:05:06 +02:00 # This is the same time as above!

Postgresql stocke également toutes les dates et heures sensibles au fuseau horaire en UTC. Ils sont convertis en heure locale dans la zone spécifiée par le paramètre de configuration de fuseau horaire avant d'être affichés au client.

Au lieu de cela, je recommande d'utiliser UTC horodatages à la fois dans l'application et les dates et heures naïves du fuseau horaire dans la base de données, et de les convertir uniquement en fuseau horaire local des utilisateurs avant que l'utilisateur ne les voie.

Cette stratégie vous permet d'avoir le code le plus propre, en évitant les conversions et les confusions de fuseau horaire, et fait fonctionner votre base de données et votre application indépendamment des différences de "fuseau horaire local". Par exemple, votre machine de développement et votre serveur de production peuvent s'exécuter sur le cloud dans différents fuseaux horaires.

Pour ce faire, indiquez à Postgresql que vous souhaitez voir les fuseaux horaires en UTC avant d'initialiser le moteur.

Dans SqlAlchemy, vous le faites comme ceci:

engine = create_engine(..., connect_args={"options": "-c timezone=utc"})

Et si vous utilisez tornado-sqlalchemy, vous pouvez utiliser:

factory = make_session_factory(..., connect_args={"options": "-c timezone=utc"})

Puisque nous utilisons tous les fuseaux horaires UTC partout, nous utilisons simplement des dates et heures naïves de fuseau horaire dans le modèle:

created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime)

Et la même chose au cas où si vous utilisez l'alambic:

sa.Column('created_at', sa.DateTime()),
sa.Column('updated_at', sa.DateTime()),

Et dans le code, utilisez l'heure UTC:

from datetime import datetime
...
model_object.updated_at = datetime.now(timezone.utc)
0
Caner