diff --git a/docs/dates_and_times.rst b/docs/dates_and_times.rst new file mode 100644 index 00000000..5b13cb7f --- /dev/null +++ b/docs/dates_and_times.rst @@ -0,0 +1,87 @@ +Working with Dates and Times +============================ + +This document is meant to provide on overview of the assumptions and limitations of the driver time handling, the +reasoning behind it, and describe approaches to working with these types. + +timestamps (Cassandra DateType) +------------------------------- + +Timestamps in Cassandra are timezone-naive timestamps encoded as microseconds since UNIX epoch. Clients working with +timestamps in this database usually find it easiest to reason about them if they are always assumed to be UTC. To quote the +pytz documentation, "The preferred way of dealing with times is to always work in UTC, converting to localtime only when +generating output to be read by humans." The driver adheres to this tenant, and assumes UTC is always in the database. The +driver attempts to make this correct on the way in, and assumes no timezone on the way out. + +Write Path +~~~~~~~~~~ +When inserting timestamps, the driver handles serialization for the write path as follows: + +If the input is a ``datetime.datetime``, the serialization is normalized by starting with the ``utctimetuple()`` of the +value. + +- If the ``datetime`` object is timezone-aware, the timestamp is shifted, and represents the UTC timestamp equivalent. +- If the ``datetime`` object is timezone-naive, this results in no shift -- any ``datetime`` with no timezone information is assumed to be UTC + +Note the second point above applies even to "local" times created using ``now()``:: + + >>> d = datetime.now() + + >>> print(d.tzinfo) + None + + +These do not contain timezone information intrinsically, so they will be assumed to be UTC and not shifted. When generating +timestamps in the application, it is clearer to use ``datetime.utcnow()`` to be explicit about it. + +If the input for a timestamp is numeric, it is assumed to be a epoch-relative microsecond timestamp, as specified in the +CQL spec -- no scaling or conversion is done. + +Read Path +~~~~~~~~~ +The driver always assumes persisted timestamps are UTC and makes no attempt to localize them. Returned values are +timezone-naive ``datetime.datetime``. We follow this approach because the datetime API has deficiencies around daylight +saving time, and the defacto package for handling this is a third-party package (we try to minimize external dependencies +and not make decisions for the integrator). + +The decision for how to handle timezones is left to the application. For the most part it is straightforward to apply +localization to the ``datetime``\s returned by queries. One prevalent method is to use pytz for localization:: + + import pytz + user_tz = pytz.timezone('US/Central') + timestamp_naive = row.ts + timestamp_utc = pytz.utc.localize(timestamp_naive) + timestamp_presented = timestamp_utc.astimezone(user_tz) + +This is the most robust approach (likely refactored into a function). If it is deemed too cumbersome to apply for all call +sites in the application, it is possible to patch the driver with custom deserialization for this type. However, doing +this depends depends some on internal APIs and what extensions are present, so we will only mention the possibility, and +not spell it out here. + +date, time (Cassandra DateType) +------------------------------- +Date and time in Cassandra are idealized markers, much like ``datetime.date`` and ``datetime.time`` in the Python standard +library. Unlike these Python implementations, the Cassandra encoding supports much wider ranges. To accommodate these +ranges without overflow, this driver returns these data in custom types: :class:`.util.Date` and :class:`.util.Time`. + +Write Path +~~~~~~~~~~ +For simple (not prepared) statements, the input values for each of these can be either a string literal or an encoded +integer. See `Working with dates `_ +or `Working with time `_ for details +on the encoding or string formats. + +For prepared statements, the driver accepts anything that can be used to construct the :class:`.util.Date` or +:class:`.util.Time` classes. See the linked API docs for details. + +Read Path +~~~~~~~~~ +The driver always returns custom types for ``date`` and ``time``. + +The driver returns :class:`.util.Date` for ``date`` in order to accommodate the wider range of values without overflow. +For applications working within the supported range of [``datetime.MINYEAR``, ``datetime.MAXYEAR``], these are easily +converted to standard ``datetime.date`` insances using :meth:`.Date.date`. + +The driver returns :class:`.util.Time` for ``time`` in order to retain nanosecond precision stored in the database. +For applications not concerned with this level of precision, these are easily converted to standard ``datetime.time`` +insances using :meth:`.Time.time`. diff --git a/docs/index.rst b/docs/index.rst index 903a7506..c68e8653 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -42,6 +42,9 @@ Contents :doc:`security` An overview of the security features of the driver. +:doc:`dates_and_times` + Some discussion on the driver's approach to working with timestamp, date, time types + :doc:`faq` A collection of Frequently Asked Questions @@ -58,6 +61,7 @@ Contents security user_defined_types object_mapper + dates_and_times faq Getting Help