From: Mike Bayer Date: Wed, 13 Nov 2019 15:49:01 +0000 (-0500) Subject: Add TypeDecorator recipe for timezone aware/UTC conversion X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=937949fb0cf6def1fe45c2050c17f3ba15928a96;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Add TypeDecorator recipe for timezone aware/UTC conversion Change-Id: I59e6c76a4a53ce3782bcfc4aecdeb1b4fdd7b941 References: https://github.com/sqlalchemy/sqlalchemy/issues/4980 (cherry picked from commit e345864506346700dc4c21ff21bfc18f2c047831) --- diff --git a/doc/build/core/custom_types.rst b/doc/build/core/custom_types.rst index 7ac23ebcb2..1120f04bfe 100644 --- a/doc/build/core/custom_types.rst +++ b/doc/build/core/custom_types.rst @@ -127,6 +127,42 @@ many decimal places. Here's a recipe that rounds them down:: value = value.quantize(self.quantize) return value +Store Timezone Aware Timestamps as Timezone Naive UTC +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Timestamps in databases should always be stored in a timezone-agnostic way. For +most databases, this means ensuring a timestamp is first in the UTC timezone +before it is stored, then storing it as timezone-naive (that is, without any +timezone associated with it; UTC is assumed to be the "implicit" timezone). +Alternatively, database-specific types like PostgreSQLs "TIMESTAMP WITH +TIMEZONE" are often preferred for their richer functionality; however, storing +as plain UTC will work on all databases and drivers. When a +timezone-intelligent database type is not an option or is not preferred, the +:class:`.TypeDecorator` can be used to create a datatype that convert timezone +aware timestamps into timezone naive and back again. Below, Python's +built-in ``datetime.timezone.utc`` timezone is used to normalize and +denormalize:: + + import datetime + + class TZDateTime(TypeDecorator): + impl = DateTime + + def process_bind_param(self, value, dialect): + if value is not None: + if not value.tzinfo: + raise TypeError("tzinfo is required") + value = value.astimezone(datetime.timezone.utc).replace( + tzinfo=None + ) + return value + + def process_result_value(self, value, dialect): + if value is not None: + value = value.replace(tzinfo=datetime.timezone.utc) + return value + + .. _custom_guid_type: Backend-agnostic GUID Type