|
18 | 18 |
|
19 | 19 | .. data:: DATETIME_MODE_NONE |
20 | 20 |
|
21 | | - This is the default ``datetime_mode``: *neither* :class:`datetime` *nor* |
22 | | - :class:`date` instances are recognized by :func:`dumps` and :func:`loads`. |
| 21 | + This is the default ``datetime_mode``: *neither* :class:`date` *nor* |
| 22 | + :class:`datetime` *nor* :class:`time` instances are recognized by |
| 23 | + :func:`dumps` and :func:`loads`. |
23 | 24 |
|
24 | 25 | .. data:: DATETIME_MODE_ISO8601 |
25 | 26 |
|
26 | 27 | In this ``datetime_mode`` mode :func:`dumps` and :func:`loads` handle |
27 | | - :class:`datetime` *and* :class:`date` instances representing those values |
28 | | - using the `ISO 8601`_ format. |
| 28 | + :class:`date`, :class:`datetime` *and* :class:`date` instances representing |
| 29 | + those values using the `ISO 8601`_ format. |
29 | 30 |
|
30 | | -.. data:: DATETIME_MODE_ISO8601_IGNORE_TZ |
| 31 | +.. data:: DATETIME_MODE_UNIX_TIME |
31 | 32 |
|
32 | | - This is like :data:`DATETIME_MODE_ISO8601` except that the value's timezone |
33 | | - is ignored. |
| 33 | + This mode tells RapidJSON to serialize :class:`date`, :class:`datetime` and |
| 34 | + :class:`time` as *numeric timestamps*: for the formers it is exactly the |
| 35 | + result of their :meth:`timestamp` method, that is as the number of seconds |
| 36 | + passed since ``EPOCH``; ``date`` instances are serialized as the |
| 37 | + corresponding ``datetime`` instance with all the `time` slots set to 0; |
| 38 | + ``time`` instances are serialized as the number of seconds since midnight. |
34 | 39 |
|
35 | | -.. data:: DATETIME_MODE_ISO8601_UTC |
| 40 | + Since this is obviously *irreversible*, this flag is usable **only** for |
| 41 | + :func:`dumps`: an error is raised when passed to :func:`loads`. |
36 | 42 |
|
37 | | - This is like :data:`DATETIME_MODE_ISO8601` except that the times are always |
38 | | - *shifted* to the UTC_ timezone. |
| 43 | +.. data:: DATETIME_MODE_ONLY_SECONDS |
| 44 | + |
| 45 | + This is usable in combination with :data:`DATETIME_MODE_UNIX_TIME` so that |
| 46 | + an integer representation is used, ignoring *microseconds*. |
| 47 | + |
| 48 | +.. data:: DATETIME_MODE_IGNORE_TZ |
| 49 | + |
| 50 | + This can be used combined with :data:`DATETIME_MODE_ISO8601` or |
| 51 | + :data:`DATETIME_MODE_UNIX_TIME`, to ignore the value's timezones. |
| 52 | + |
| 53 | +.. data:: DATETIME_MODE_NAIVE_IS_UTC |
| 54 | + |
| 55 | + This can be used combined with :data:`DATETIME_MODE_ISO8601` or |
| 56 | + :data:`DATETIME_MODE_UNIX_TIME`, to tell RapidJSON that the naïve values |
| 57 | + (that is, without an explicit timezone) are already in UTC_ timezone. |
| 58 | + |
| 59 | +.. data:: DATETIME_MODE_SHIFT_TO_UTC |
| 60 | + |
| 61 | + This can be used combined with :data:`DATETIME_MODE_ISO8601` or |
| 62 | + :data:`DATETIME_MODE_UNIX_TIME`, to always *shift* values the UTC_ |
| 63 | + timezone. |
39 | 64 |
|
40 | 65 | .. data:: UUID_MODE_NONE |
41 | 66 |
|
|
58 | 83 |
|
59 | 84 | .. testsetup:: |
60 | 85 |
|
61 | | - from rapidjson import (dumps, loads, DATETIME_MODE_NONE, DATETIME_MODE_ISO8601, |
62 | | - DATETIME_MODE_ISO8601_IGNORE_TZ, DATETIME_MODE_ISO8601_UTC, |
63 | | - UUID_MODE_NONE, UUID_MODE_CANONICAL, UUID_MODE_HEX) |
| 86 | + from rapidjson import (dumps, loads, |
| 87 | + DATETIME_MODE_NONE, DATETIME_MODE_ISO8601, |
| 88 | + DATETIME_MODE_UNIX_TIME, DATETIME_MODE_ONLY_SECONDS, |
| 89 | + DATETIME_MODE_IGNORE_TZ, DATETIME_MODE_NAIVE_IS_UTC, |
| 90 | + DATETIME_MODE_SHIFT_TO_UTC, |
| 91 | + UUID_MODE_NONE, UUID_MODE_CANONICAL, UUID_MODE_HEX) |
64 | 92 |
|
65 | 93 | .. function:: dumps(obj, skipkeys=False, ensure_ascii=True, allow_nan=True, indent=None, \ |
66 | 94 | default=None, sort_keys=False, use_decimal=False, \ |
|
77 | 105 | alphabetically |
78 | 106 | :param bool use_decimal: whether :class:`Decimal` should be handled |
79 | 107 | :param int max_recursion_depth: maximum depth for nested structures |
80 | | - :param int datetime_mode: how should :class:`datetime` and :class:`date` |
81 | | - instances be handled |
| 108 | + :param int datetime_mode: how should :class:`datetime`, :class:`time` and |
| 109 | + :class:`date` instances be handled |
82 | 110 | :param int uuid_mode: how should :class:`UUID` instances be handled |
83 | 111 | :returns: A Python :class:`str` instance. |
84 | 112 |
|
|
219 | 247 | File "<stdin>", line 1, in <module> |
220 | 248 | OverflowError: Max recursion depth reached |
221 | 249 |
|
222 | | - By default :class:`date` and :class:`datetime` instances are not |
223 | | - serializable. When `datetime_mode` is set to :data:`DATETIME_MODE_ISO8601` |
| 250 | + By default :class:`date`, :class:`datetime` and :class:`time` instances are |
| 251 | + not serializable. When `datetime_mode` is set to :data:`DATETIME_MODE_ISO8601` |
224 | 252 | those values are serialized using the common `ISO 8601`_ format: |
225 | 253 |
|
226 | 254 | .. doctest:: |
227 | 255 |
|
228 | | - >>> from datetime import date, datetime |
229 | | - >>> today = date.today() |
230 | | - >>> right_now = datetime.now() |
231 | | - >>> dumps({'date': today, 'timestamp': right_now}) |
| 256 | + >>> from datetime import datetime |
| 257 | + >>> right_now = datetime(2016, 8, 28, 13, 14, 52, 277256) |
| 258 | + >>> date = right_now.date() |
| 259 | + >>> time = right_now.time() |
| 260 | + >>> dumps({'date': date, 'time': time, 'timestamp': right_now}) |
232 | 261 | Traceback (most recent call last): |
233 | 262 | File "<stdin>", line 1, in <module> |
234 | 263 | TypeError: datetime(…) is not JSON serializable |
235 | | - >>> dumps({'a date': today, 'a timestamp': right_now}, |
236 | | - ... datetime_mode=DATETIME_MODE_ISO8601) # doctest: +SKIP |
237 | | - '{"timestamp":"2016-08-28T13:14:52.277256","date":"2016-08-28"}' |
| 264 | + >>> dumps(['date', date, 'time', time, 'timestamp', right_now], |
| 265 | + ... datetime_mode=DATETIME_MODE_ISO8601) |
| 266 | + '["date","2016-08-28","time","13:14:52.277256","timestamp","2016-08-28T13:14:52.277256"]' |
238 | 267 |
|
239 | | - Another mode is :data:`DATETIME_MODE_ISO8601_UTC`, that *shifts* all |
240 | | - timestamps to the UTC_ timezone before serializing them: |
| 268 | + The `right_now` value is a naïve datetime (because it does not carry the |
| 269 | + timezone information) and is normally assumed to be in the local timezone, |
| 270 | + whatever your system thinks it is. When you instead *know* that your value, |
| 271 | + even being naïve are actually in the UTC_ timezone, you can use the |
| 272 | + :data:`DATETIME_MODE_NAIVE_IS_UTC` flag to inform RapidJSON about that: |
| 273 | + |
| 274 | + .. doctest:: |
| 275 | + |
| 276 | + >>> mode = DATETIME_MODE_ISO8601 | DATETIME_MODE_NAIVE_IS_UTC |
| 277 | + >>> dumps(['time', time, 'timestamp', right_now], datetime_mode=mode) |
| 278 | + '["time","13:14:52.277256+00:00","timestamp","2016-08-28T13:14:52.277256+00:00"]' |
| 279 | + |
| 280 | + A variant is :data:`DATETIME_MODE_SHIFT_TO_UTC`, that *shifts* all datetime |
| 281 | + values to the UTC_ timezone before serializing them: |
241 | 282 |
|
242 | 283 | .. doctest:: |
243 | 284 |
|
244 | 285 | >>> from datetime import timedelta, timezone |
245 | 286 | >>> here = timezone(timedelta(hours=2)) |
246 | | - >>> now = datetime.now(here) |
247 | | - >>> dumps(now) |
248 | | - Traceback (most recent call last): |
249 | | - File "<stdin>", line 1, in <module> |
250 | | - TypeError: datetime.datetime(…) is not JSON serializable |
251 | | - >>> dumps(now, datetime_mode=DATETIME_MODE_ISO8601) # doctest: +SKIP |
| 287 | + >>> now = datetime(2016, 8, 28, 20, 31, 11, 84418, here) |
| 288 | + >>> dumps(now, datetime_mode=DATETIME_MODE_ISO8601) |
252 | 289 | '"2016-08-28T20:31:11.084418+02:00"' |
253 | | - >>> dumps(now, datetime_mode=DATETIME_MODE_ISO8601_UTC) # doctest: +SKIP |
| 290 | + >>> mode = DATETIME_MODE_ISO8601 | DATETIME_MODE_SHIFT_TO_UTC |
| 291 | + >>> dumps(now, datetime_mode=mode) |
254 | 292 | '"2016-08-28T18:31:11.084418+00:00"' |
255 | 293 |
|
256 | | - With :data:`DATETIME_MODE_ISO8601_IGNORE_TZ` the timezone, if present, is |
257 | | - simply omitted: |
| 294 | + With :data:`DATETIME_MODE_IGNORE_TZ` the timezone, if present, is simply |
| 295 | + omitted: |
258 | 296 |
|
259 | 297 | .. doctest:: |
260 | 298 |
|
261 | | - >>> dumps(now, datetime_mode=DATETIME_MODE_ISO8601_IGNORE_TZ) # doctest: +SKIP |
| 299 | + >>> mode = DATETIME_MODE_ISO8601 | DATETIME_MODE_IGNORE_TZ |
| 300 | + >>> dumps(now, datetime_mode=mode) |
262 | 301 | '"2016-08-28T20:31:11.084418"' |
263 | 302 |
|
| 303 | + Another :ref:`one-way only <no-unix-time-loads>` alternative format is |
| 304 | + `Unix time`_: with :data:`DATETIME_MODE_UNIX_TIME` :class:`date`, |
| 305 | + :class:`datetime` and :class:`time` instances are serialized as a number of |
| 306 | + seconds, respectively since the ``EPOCH`` for the first two kinds and since |
| 307 | + midnight for the latter: |
| 308 | + |
| 309 | + .. doctest:: |
| 310 | + |
| 311 | + >>> mode = DATETIME_MODE_UNIX_TIME |
| 312 | + >>> dumps([now, now.date(), now.time()], datetime_mode=mode) |
| 313 | + '[1472409071.084418,1472335200.0,73871.084418]' |
| 314 | + >>> unixtime = float(dumps(now, datetime_mode=mode)) |
| 315 | + >>> datetime.fromtimestamp(unixtime, here) == now |
| 316 | + True |
| 317 | + |
| 318 | + Combining it with the :data:`DATETIME_MODE_ONLY_SECONDS` will produce |
| 319 | + integer values instead, dropping *microseconds*: |
| 320 | + |
| 321 | + .. doctest:: |
| 322 | + |
| 323 | + >>> mode = DATETIME_MODE_UNIX_TIME | DATETIME_MODE_ONLY_SECONDS |
| 324 | + >>> dumps([now, now.date(), now.time()], datetime_mode=mode) |
| 325 | + '[1472409071,1472335200,73871]' |
| 326 | + |
| 327 | + It can be used combined with :data:`DATETIME_MODE_SHIFT_TO_UTC` to obtain the |
| 328 | + timestamp of the corresponding UTC_ time: |
| 329 | + |
| 330 | + >>> mode = DATETIME_MODE_UNIX_TIME | DATETIME_MODE_SHIFT_TO_UTC |
| 331 | + >>> dumps(now, datetime_mode=mode) |
| 332 | + '1472409071.084418' |
| 333 | + |
| 334 | + As above, when you know that your values are in the UTC_ timezone, you can use the |
| 335 | + :data:`DATETIME_MODE_NAIVE_IS_UTC` flag to get the right result: |
| 336 | + |
| 337 | + .. doctest:: |
| 338 | + |
| 339 | + >>> a_long_time_ago = datetime(1968, 3, 18, 9, 10, 0, 0) |
| 340 | + >>> mode = DATETIME_MODE_UNIX_TIME | DATETIME_MODE_NAIVE_IS_UTC |
| 341 | + >>> dumps([a_long_time_ago, a_long_time_ago.date(), a_long_time_ago.time()], |
| 342 | + ... datetime_mode=mode) |
| 343 | + '[-56472600.0,-56505600.0,33000.0]' |
| 344 | + |
264 | 345 | Likewise, to handle :class:`UUID` instances there are two modes that can be |
265 | 346 | specified with the `uuid_mode` argument, that will use the string |
266 | 347 | representation of their values: |
|
289 | 370 | :param bool use_decimal: whether :class:`Decimal` should be used for float |
290 | 371 | values |
291 | 372 | :param bool allow_nan: whether ``NaN`` values are recognized |
292 | | - :param int datetime_mode: how should :class:`datetime` and :class:`date` |
293 | | - instances be handled |
| 373 | + :param int datetime_mode: how should :class:`date`, :class:`datetime` and |
| 374 | + :class:`time` instances be handled |
294 | 375 | :param int uuid_mode: how should :class:`UUID` instances be handled |
295 | 376 | :returns: An equivalent Python object. |
296 | 377 |
|
|
341 | 422 | ValueError: … Out of range float values are not JSON compliant |
342 | 423 |
|
343 | 424 | With `datetime_mode` you can enable recognition of string literals |
344 | | - containing an `ISO 8601`_ representation as either :class:`date` or |
345 | | - :class:`datetime` instances: |
| 425 | + containing an `ISO 8601`_ representation as either :class:`date`, |
| 426 | + :class:`datetime` or :class:`time` instances: |
346 | 427 |
|
347 | 428 | .. doctest:: |
348 | 429 |
|
|
351 | 432 | >>> loads('"2016-01-02T01:02:03+01:00"', |
352 | 433 | ... datetime_mode=DATETIME_MODE_ISO8601) |
353 | 434 | datetime.datetime(2016, 1, 2, 1, 2, 3, tzinfo=...delta(0, 3600))) |
354 | | - >>> loads('"2016-01-02T01:02:03+01:00"', |
355 | | - ... datetime_mode=DATETIME_MODE_ISO8601_UTC) |
356 | | - datetime.datetime(2016, 1, 2, 0, 2, 3, tzinfo=...utc) |
357 | | - >>> loads('"2016-01-02T01:02:03+01:00"', |
358 | | - ... datetime_mode=DATETIME_MODE_ISO8601_IGNORE_TZ) |
359 | | - datetime.datetime(2016, 1, 2, 1, 2, 3) |
360 | 435 | >>> loads('"2016-01-02"', datetime_mode=DATETIME_MODE_ISO8601) |
361 | 436 | datetime.date(2016, 1, 2) |
| 437 | + >>> loads('"01:02:03+01:00"', |
| 438 | + ... datetime_mode=DATETIME_MODE_ISO8601) |
| 439 | + datetime.time(1, 2, 3, tzinfo=...delta(0, 3600))) |
| 440 | + |
| 441 | + It can be combined with :data:`DATETIME_MODE_SHIFT_TO_UTC` to *always* |
| 442 | + obtain values in the UTC_ timezone: |
| 443 | + |
| 444 | + .. doctest:: |
| 445 | + |
| 446 | + >>> mode = DATETIME_MODE_ISO8601 | DATETIME_MODE_SHIFT_TO_UTC |
| 447 | + >>> loads('"2016-01-02T01:02:03+01:00"', datetime_mode=mode) |
| 448 | + datetime.datetime(2016, 1, 2, 0, 2, 3, tzinfo=...utc) |
| 449 | + |
| 450 | + .. note:: |
| 451 | + |
| 452 | + This option is somewhat limited when the value is a non-naïve time literal |
| 453 | + because negative values cannot be represented by the underlying Python |
| 454 | + type, so it cannot adapt such values reliably: |
| 455 | + |
| 456 | + .. doctest:: |
| 457 | + |
| 458 | + >>> mode = DATETIME_MODE_ISO8601 | DATETIME_MODE_SHIFT_TO_UTC |
| 459 | + >>> loads('"00:01:02+00:00"', datetime_mode=mode) |
| 460 | + datetime.time(0, 1, 2, tzinfo=...utc) |
| 461 | + >>> loads('"00:01:02+01:00"', datetime_mode=mode) |
| 462 | + Traceback (most recent call last): |
| 463 | + ... |
| 464 | + ValueError: ... Time literal cannot be shifted to UTC: 00:01:02+01:00 |
| 465 | + |
| 466 | + If you combine it with :data:`DATETIME_MODE_NAIVE_IS_UTC` then all values |
| 467 | + without a timezone will be assumed to be relative to UTC_: |
| 468 | + |
| 469 | + .. doctest:: |
| 470 | + |
| 471 | + >>> mode = DATETIME_MODE_ISO8601 | DATETIME_MODE_NAIVE_IS_UTC |
| 472 | + >>> loads('"2016-01-02T01:02:03"', datetime_mode=mode) |
| 473 | + datetime.datetime(2016, 1, 2, 1, 2, 3, tzinfo=...utc) |
| 474 | + >>> loads('"2016-01-02T01:02:03+01:00"', datetime_mode=mode) |
| 475 | + datetime.datetime(2016, 1, 2, 1, 2, 3, tzinfo=...delta(0, 3600))) |
| 476 | + >>> loads('"01:02:03"', datetime_mode=mode) |
| 477 | + datetime.time(1, 2, 3, tzinfo=...utc) |
| 478 | + |
| 479 | + Yet another combination is with :data:`DATETIME_MODE_IGNORE_TZ` to ignore |
| 480 | + the timezone and obtain naïve values: |
| 481 | + |
| 482 | + .. doctest:: |
| 483 | + |
| 484 | + >>> mode = DATETIME_MODE_ISO8601 | DATETIME_MODE_IGNORE_TZ |
| 485 | + >>> loads('"2016-01-02T01:02:03+01:00"', datetime_mode=mode) |
| 486 | + datetime.datetime(2016, 1, 2, 1, 2, 3) |
| 487 | + >>> loads('"01:02:03+01:00"', datetime_mode=mode) |
| 488 | + datetime.time(1, 2, 3) |
| 489 | + |
| 490 | + .. _no-unix-time-loads: |
| 491 | + |
| 492 | + The :data:`DATETIME_MODE_UNIX_TIME` cannot be used here, because there |
| 493 | + isn't a reasonable heuristic to disambiguate between plain numbers and |
| 494 | + timestamps: |
| 495 | + |
| 496 | + .. doctest:: |
| 497 | + |
| 498 | + >>> loads('[1,2,3]', datetime_mode=DATETIME_MODE_UNIX_TIME) |
| 499 | + Traceback (most recent call last): |
| 500 | + File "<stdin>", line 1, in <module> |
| 501 | + ValueError: Invalid datetime_mode, can deserialize only from ISO8601 |
362 | 502 |
|
363 | 503 | With `uuid_mode` you can enable recognition of string literals containing |
364 | 504 | two different representations of :class:`UUID` values: |
|
384 | 524 | .. _ISO 8601: https://en.wikipedia.org/wiki/ISO_8601 |
385 | 525 | .. _RapidJSON: https://github.com/miloyip/rapidjson |
386 | 526 | .. _UTC: https://en.wikipedia.org/wiki/Coordinated_Universal_Time |
| 527 | +.. _Unix time: https://en.wikipedia.org/wiki/Unix_time |
0 commit comments