Skip to content

Commit 92a6fa7

Browse files
authored
PYTHON-3376/PYTHON-3378 Update FAQ about OverflowError when decoding out of range datetimes (#1025)
1 parent 13e2715 commit 92a6fa7

File tree

2 files changed

+73
-20
lines changed

2 files changed

+73
-20
lines changed

doc/examples/datetimes.rst

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -125,32 +125,52 @@ To decode UTC datetime values as :class:`~bson.datetime_ms.DatetimeMS`,
125125
:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME_AUTO`,
126126
:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME_CLAMP`.
127127
:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME` is the default
128-
option and has the behavior of raising an exception upon attempting to
129-
decode an out-of-range date.
128+
option and has the behavior of raising an :class:`~builtin.OverflowError` upon
129+
attempting to decode an out-of-range date.
130130
:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME_MS` will only return
131131
:class:`~bson.datetime_ms.DatetimeMS` objects, regardless of whether the
132-
represented datetime is in- or out-of-range.
132+
represented datetime is in- or out-of-range:
133+
134+
.. doctest::
135+
136+
>>> from datetime import datetime
137+
>>> from bson import encode, decode
138+
>>> from bson.datetime_ms import DatetimeMS
139+
>>> from bson.codec_options import CodecOptions, DatetimeConversionOpts
140+
>>> x = encode({"x": datetime(1970, 1, 1)})
141+
>>> codec_ms = CodecOptions(datetime_conversion=DatetimeConversionOpts.DATETIME_MS)
142+
>>> decode(x, codec_options=codec_ms)
143+
{'x': DatetimeMS(0)}
144+
133145
:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME_AUTO` will return
134146
:class:`~datetime.datetime` if the underlying UTC datetime is within range,
135147
or :class:`~bson.datetime_ms.DatetimeMS` if the underlying datetime
136-
cannot be represented using the builtin Python :class:`~datetime.datetime`.
148+
cannot be represented using the builtin Python :class:`~datetime.datetime`:
149+
150+
.. doctest::
151+
152+
>>> x = encode({"x": datetime(1970, 1, 1)})
153+
>>> y = encode({"x": DatetimeMS(-2**62)})
154+
>>> codec_auto = CodecOptions(datetime_conversion=DatetimeConversionOpts.DATETIME_AUTO)
155+
>>> decode(x, codec_options=codec_auto)
156+
{'x': datetime.datetime(1970, 1, 1, 0, 0)}
157+
>>> decode(y, codec_options=codec_auto)
158+
{'x': DatetimeMS(-4611686018427387904)}
159+
137160
:attr:`~bson.datetime_ms.DatetimeConversionOpts.DATETIME_CLAMP` will clamp
138161
resulting :class:`~datetime.datetime` objects to be within
139162
:attr:`~datetime.datetime.min` and :attr:`~datetime.datetime.max`
140-
(trimmed to `999000` microseconds).
141-
142-
An example of encoding and decoding using `DATETIME_MS` is as follows:
163+
(trimmed to `999000` microseconds):
143164

144165
.. doctest::
145-
>>> from datetime import datetime
146-
>>> from bson import encode, decode
147-
>>> from bson.datetime_ms import DatetimeMS
148-
>>> from bson.codec_options import CodecOptions,DatetimeConversionOpts
149-
>>> x = encode({"x": datetime(1970, 1, 1)})
150-
>>> x
151-
b'\x10\x00\x00\x00\tx\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
152-
>>> decode(x, codec_options=CodecOptions(datetime_conversion=DatetimeConversionOpts.DATETIME_MS))
153-
{'x': DatetimeMS(0)}
166+
167+
>>> x = encode({"x": DatetimeMS(2**62)})
168+
>>> y = encode({"x": DatetimeMS(-2**62)})
169+
>>> codec_clamp = CodecOptions(datetime_conversion=DatetimeConversionOpts.DATETIME_CLAMP)
170+
>>> decode(x, codec_options=codec_clamp)
171+
{'x': datetime.datetime(9999, 12, 31, 23, 59, 59, 999000)}
172+
>>> decode(y, codec_options=codec_clamp)
173+
{'x': datetime.datetime(1, 1, 1, 0, 0)}
154174

155175
:class:`~bson.datetime_ms.DatetimeMS` objects have support for rich comparison
156176
methods against other instances of :class:`~bson.datetime_ms.DatetimeMS`.

doc/faq.rst

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ collection, configured to use :class:`~bson.son.SON` instead of dict:
264264
>>> from bson import CodecOptions, SON
265265
>>> opts = CodecOptions(document_class=SON)
266266
>>> opts
267-
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))
267+
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)
268268
>>> collection_son = collection.with_options(codec_options=opts)
269269

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

492-
There are a few ways to work around this issue. One option is to filter
493-
out documents with values outside of the range supported by
494-
:class:`datetime.datetime`::
492+
There are a few ways to work around this issue. Starting with PyMongo 4.3,
493+
:func:`bson.decode` can decode BSON datetimes in one of four ways, and can
494+
be specified using the ``datetime_conversion`` parameter of
495+
:class:`~bson.codec_options.CodecOptions`.
496+
497+
The default option is
498+
:attr:`~bson.codec_options.DatetimeConversionOpts.DATETIME`, which will
499+
attempt to decode as a :class:`datetime.datetime`, allowing
500+
:class:`~builtin.OverflowError` to occur upon out-of-range dates.
501+
:attr:`~bson.codec_options.DatetimeConversionOpts.DATETIME_AUTO` alters
502+
this behavior to instead return :class:`~bson.datetime_ms.DatetimeMS` when
503+
representations are out-of-range, while returning :class:`~datetime.datetime`
504+
objects as before:
505+
506+
.. doctest::
507+
508+
>>> from datetime import datetime
509+
>>> from bson.datetime_ms import DatetimeMS
510+
>>> from bson.codec_options import DatetimeConversionOpts
511+
>>> from pymongo import MongoClient
512+
>>> client = MongoClient(datetime_conversion=DatetimeConversionOpts.DATETIME_AUTO)
513+
>>> client.db.collection.insert_one({"x": datetime(1970, 1, 1)})
514+
<pymongo.results.InsertOneResult object at 0x...>
515+
>>> client.db.collection.insert_one({"x": DatetimeMS(2**62)})
516+
<pymongo.results.InsertOneResult object at 0x...>
517+
>>> for x in client.db.collection.find():
518+
... print(x)
519+
{'_id': ObjectId('...'), 'x': datetime.datetime(1970, 1, 1, 0, 0)}
520+
{'_id': ObjectId('...'), 'x': DatetimeMS(4611686018427387904)}
521+
522+
For other options, please refer to
523+
:class:`~bson.codec_options.DatetimeConversionOpts`.
524+
525+
Another option that does not involve setting `datetime_conversion` is to to
526+
filter out documents values outside of the range supported by
527+
:class:`~datetime.datetime`:
495528

496529
>>> from datetime import datetime
497530
>>> coll = client.test.dates

0 commit comments

Comments
 (0)