Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 36 additions & 16 deletions doc/examples/datetimes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,32 +125,52 @@ To decode UTC datetime values as :class:`~bson.datetime_ms.DatetimeMS`,
:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME_AUTO`,
:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME_CLAMP`.
:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME` is the default
option and has the behavior of raising an exception upon attempting to
decode an out-of-range date.
option and has the behavior of raising an :class:`~builtin.OverflowError` upon
attempting to decode an out-of-range date.
:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME_MS` will only return
:class:`~bson.datetime_ms.DatetimeMS` objects, regardless of whether the
represented datetime is in- or out-of-range.
represented datetime is in- or out-of-range:

.. doctest::

>>> from datetime import datetime
>>> from bson import encode, decode
>>> from bson.datetime_ms import DatetimeMS
>>> from bson.codec_options import CodecOptions, DatetimeConversionOpts
>>> x = encode({"x": datetime(1970, 1, 1)})
>>> codec_ms = CodecOptions(datetime_conversion=DatetimeConversionOpts.DATETIME_MS)
>>> decode(x, codec_options=codec_ms)
{'x': DatetimeMS(0)}

:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME_AUTO` will return
:class:`~datetime.datetime` if the underlying UTC datetime is within range,
or :class:`~bson.datetime_ms.DatetimeMS` if the underlying datetime
cannot be represented using the builtin Python :class:`~datetime.datetime`.
cannot be represented using the builtin Python :class:`~datetime.datetime`:

.. doctest::

>>> x = encode({"x": datetime(1970, 1, 1)})
>>> y = encode({"x": DatetimeMS(-2**62)})
>>> codec_auto = CodecOptions(datetime_conversion=DatetimeConversionOpts.DATETIME_AUTO)
>>> decode(x, codec_options=codec_auto)
{'x': datetime.datetime(1970, 1, 1, 0, 0)}
>>> decode(y, codec_options=codec_auto)
{'x': DatetimeMS(-4611686018427387904)}

:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME_CLAMP` will clamp
resulting :class:`~datetime.datetime` objects to be within
:attr:`~datetime.datetime.min` and :attr:`~datetime.datetime.max`
(trimmed to `999000` microseconds).

An example of encoding and decoding using `DATETIME_MS` is as follows:
(trimmed to `999000` microseconds):

.. doctest::
>>> from datetime import datetime
>>> from bson import encode, decode
>>> from bson.datetime_ms import DatetimeMS
>>> from bson.codec_options import CodecOptions,DatetimeConversionOpts
>>> x = encode({"x": datetime(1970, 1, 1)})
>>> x
b'\x10\x00\x00\x00\tx\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> decode(x, codec_options=CodecOptions(datetime_conversion=DatetimeConversionOpts.DATETIME_MS))
{'x': DatetimeMS(0)}

>>> x = encode({"x": DatetimeMS(2**62)})
>>> y = encode({"x": DatetimeMS(-2**62)})
>>> codec_clamp = CodecOptions(datetime_conversion=DatetimeConversionOpts.DATETIME_CLAMP)
>>> decode(x, codec_options=codec_clamp)
{'x': datetime.datetime(9999, 12, 31, 23, 59, 59, 999000)}
>>> decode(y, codec_options=codec_clamp)
{'x': datetime.datetime(1, 1, 1, 0, 0)}

:class:`~bson.datetime_ms.DatetimeMS` objects have support for rich comparison
methods against other instances of :class:`~bson.datetime_ms.DatetimeMS`.
Expand Down
41 changes: 37 additions & 4 deletions doc/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ collection, configured to use :class:`~bson.son.SON` instead of dict:
>>> from bson import CodecOptions, SON
>>> opts = CodecOptions(document_class=SON)
>>> opts
CodecOptions(document_class=...SON..., tz_aware=False, uuid_representation=UuidRepresentation.UNSPECIFIED, unicode_decode_error_handler='strict', tzinfo=None, type_registry=TypeRegistry(type_codecs=[], fallback_encoder=None))
CodecOptions(document_class=...SON..., tz_aware=False, uuid_representation=UuidRepresentation.UNSPECIFIED, unicode_decode_error_handler='strict', tzinfo=None, type_registry=TypeRegistry(type_codecs=[], fallback_encoder=None), datetime_conversion=DatetimeConversionOpts.DATETIME)
>>> collection_son = collection.with_options(codec_options=opts)

Now, documents and subdocuments in query results are represented with
Expand Down Expand Up @@ -489,9 +489,42 @@ limited to years between :data:`datetime.MINYEAR` (usually 1) and
driver) can store BSON datetimes with year values far outside those supported
by :class:`datetime.datetime`.

There are a few ways to work around this issue. One option is to filter
out documents with values outside of the range supported by
:class:`datetime.datetime`::
There are a few ways to work around this issue. Starting with PyMongo 4.3,
:func:`bson.decode` can decode BSON datetimes in one of four ways, and can
be specified using the ``datetime_conversion`` parameter of
:class:`~bson.codec_options.CodecOptions`.

The default option is
:attr:`~bson.codec_options.DatetimeConversionOpts.DATETIME`, which will
attempt to decode as a :class:`datetime.datetime`, allowing
:class:`~builtin.OverflowError` to occur upon out-of-range dates.
:attr:`~bson.codec_options.DatetimeConversionOpts.DATETIME_AUTO` alters
this behavior to instead return :class:`~bson.datetime_ms.DatetimeMS` when
representations are out-of-range, while returning :class:`~datetime.datetime`
objects as before:

.. doctest::

>>> from datetime import datetime
>>> from bson.datetime_ms import DatetimeMS
>>> from bson.codec_options import DatetimeConversionOpts
>>> from pymongo import MongoClient
>>> client = MongoClient(datetime_conversion=DatetimeConversionOpts.DATETIME_AUTO)
>>> client.db.collection.insert_one({"x": datetime(1970, 1, 1)})
<pymongo.results.InsertOneResult object at 0x...>
>>> client.db.collection.insert_one({"x": DatetimeMS(2**62)})
<pymongo.results.InsertOneResult object at 0x...>
>>> for x in client.db.collection.find():
... print(x)
{'_id': ObjectId('...'), 'x': datetime.datetime(1970, 1, 1, 0, 0)}
{'_id': ObjectId('...'), 'x': DatetimeMS(4611686018427387904)}

For other options, please refer to
:class:`~bson.codec_options.DatetimeConversionOpts`.

Another option that does not involve setting `datetime_conversion` is to to
filter out documents values outside of the range supported by
:class:`~datetime.datetime`:

>>> from datetime import datetime
>>> coll = client.test.dates
Expand Down