Overview
En esta guía, puedes aprender a gestionar Python datetime objetos en PyMongo.
Terminología
Python usa un tipo de dato especializado, datetime.datetime, para representar fechas y horas. MongoDB almacena los valores de datetime en tiempo universal coordinado (UTC), un estándar global de tiempo local en Londres, Inglaterra.
Datetimes ingenuos
Un valor de datetime es ingenuo cuando no incluye información suplementaria sobre su diferencia horaria respecto a UTC o zona horaria. El siguiente es un ejemplo de un objeto datetime ingenuo:
datetime(2002, 10, 27, 14, 0, 0)
Fechas y horas con conocimiento de zona horaria
Un valor de datetime es consciente cuando incluye un atributo tzinfo. Este atributo indica la diferencia horaria respecto al horario UTC, su zona horaria y si el horario de verano estaba en vigor. A continuación se muestra un ejemplo de un objeto datetime consciente:
datetime(2002, 10, 27, 6, 0, tzinfo=<DstTzInfo 'US/Pacific' PST-1 day, 16:00:00 STD>)
Importante
Fechas y horas ingenuas y UTC
PyMongo trata los valores datetime ingenuos como valores UTC. PyMongo no convierte los valores ingenuos a UTC; simplemente los interpreta como UTC para su almacenamiento y recuperación.
Para evitar ambigüedades y posibles errores, utilice valores datetime con información de zona horaria explícita. Usar fechas y horas con información de zona horaria le ayudará a evitar discrepancias horarias.
Localización
Localización es el proceso de añadir o restar horas a un valor datetime para traducir su valor a otra zona horaria. Para localizar un valor datetime, realiza los siguientes pasos:
Utilice pip para instalar la librería
pytzen su entorno de Python:pip install pytzCrea un objeto
pytz.timezone. Pasa la zona horaria de destino al constructor como una cadena.Llama al método
localize()en el objetotimezone, pasando el valordatetimepara localizar.
El siguiente ejemplo de código localiza un valor datetime en la zona horaria "US/Pacific", con un desfase de ocho horas:
from datetime import datetime from pytz import timezone utc_datetime = datetime(2002, 10, 27, 6, 0, 0) pacific = timezone("US/Pacific") local_datetime = pacific.localize(utc_datetime) print(f"UTC datetime: {utc_datetime}") print(f"Local datetime: {local_datetime}")
UTC datetime: 2002-10-27 06:00:00 Local datetime: 2002-10-27 06:00:00-08:00
Para una lista canónica de cadenas de zonas horarias, consúltese el Base de datos de zona horaria o su artículo correspondiente en Wikipedia.
Importante
Con PyMongo, no puedes guardar instancias de datetime.date, porque no existe un tipo BSON para fechas sin horas. Convierte todos los objetos date en objetos datetime antes de guardarlos en MongoDB.
Leyendo fechas y horas
Al usar PyMongo para recuperar un valor datetime, el controlador puede formatearlo como UTC ingenuo, UTC consciente o valor localizado. Las siguientes secciones describen cómo recuperar cada tipo de valor.
Para estas secciones, se asume que una colección de MongoDB llamada sample_collection contiene el siguiente documento. El valor del campo "date" es un valor UTC datetime.
{"date": datetime(2002, 10, 27, 14, 0, 0)}
Fecha y hora UTC ingenua
De forma predeterminada, PyMongo recupera valores UTC datetime sin información adicional. El siguiente ejemplo de código recupera el documento de muestra e imprime el valor datetime. El valor impreso es idéntico al del documento de muestra. Seleccione Synchronous o la pestaña Asynchronous para ver el código correspondiente:
from datetime import datetime collection = database["sample_collection"] find_result = collection.find_one()["date"] print(f"datetime: {find_result}") print(f"datetime.tzinfo: {find_result.tzinfo}")
datetime: 2002-10-27 14:00:00 datetime.tzinfo: None
from datetime import datetime collection = database["sample_collection"] find_result = (await collection.find_one())["date"] print(f"datetime: {find_result}") print(f"datetime.tzinfo: {find_result.tzinfo}")
datetime: 2002-10-27 14:00:00 datetime.tzinfo: None
Fecha y hora UTC conscientes
Para indicar a PyMongo que recupere un valor aware datetime, cree un objeto CodecOptions y pase tz_aware = True al constructor. Luego, pasa el objeto CodecOptions al método get_collection().
El siguiente ejemplo de código recupera el valor datetime del documento de muestra como un datetime consciente. Seleccione la pestaña Synchronous o Asynchronous para ver el código correspondiente:
from pymongo import MongoClient from datetime import datetime from bson.codec_options import CodecOptions options = CodecOptions(tz_aware = True) collection = database.get_collection("sample_collection", options) find_result = collection.find_one()["date"] print(f"datetime: {find_result}") print(f"datetime.tzinfo: {find_result.tzinfo}")
datetime: 2002-10-27 14:00:00+00:00 datetime.tzinfo: <bson.tz_util.FixedOffset object at 0x104db2b80>
from pymongo import MongoClient from datetime import datetime from bson.codec_options import CodecOptions options = CodecOptions(tz_aware = True) collection = database.get_collection("sample_collection", options) find_result = (await collection.find_one())["date"] print(f"datetime: {find_result}") print(f"datetime.tzinfo: {find_result.tzinfo}")
datetime: 2002-10-27 14:00:00+00:00 datetime.tzinfo: <bson.tz_util.FixedOffset object at 0x104db2b80>
Fecha y hora localizada
Si planeas mostrar valores de datetime al usuario, puedes instruir a PyMongo para que convierta automáticamente todos los tiempos leídos desde MongoDB a una zona horaria específica. Para hacerlo, crea un objeto timezone para la zona horaria objetivo, como se describe en la sección Terminología. Luego, crea un objeto CodecOptions y pasa los siguientes parámetros al constructor:
tz_aware:Establecer enTrue.tzinfo:El objetotimezone.
El siguiente ejemplo de código recupera el documento de muestra, pero usa los argumentos tz_aware y tzinfo para localizar automáticamente el valor datetime a la zona horaria "US/Pacific". Selecciona la pestaña Synchronous o Asynchronous para ver el código correspondiente:
from pymongo import MongoClient from datetime import datetime from bson.codec_options import CodecOptions import pytz from pytz import timezone pacific = timezone("US/Pacific") options = CodecOptions(tz_aware = True, tzinfo = pacific) collection = database.get_collection("sample_collection", options) find_result = collection.find_one()["date"] print(f"datetime: {find_result}") print(f"datetime.tzinfo: {find_result.tzinfo}")
datetime: 2002-10-27 06:00:00-08:00 datetime.tzinfo: US/Pacific
from pymongo import MongoClient from datetime import datetime from bson.codec_options import CodecOptions import pytz from pytz import timezone pacific = timezone("US/Pacific") options = CodecOptions(tz_aware = True, tzinfo = pacific) collection = database.get_collection("sample_collection", options) find_result = (await collection.find_one())["date"] print(f"datetime: {find_result}") print(f"datetime.tzinfo: {find_result.tzinfo}")
datetime: 2002-10-27 06:00:00-08:00 datetime.tzinfo: US/Pacific
Tip
El ejemplo anterior especifica las opciones del códec a nivel de colección. También puedes especificar opciones de códec a nivel de cliente o de base de datos.
Almacenamiento de fechas y horas
Para mantener la coherencia, almacene solo valores UTC datetime en MongoDB. Cuando utilizas PyMongo para crear o actualizar un campo que contiene un valor datetime, el controlador primero verifica si el valor datetime es ingenuo o consciente:
Si
datetimees ingenuo, PyMongo supone quedatetimeestá en UTC y lo almacena en MongoDB sin cambios.Si el
datetimees consciente, PyMongo convierte automáticamente la hora a UTC antes de almacenarla en MongoDB.
El siguiente ejemplo de código inserta un documento que contiene un valor datetime localizado en la zona horaria "US/Pacific". PyMongo utiliza la zona horaria adjunta para convertir la hora local a UTC. Al recuperar el documento de MongoDB, el valor datetime está en UTC. Seleccione la pestaña Synchronous o Asynchronous para ver el código correspondiente:
from pymongo import MongoClient from datetime import datetime from pytz import timezone utc_datetime = datetime(2002, 10, 27, 6, 0, 0) pacific = timezone("US/Pacific") local_datetime = pacific.localize(utc_datetime) print(f"datetime before storage: {local_datetime}") collection.insert_one({"date": local_datetime}) find_result = collection.find_one()["date"] print(f"datetime after storage: {find_result}")
datetime before storage: 2002-10-27 06:00:00-08:00 datetime after storage: 2002-10-27 14:00:00
from pymongo import MongoClient from datetime import datetime from pytz import timezone utc_datetime = datetime(2002, 10, 27, 6, 0, 0) pacific = timezone("US/Pacific") local_datetime = pacific.localize(utc_datetime) print(f"datetime before storage: {local_datetime}") await collection.insert_one({"date": local_datetime}) find_result = (await collection.find_one())["date"] print(f"datetime after storage: {find_result}")
datetime before storage: 2002-10-27 06:00:00-08:00 datetime after storage: 2002-10-27 14:00:00
Importante
fecha y hora.ahora()
Evite llamar al método datetime.now() sin argumentos. Esto devuelve la hora local actual.
En su lugar, siempre llama al método datetime.now(tz=datetime.timezone.utc), que devuelve el tiempo actual en UTC.
Manejo de fechas y horas fuera de rango
La clase datetime de Python solo puede representar valores de datetime entre datetime.min y datetime.max (años 1-9999). Puedes representar un rango mucho mayor de fechas y horas utilizando BSON, que permite cualquier valor de milisegundo de 64bits desde la Unix epoch.
Para representar una hora BSON con PyMongo, cree un objeto datetime_ms.DatetimeMS, un contenedor para el tipo int integrado de Python. Puede crear manualmente un objeto DatetimeMS pasando uno de los siguientes valores:
Un
intque representa el número de milisegundos desde el Unix epochUn objeto
datetime
El siguiente ejemplo de código construye un objeto DatetimeMS pasando un valor int que representa el año -146136543, una fecha fuera del rango datetime:
from bson.datetime_ms import DatetimeMS out_of_range = DatetimeMS(-(2**62))
También puedes indicarle a PyMongo que decodifique automáticamente los valores UTC datetime como objetos DatetimeMS. Para hacerlo, establece el parámetro datetime_conversion de CodecOptions a un valor del enum datetime_ms.DatetimeConversion. Las siguientes secciones describen estos valores.
Tip
DatetimeMS los objetos admiten métodos de comparación de tipo enriquecido respecto de otras instancias de DatetimeMS. También puedes convertirlos en objetos datetime usando el método DatetimeMS.to_datetime().
DatetimeConversion.DATETIME
DatetimeConversion.DATETIME Es el valor predeterminado. Este valor provoca que PyMongo genere un error al intentar decodificar una fecha fuera de rango, como se muestra en el siguiente ejemplo:
from datetime import datetime from bson import encode, decode from bson.datetime_ms import DatetimeMS out_of_range = DatetimeMS(-(2**62)) val = encode({"date": out_of_range}) decoded = decode(val) print(decoded)
... bson.errors.InvalidBSON: year -146136543 is out of range (Consider Using CodecOptions(datetime_conversion=DATETIME_AUTO) or MongoClient(datetime_conversion='DATETIME_AUTO')). ...
DatetimeConversion.DATETIME_MS
Este valor indica a PyMongo que solo retorne DatetimeMS objetos, incluso si la fecha está dentro del rango de datetime:
from datetime import datetime from bson import encode, decode from bson.datetime_ms import DatetimeMS from bson.codec_options import CodecOptions, DatetimeConversion val = encode({"date": datetime(1970, 1, 2)}) codec_ms = CodecOptions(datetime_conversion=DatetimeConversion.DATETIME_MS) decoded = decode(val, codec_options=codec_ms) print(decoded)
{"date": DatetimeMS(86400000)}
DatetimeConversion.DATETIME_AUTO
Este valor indica a PyMongo que devuelva objetos datetime si el valor está dentro del rango datetime y, en caso contrario, un objeto DatetimeMS. El siguiente ejemplo de código codifica y decodifica una fecha y hora dentro del rango datetime y otra fuera del rango:
from datetime import datetime from bson import encode, decode from bson.datetime_ms import DatetimeMS from bson.codec_options import CodecOptions, DatetimeConversion in_range = encode({"date": datetime(1970, 1, 1)}) out_of_range = encode({"date": DatetimeMS(-(2**62))}) codec_auto = CodecOptions(datetime_conversion=DatetimeConversion.DATETIME_AUTO) in_decoded = decode(in_range, codec_options=codec_auto) out_decoded = decode(out_of_range, codec_options=codec_auto) print(f"in-range date: {in_decoded}") print(f"out-of-range date: {out_decoded}")
in-range date: {"date": datetime.datetime(1970, 1, 1, 0, 0)} out-of-range date: {'x': DatetimeMS(-4611686018427387904)}
DatetimeConversion.DATETIME_CLAMP
Este valor "fija" los objetos datetime resultantes, obligándolos a estar dentro del rango datetime (recortados a 999,000 microsegundos).
El siguiente ejemplo de código codifica y decodifica una fecha y hora anterior al rango datetime y una fecha y hora posterior al rango datetime. Los valores resultantes se encuentran al principio y al final del rango permitido.
from datetime import datetime from bson import encode, decode from bson.datetime_ms import DatetimeMS from bson.codec_options import CodecOptions, DatetimeConversion before = encode({"date": DatetimeMS(-(2**62))}) after = encode({"date": DatetimeMS(2**62)}) codec_clamp = CodecOptions(datetime_conversion=DatetimeConversion.DATETIME_CLAMP) before_decoded = decode(before, codec_options=codec_clamp) after_decoded = decode(after, codec_options=codec_clamp) print(f"datetime before the range: {before_decoded}") print(f"datetime after the range: {after_decoded}")
datetime before the range: {"date": datetime.datetime(1, 1, 1, 0, 0)} datetime after the range: {"date": datetime.datetime(9999, 12, 31, 23, 59, 59, 999000)}
Solución de problemas
OverflowError al decodificar fechas almacenadas por el driver de otro lenguaje
PyMongo decodifica valores BSON datetime en instancias de la clase datetime.datetime de Python. Las instancias de datetime.datetime están limitadas a años entre datetime.MINYEAR (1) y datetime.MAXYEAR (9999). Algunos controladores de MongoDB pueden almacenar valores de fecha y hora BSON con años muy diferentes a los admitidos por datetime.datetime.
Existen algunas formas de solucionar este problema. A partir de PyMongo 4.3, bson.decode puede decodificar los valores BSON datetime de una de cuatro maneras. Puedes especificar el método de conversión mediante el uso del parámetro datetime_conversion de ~bson.codec_options.CodecOptions.
La opción de conversión por defecto es ~bson.codec_options.DatetimeConversion.DATETIME, que intentará decodificar el valor como un datetime.datetime, permitiendo que ocurra ~builtin.OverflowError para fechas fuera de rango. ~bson.codec_options.DatetimeConversion.DATETIME_AUTO modifica este comportamiento para devolver ~bson.datetime_ms.DatetimeMS cuando las representaciones están fuera de rango, mientras que devuelve objetos ~datetime.datetime como antes. Seleccione la pestaña Synchronous o Asynchronous para ver el código correspondiente:
from datetime import datetime from bson.datetime_ms import DatetimeMS from bson.codec_options import DatetimeConversion from pymongo import MongoClient client = MongoClient(datetime_conversion=DatetimeConversion.DATETIME_AUTO) client.db.collection.insert_one({"x": datetime(1970, 1, 1)}) client.db.collection.insert_one({"x": DatetimeMS(2**62)}) for x in client.db.collection.find(): print(x)
{'_id': ObjectId('...'), 'x': datetime.datetime(1970, 1, 1, 0, 0)} {'_id': ObjectId('...'), 'x': DatetimeMS(4611686018427387904)}
from datetime import datetime from bson.datetime_ms import DatetimeMS from bson.codec_options import DatetimeConversion from pymongo import MongoClient client = AsyncMongoClient(datetime_conversion=DatetimeConversion.DATETIME_AUTO) await client.db.collection.insert_one({"x": datetime(1970, 1, 1)}) await client.db.collection.insert_one({"x": DatetimeMS(2**62)}) async for x in client.db.collection.find(): print(x)
{'_id': ObjectId('...'), 'x': datetime.datetime(1970, 1, 1, 0, 0)} {'_id': ObjectId('...'), 'x': DatetimeMS(4611686018427387904)}
Para otras opciones, consulta la documentación de la API para la clase DatetimeConversion.
Otra opción que no implica configurar datetime_conversion es filtrar los valores del documento fuera del rango admitido por ~datetime.datetime. Seleccione la pestaña Synchronous o Asynchronous para ver el código correspondiente:
from datetime import datetime coll = client.test.dates cur = coll.find({'dt': {'$gte': datetime.min, '$lte': datetime.max}})
from datetime import datetime coll = client.test.dates cur = coll.find({'dt': {'$gte': datetime.min, '$lte': datetime.max}})
Si no necesita el valor datetime, puede filtrar solo ese campo. Seleccione la pestaña Synchronous o Asynchronous para ver el código correspondiente:
cur = coll.find({}, projection={'dt': False})
cur = coll.find({}, projection={'dt': False})
Documentación de la API
Para obtener más información sobre cómo trabajar con fechas y horas en PyMongo, consulte la siguiente documentación de API:
datetimeen docs.python.orgpytzen pypi.org