]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add TypeDecorator recipe for timezone aware/UTC conversion
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 13 Nov 2019 15:49:01 +0000 (10:49 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 13 Nov 2019 16:47:44 +0000 (11:47 -0500)
Change-Id: I59e6c76a4a53ce3782bcfc4aecdeb1b4fdd7b941
References: https://github.com/sqlalchemy/sqlalchemy/issues/4980
(cherry picked from commit e345864506346700dc4c21ff21bfc18f2c047831)

doc/build/core/custom_types.rst

index 7ac23ebcb21b4c3a395df080c499c714095a581d..1120f04bfe2f0e9e0c155c71a61950dfe96665e3 100644 (file)
@@ -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