Repository: python-caldav/caldav (language: python)

Issue:
Remove dependency on pytz
The pytz and tzlocal libraries are deprecated, in modern python versions timezone handling is included in the embedded libraries - so we should remove all dependencies on pytz.  I think I sort of did this already, but then I got into problems.  The task is basically to look into the code and search for all instances of pytz and tzlocal and ensure that it's either removed or only used as a fallback should timezones not be available.  I vaguely remember that I already did this but had to comment out the work due to problems with the icalendar library.

Problems:

* The icalendar library had problems with the new timezone objects.  While waiting for https://github.com/collective/icalendar/issues/333 to be fixed and released it was needed to stick to pytz.
* We need the library to be backwards-compatible.  For python version 3.x this may be fixed by adding a dependency on backports.timezone - see https://pypi.org/project/backports.zoneinfo/.  With python2, I guess we still has to rely on pytz. Arguably, we should drop support for python2, but I have some comments on that at https://github.com/python-caldav/caldav/pull/228.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fc9bed72..8574bb78 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ This project should more or less adhere to [Semantic Versioning](https://semver.
 ## [x.x.x] - unreleased
 
 * Initial work at integrating typing information. Details in https://github.com/python-caldav/caldav/pull/358
+* Remove dependency on pytz. Details in https://github.com/python-caldav/caldav/issues/231
 
 ## [1.3.9] - 2023-12-12
 
diff --git a/caldav/elements/cdav.py b/caldav/elements/cdav.py
index d59e3772..1efdd6da 100644
--- a/caldav/elements/cdav.py
+++ b/caldav/elements/cdav.py
@@ -2,26 +2,17 @@
 # -*- encoding: utf-8 -*-
 import logging
 from datetime import datetime
+from datetime import timezone
 from typing import ClassVar
 from typing import Optional
 
+from caldav.lib.namespace import ns
+
 from .base import BaseElement
 from .base import NamedBaseElement
 from .base import ValuedBaseElement
 
-try:
-    from datetime import timezone
-
-    utc_tz = timezone.utc
-except:
-    ## pytz is deprecated - but as of 2021-11, the icalendar library is only
-    ## compatible with pytz (see https://github.com/collective/icalendar/issues/333 https://github.com/collective/icalendar/issues/335 https://github.com/collective/icalendar/issues/336)
-    import pytz
-
-    utc_tz = pytz.utc
-
-from caldav.lib.namespace import ns
-from .base import BaseElement, NamedBaseElement, ValuedBaseElement
+utc_tz = timezone.utc
 
 
 def _to_utc_date_string(ts):
diff --git a/examples/scheduling_examples.py b/examples/scheduling_examples.py
index 24b38e65..4de46e97 100644
--- a/examples/scheduling_examples.py
+++ b/examples/scheduling_examples.py
@@ -2,13 +2,14 @@
 import uuid
 from datetime import datetime
 from datetime import timedelta
+from datetime import timezone
 
-import pytz
 from caldav import DAVClient
 from caldav import error
 from icalendar import Calendar
 from icalendar import Event
 
+
 ###############
 ### SETUP START
 ### rfc6638_users should be a list with three dicts containing credential details.
@@ -218,8 +219,8 @@ def cleanup(self, calendar_name):
 
 pdb.set_trace()
 some_data_returned = organizer.principal.freebusy_request(
-    dtstart=datetime.now().astimezone(pytz.utc) + timedelta(days=399),
-    dtend=datetime.now().astimezone(pytz.utc) + timedelta(days=399, hours=1),
+    dtstart=datetime.now().astimezone(timezone.utc) + timedelta(days=399),
+    dtend=datetime.now().astimezone(timezone.utc) + timedelta(days=399, hours=1),
     attendees=[attendee1.principal, attendee2.principal],
 )
 
diff --git a/setup.py b/setup.py
index ad69e9e6..71b04944 100755
--- a/setup.py
+++ b/setup.py
@@ -24,24 +24,6 @@
     )
 
 if __name__ == "__main__":
