Skip to content

Commit 196b5f5

Browse files
authored
Remove pytz dependency (#518)
* Remove pytz Everything that pytz was being utilised for can be serviced perfectly fine by the standard library. Meanwhile, pytz is known as a footgun: https://blog.ganssle.io/articles/2018/03/pytz-fastest-footgun.html * Introduce compatibility timezone code for python2
1 parent 8c18750 commit 196b5f5

File tree

8 files changed

+72
-27
lines changed

8 files changed

+72
-27
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1616
- Support more lenient usernames and group names in FTP servers
1717
([#507](https://github.com/PyFilesystem/pyfilesystem2/pull/507)).
1818
Closes [#506](https://github.com/PyFilesystem/pyfilesystem2/issues/506).
19+
- Removed dependency on pytz ([#518](https://github.com/PyFilesystem/pyfilesystem2/pull/518)).
20+
Closes [#516](https://github.com/PyFilesystem/pyfilesystem2/issues/518).
1921

2022
### Fixed
2123

fs/_ftp_parse.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
from __future__ import unicode_literals
44

55
import unicodedata
6-
import datetime
76
import re
87
import time
8+
from datetime import datetime
99

10-
from pytz import UTC
10+
try:
11+
from datetime import timezone
12+
except ImportError:
13+
from ._tzcompat import timezone # type: ignore
1114

1215
from .enums import ResourceType
1316
from .permissions import Permissions
1417

1518

16-
EPOCH_DT = datetime.datetime.fromtimestamp(0, UTC)
19+
EPOCH_DT = datetime.fromtimestamp(0, timezone.utc)
1720

1821

1922
RE_LINUX = re.compile(
@@ -98,7 +101,7 @@ def _parse_time(t, formats):
98101
day = _t.tm_mday
99102
hour = _t.tm_hour
100103
minutes = _t.tm_min
101-
dt = datetime.datetime(year, month, day, hour, minutes, tzinfo=UTC)
104+
dt = datetime(year, month, day, hour, minutes, tzinfo=timezone.utc)
102105

103106
epoch_time = (dt - EPOCH_DT).total_seconds()
104107
return epoch_time

fs/_tzcompat.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Compatibility shim for python2's lack of datetime.timezone.
2+
3+
This is the example code from the Python 2 documentation:
4+
https://docs.python.org/2.7/library/datetime.html#tzinfo-objects
5+
"""
6+
7+
from datetime import tzinfo, timedelta
8+
9+
10+
ZERO = timedelta(0)
11+
12+
13+
class UTC(tzinfo):
14+
"""UTC"""
15+
16+
def utcoffset(self, dt):
17+
return ZERO
18+
19+
def tzname(self, dt):
20+
return "UTC"
21+
22+
def dst(self, dt):
23+
return ZERO
24+
25+
26+
utc = UTC()
27+
28+
29+
class timezone:
30+
utc = utc

fs/test.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
from fs.opener import open_fs
2727
from fs.subfs import ClosingSubFS, SubFS
2828

29-
import pytz
3029
import six
3130
from six import text_type
3231

@@ -35,6 +34,11 @@
3534
else:
3635
import collections.abc as collections_abc
3736

37+
try:
38+
from datetime import timezone
39+
except ImportError:
40+
from ._tzcompat import timezone # type: ignore
41+
3842

3943
UNICODE_TEXT = """
4044
@@ -1196,17 +1200,17 @@ def test_settimes(self):
11961200
can_write_acccess = info.is_writeable("details", "accessed")
11971201
can_write_modified = info.is_writeable("details", "modified")
11981202
if can_write_acccess:
1199-
self.assertEqual(info.accessed, datetime(2016, 7, 5, tzinfo=pytz.UTC))
1203+
self.assertEqual(info.accessed, datetime(2016, 7, 5, tzinfo=timezone.utc))
12001204
if can_write_modified:
1201-
self.assertEqual(info.modified, datetime(2016, 7, 5, tzinfo=pytz.UTC))
1205+
self.assertEqual(info.modified, datetime(2016, 7, 5, tzinfo=timezone.utc))
12021206

12031207
def test_touch(self):
12041208
self.fs.touch("new.txt")
12051209
self.assert_isfile("new.txt")
12061210
self.fs.settimes("new.txt", datetime(2016, 7, 5))
12071211
info = self.fs.getinfo("new.txt", namespaces=["details"])
12081212
if info.is_writeable("details", "accessed"):
1209-
self.assertEqual(info.accessed, datetime(2016, 7, 5, tzinfo=pytz.UTC))
1213+
self.assertEqual(info.accessed, datetime(2016, 7, 5, tzinfo=timezone.utc))
12101214
now = time.time()
12111215
self.fs.touch("new.txt")
12121216
accessed = self.fs.getinfo("new.txt", namespaces=["details"]).raw[

fs/time.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,16 @@
77
import typing
88
from calendar import timegm
99
from datetime import datetime
10-
from pytz import UTC, timezone
10+
11+
try:
12+
from datetime import timezone
13+
except ImportError:
14+
from ._tzcompat import timezone # type: ignore
1115

1216
if typing.TYPE_CHECKING:
1317
from typing import Optional
1418

1519

16-
utcfromtimestamp = datetime.utcfromtimestamp
17-
utclocalize = UTC.localize
18-
GMT = timezone("GMT")
19-
20-
2120
def datetime_to_epoch(d):
2221
# type: (datetime) -> int
2322
"""Convert datetime to epoch."""
@@ -39,4 +38,6 @@ def epoch_to_datetime(t): # noqa: D103
3938
def epoch_to_datetime(t):
4039
# type: (Optional[int]) -> Optional[datetime]
4140
"""Convert epoch time to a UTC datetime."""
42-
return utclocalize(utcfromtimestamp(t)) if t is not None else None
41+
if t is None:
42+
return None
43+
return datetime.fromtimestamp(t, tz=timezone.utc)

setup.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ setup_requires =
4343
setuptools >=38.3.0
4444
install_requires =
4545
appdirs~=1.4.3
46-
pytz
4746
setuptools
4847
six ~=1.10
4948
enum34 ~=1.1.6 ; python_version < '3.4'

tests/test_info.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
from __future__ import unicode_literals
22

3-
import datetime
3+
from datetime import datetime
44
import unittest
55

6-
import pytz
7-
86
from fs.enums import ResourceType
97
from fs.info import Info
108
from fs.permissions import Permissions
119
from fs.time import datetime_to_epoch
1210

11+
try:
12+
from datetime import timezone
13+
except ImportError:
14+
from fs._tzcompat import timezone # type: ignore
15+
1316

1417
class TestInfo(unittest.TestCase):
1518
def test_empty(self):
@@ -71,10 +74,10 @@ def test_basic(self):
7174

7275
def test_details(self):
7376
dates = [
74-
datetime.datetime(2016, 7, 5, tzinfo=pytz.UTC),
75-
datetime.datetime(2016, 7, 6, tzinfo=pytz.UTC),
76-
datetime.datetime(2016, 7, 7, tzinfo=pytz.UTC),
77-
datetime.datetime(2016, 7, 8, tzinfo=pytz.UTC),
77+
datetime(2016, 7, 5, tzinfo=timezone.utc),
78+
datetime(2016, 7, 6, tzinfo=timezone.utc),
79+
datetime(2016, 7, 7, tzinfo=timezone.utc),
80+
datetime(2016, 7, 8, tzinfo=timezone.utc),
7881
]
7982
epochs = [datetime_to_epoch(d) for d in dates]
8083

tests/test_time.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@
33
from datetime import datetime
44
import unittest
55

6-
import pytz
7-
86
from fs.time import datetime_to_epoch, epoch_to_datetime
97

8+
try:
9+
from datetime import timezone
10+
except ImportError:
11+
from fs._tzcompat import timezone # type: ignore
12+
1013

1114
class TestEpoch(unittest.TestCase):
1215
def test_epoch_to_datetime(self):
1316
self.assertEqual(
14-
epoch_to_datetime(142214400), datetime(1974, 7, 5, tzinfo=pytz.UTC)
17+
epoch_to_datetime(142214400), datetime(1974, 7, 5, tzinfo=timezone.utc)
1518
)
1619

1720
def test_datetime_to_epoch(self):
1821
self.assertEqual(
19-
datetime_to_epoch(datetime(1974, 7, 5, tzinfo=pytz.UTC)), 142214400
22+
datetime_to_epoch(datetime(1974, 7, 5, tzinfo=timezone.utc)), 142214400
2023
)

0 commit comments

Comments
 (0)