Repository: marshmallow-code/marshmallow
Pull request #1324 (2019-07-24 01:59:53)

Remove utils.utc in favor of datetime.timezone.utc
Now that we're on py3-only, the vendorized UTC implementation from pytz is no longer necessary.

As an added bonus, the native `datetime.timezone.utc` is more performant than pytz's implementation: https://github.com/django/django/pull/9484


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 58eebd0a..5a0a27a4 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -20,6 +20,7 @@ Features:
 Deprecations/Removals:
 
 - ``LocalDateTime`` is removed (:issue:`1234`).
+- ``marshmallow.utils.utc`` is removed. Use ``datetime.timezone.utc`` instead.
 
 Bug fixes:
 
diff --git a/src/marshmallow/utils.py b/src/marshmallow/utils.py
index bc5e6ac3..aa7350f8 100644
--- a/src/marshmallow/utils.py
+++ b/src/marshmallow/utils.py
@@ -80,61 +80,6 @@ def pprint(obj, *args, **kwargs):
         py_pprint(obj, *args, **kwargs)
 
 
-# From pytz: http://pytz.sourceforge.net/
-ZERO = dt.timedelta(0)
-
-
-class UTC(dt.tzinfo):
-    """UTC
-
-    Optimized UTC implementation. It unpickles using the single module global
-    instance defined beneath this class declaration.
-    """
-
-    zone = "UTC"
-
-    _utcoffset = ZERO
-    _dst = ZERO
-    _tzname = zone
-
-    def fromutc(self, datetime):
-        if datetime.tzinfo is None:
-            return self.localize(datetime)
-        return super(utc.__class__, self).fromutc(datetime)
-
-    def utcoffset(self, datetime):
-        return ZERO
-
-    def tzname(self, datetime):
-        return "UTC"
-
-    def dst(self, datetime):
-        return ZERO
-
-    def localize(self, datetime, is_dst=False):
-        """Convert naive time to local time"""
-        if datetime.tzinfo is not None:
-            raise ValueError("Not naive datetime (tzinfo is already set)")
-        return datetime.replace(tzinfo=self)
-
-    def normalize(self, datetime, is_dst=False):
-        """Correct the timezone information on the given datetime"""
-        if datetime.tzinfo is self:
-            return datetime
-        if datetime.tzinfo is None:
-            raise ValueError("Naive time - no tzinfo set")
-        return datetime.astimezone(self)
-
-    def __repr__(self):
-        return "<UTC>"
-
-    def __str__(self):
-        return "UTC"
-
-
-UTC = utc = UTC()  # UTC is a singleton
-
-
 # https://stackoverflow.com/a/27596917
 def is_aware(datetime):
     return (
@@ -198,7 +143,7 @@ def from_iso_datetime(value):
     kw["microsecond"] = kw["microsecond"] and kw["microsecond"].ljust(6, "0")
     tzinfo = kw.pop("tzinfo")
     if tzinfo == "Z":
-        tzinfo = utc
+        tzinfo = dt.timezone.utc
     elif tzinfo is not None:
         offset_mins = int(tzinfo[-2:]) if len(tzinfo) > 3 else 0
         offset = 60 * int(tzinfo[1:3]) + offset_mins


Test patch:
diff --git a/tests/test_deserialization.py b/tests/test_deserialization.py
index 3caad6b4..3f03231f 100644
--- a/tests/test_deserialization.py
+++ b/tests/test_deserialization.py
@@ -5,7 +5,7 @@ import math
 
 import pytest
 
-from marshmallow import EXCLUDE, INCLUDE, RAISE, fields, utils, Schema, validate
+from marshmallow import EXCLUDE, INCLUDE, RAISE, fields, Schema, validate
 from marshmallow.exceptions import ValidationError
 from marshmallow.validate import Equal
 
@@ -438,7 +438,7 @@ class TestFieldDeserialization:
             ),
             (
                 "Sun, 10 Nov 2013 01:23:45 +0000",
-                utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45)),
+                dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
                 True,
             ),
             (
@@ -471,18 +471,18 @@ class TestFieldDeserialization:
             ("2013-11-10T01:23:45", dt.datetime(2013, 11, 10, 1, 23, 45), False),
             (
                 "2013-11-10T01:23:45+00:00",
-                utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45)),
+                dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
                 True,
             ),
             (
                 # Regression test for https://github.com/marshmallow-code/marshmallow/issues/1251
                 "2013-11-10T01:23:45.123+00:00",
-                utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45, 123000)),
+                dt.datetime(2013, 11, 10, 1, 23, 45, 123000, tzinfo=dt.timezone.utc),
                 True,
             ),
             (
                 "2013-11-10T01:23:45.123456+00:00",
-                utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45, 123456)),
+                dt.datetime(2013, 11, 10, 1, 23, 45, 123456, tzinfo=dt.timezone.utc),
                 True,
             ),
             (
@@ -514,7 +514,7 @@ class TestFieldDeserialization:
             ("iso", None, "2013-11-10T01:23:45", dt.datetime(2013, 11, 10, 1, 23, 45)),
             (
                 "iso",
-                utils.UTC,
+                dt.timezone.utc,
                 "2013-11-10T01:23:45+00:00",
                 dt.datetime(2013, 11, 10, 1, 23, 45),
             ),
@@ -532,7 +532,7 @@ class TestFieldDeserialization:
             ),
             (
                 "rfc",
-                utils.UTC,
+                dt.timezone.utc,
                 "Sun, 10 Nov 2013 01:23:45 +0000",
                 dt.datetime(2013, 11, 10, 1, 23, 45),
             ),