-    ## For python 2.7 and 3.5 we depend on pytz and tzlocal.  For 3.6 and up, batteries are included.  Same with mock. (But unfortunately the icalendar library only support pytz timezones, so we'll keep pytz around for a bit longer).
-    try:
-        import datetime
-        from datetime import timezone
-
-        datetime.datetime.now().astimezone(timezone.utc)
-        extra_packages = []
-        ## line below can be removed when https://github.com/collective/icalendar/issues/333 is fixed
-        extra_packages = ["pytz", "tzlocal"]
-    except:
-        extra_packages = ["pytz", "tzlocal"]
-    try:
-        from unittest.mock import MagicMock
-
-        extra_test_packages = []
-    except:
-        extra_test_packages = ["mock"]
-
     ## TODO: consider if automated testing with radicale in addition to
     ## xandikos would yield any benefits.
     test_packages = [
@@ -49,14 +31,13 @@
         "pytest-coverage",
         "coverage",
         "sphinx",
+        "backports.zoneinfo;python_version<'3.9'",
+        "tzlocal",
+        "xandikos==0.2.8;python_version<'3.9'",
+        "dulwich==0.20.50;python_version<'3.9'",
+        "xandikos;python_version>='3.9'",
     ]
 
-    if sys.version_info.major == 3 and sys.version_info.minor < 9:
-        test_packages.append("xandikos==0.2.8")
-        test_packages.append("dulwich==0.20.50")
-    else:
-        test_packages.append("xandikos")
-
     setup(
         name="caldav",
         version=version,
@@ -90,8 +71,7 @@
             "icalendar",
             "recurring-ical-events>=2.0.0",
             "typing_extensions",
-        ]
-        + extra_packages,
+        ],
         extras_require={
             "test": test_packages,
         },


Test patch:
diff --git a/tests/test_cdav.py b/tests/test_cdav.py
index 417ef7f5..a6b3ee88 100644
--- a/tests/test_cdav.py
+++ b/tests/test_cdav.py
@@ -1,11 +1,15 @@
 import datetime
 
-import pytz
 import tzlocal
 from caldav.elements.cdav import _to_utc_date_string
 from caldav.elements.cdav import CalendarQuery
 
-SOMEWHERE_REMOTE = pytz.timezone("Brazil/DeNoronha")  # UTC-2 and no DST
+try:
+    import zoneinfo
+except ImportError:
+    from backports import zoneinfo
+
+SOMEWHERE_REMOTE = zoneinfo.ZoneInfo("Brazil/DeNoronha")  # UTC-2 and no DST
 
 
 def test_element():
@@ -32,9 +36,9 @@ def test_to_utc_date_string_utc():
     assert res == "20190514T211023Z"
 
 
-def test_to_utc_date_string_dt_with_pytz_tzinfo():
-    input = datetime.datetime(2019, 5, 14, 21, 10, 23, 23)
-    res = _to_utc_date_string(SOMEWHERE_REMOTE.localize(input))
+def test_to_utc_date_string_dt_with_zoneinfo_tzinfo():
+    input = datetime.datetime(2019, 5, 14, 21, 10, 23, 23, tzinfo=SOMEWHERE_REMOTE)
+    res = _to_utc_date_string(input)
     assert res == "20190514T231023Z"
 
 
diff --git a/tests/test_vcal.py b/tests/test_vcal.py
index a0ba5d56..b5bc2470 100644
--- a/tests/test_vcal.py
+++ b/tests/test_vcal.py
@@ -3,11 +3,11 @@
 import uuid
 from datetime import datetime
 from datetime import timedelta
+from datetime import timezone
 from unittest import TestCase
 
 import icalendar
 import pytest
-import pytz
 import vobject
 from caldav.lib import vcal
 from caldav.lib.python_utilities import to_normal_str
@@ -15,9 +15,7 @@
 from caldav.lib.vcal import create_ical
 from caldav.lib.vcal import fix
 
-# from datetime import timezone
-# utc = timezone.utc
-utc = pytz.utc
+utc = timezone.utc
 
 # example from http://www.rfc-editor.org/rfc/rfc5545.txt
 ev = """BEGIN:VCALENDAR