@@ -548,7 +548,7 @@ class TestFieldDeserialization:
         field = fields.NaiveDateTime(format=fmt, timezone=timezone)
         assert field.deserialize(value) == expected
 
-    @pytest.mark.parametrize("timezone", (utils.UTC, central))
+    @pytest.mark.parametrize("timezone", (dt.timezone.utc, central))
     @pytest.mark.parametrize(
         ("fmt", "value"),
         [("iso", "2013-11-10T01:23:45"), ("rfc", "Sun, 10 Nov 2013 01:23:45")],
diff --git a/tests/test_serialization.py b/tests/test_serialization.py
index b7d2e257..db7b141f 100644
--- a/tests/test_serialization.py
+++ b/tests/test_serialization.py
@@ -8,7 +8,7 @@ import math
 
 import pytest
 
-from marshmallow import Schema, fields, utils, missing as missing_
+from marshmallow import Schema, fields, missing as missing_
 from marshmallow.exceptions import ValidationError
 
 from tests.base import User, ALL_FIELDS, central
@@ -515,7 +515,7 @@ class TestFieldSerialization:
         [
             (dt.datetime(2013, 11, 10, 1, 23, 45), "Sun, 10 Nov 2013 01:23:45 -0000"),
             (
-                utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45)),
+                dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
                 "Sun, 10 Nov 2013 01:23:45 +0000",
             ),
             (
@@ -534,11 +534,11 @@ class TestFieldSerialization:
         [
             (dt.datetime(2013, 11, 10, 1, 23, 45), "2013-11-10T01:23:45"),
             (
-                utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45, 123456)),
+                dt.datetime(2013, 11, 10, 1, 23, 45, 123456, tzinfo=dt.timezone.utc),
                 "2013-11-10T01:23:45.123456+00:00",
             ),
             (
-                utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45)),
+                dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
                 "2013-11-10T01:23:45+00:00",
             ),
             (
diff --git a/tests/test_utils.py b/tests/test_utils.py
index e211b1f6..fff2257f 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -120,7 +120,7 @@ def test_is_collection():
     [
         (dt.datetime(2013, 11, 10, 1, 23, 45), "Sun, 10 Nov 2013 01:23:45 -0000"),
         (
-            utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45)),
+            dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
             "Sun, 10 Nov 2013 01:23:45 +0000",
         ),
         (
@@ -138,11 +138,11 @@ def test_rfc_format(value, expected):
     [
         (dt.datetime(2013, 11, 10, 1, 23, 45), "2013-11-10T01:23:45"),
         (
-            utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45, 123456)),
+            dt.datetime(2013, 11, 10, 1, 23, 45, 123456, tzinfo=dt.timezone.utc),
             "2013-11-10T01:23:45.123456+00:00",
         ),
         (
-            utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45)),
+            dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
             "2013-11-10T01:23:45+00:00",
         ),
         (
@@ -161,7 +161,7 @@ def test_isoformat(value, expected):
         ("Sun, 10 Nov 2013 01:23:45 -0000", dt.datetime(2013, 11, 10, 1, 23, 45)),
         (
             "Sun, 10 Nov 2013 01:23:45 +0000",
-            utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45)),
+            dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
         ),
         (
             "Sun, 10 Nov 2013 01:23:45 -0600",
@@ -181,16 +181,16 @@ def test_from_rfc(value, expected):
         ("2013-11-10T01:23:45", dt.datetime(2013, 11, 10, 1, 23, 45)),
         (
             "2013-11-10T01:23:45+00:00",
-            utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45)),
+            dt.datetime(2013, 11, 10, 1, 23, 45, tzinfo=dt.timezone.utc),
         ),
         (
             # Regression test for https://github.com/marshmallow-code/marshmallow/issues/1251
             "2013-11-10T01:23:45.123+00:00",
-            utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45, 123000)),
+            dt.datetime(2013, 11, 10, 1, 23, 45, 123000, tzinfo=dt.timezone.utc),
         ),
         (
             "2013-11-10T01:23:45.123456+00:00",
-            utils.UTC.localize(dt.datetime(2013, 11, 10, 1, 23, 45, 123456)),
+            dt.datetime(2013, 11, 10, 1, 23, 45, 123456, tzinfo=dt.timezone.utc),
         ),
         (
             "2013-11-10T01:23:45-06:00",