Skip to content

Commit 25ffdb9

Browse files
committed
fix: Drop dependency on pytz
Since pytz is only being used for UTC and fixed offsets — both of which are available in the standard library — there is no longer any reason for this dependency.
1 parent db5e8ea commit 25ffdb9

File tree

3 files changed

+31
-33
lines changed

3 files changed

+31
-33
lines changed

google/api_core/datetime_helpers.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@
1818
import datetime
1919
import re
2020

21-
import pytz
22-
2321
from google.protobuf import timestamp_pb2
2422

2523

26-
_UTC_EPOCH = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc)
24+
_UTC_EPOCH = datetime.datetime.fromtimestamp(0, datetime.timezone.utc)
2725
_RFC3339_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ"
2826
_RFC3339_NO_FRACTION = "%Y-%m-%dT%H:%M:%S"
2927
# datetime.strptime cannot handle nanosecond precision: parse w/ regex
@@ -83,9 +81,9 @@ def to_microseconds(value):
8381
int: Microseconds since the unix epoch.
8482
"""
8583
if not value.tzinfo:
86-
value = value.replace(tzinfo=pytz.utc)
84+
value = value.replace(tzinfo=datetime.timezone.utc)
8785
# Regardless of what timezone is on the value, convert it to UTC.
88-
value = value.astimezone(pytz.utc)
86+
value = value.astimezone(datetime.timezone.utc)
8987
# Convert the datetime to a microsecond timestamp.
9088
return int(calendar.timegm(value.timetuple()) * 1e6) + value.microsecond
9189

@@ -156,7 +154,7 @@ def from_rfc3339(value):
156154
nanos = int(fraction) * (10 ** scale)
157155
micros = nanos // 1000
158156

159-
return bare_seconds.replace(microsecond=micros, tzinfo=pytz.utc)
157+
return bare_seconds.replace(microsecond=micros, tzinfo=datetime.timezone.utc)
160158

161159

162160
from_rfc3339_nanos = from_rfc3339 # from_rfc3339_nanos method was deprecated.
@@ -256,7 +254,7 @@ def from_rfc3339(cls, stamp):
256254
bare.minute,
257255
bare.second,
258256
nanosecond=nanos,
259-
tzinfo=pytz.UTC,
257+
tzinfo=datetime.timezone.utc,
260258
)
261259

262260
def timestamp_pb(self):
@@ -265,7 +263,7 @@ def timestamp_pb(self):
265263
Returns:
266264
(:class:`~google.protobuf.timestamp_pb2.Timestamp`): Timestamp message
267265
"""
268-
inst = self if self.tzinfo is not None else self.replace(tzinfo=pytz.UTC)
266+
inst = self if self.tzinfo is not None else self.replace(tzinfo=datetime.timezone.utc)
269267
delta = inst - _UTC_EPOCH
270268
seconds = int(delta.total_seconds())
271269
nanos = self._nanosecond or self.microsecond * 1000
@@ -292,5 +290,5 @@ def from_timestamp_pb(cls, stamp):
292290
bare.minute,
293291
bare.second,
294292
nanosecond=stamp.nanos,
295-
tzinfo=pytz.UTC,
293+
tzinfo=datetime.timezone.utc,
296294
)

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
"requests >= 2.18.0, < 3.0.0dev",
3636
"setuptools >= 34.0.0",
3737
"six >= 1.10.0",
38-
"pytz",
3938
'futures >= 3.2.0; python_version < "3.2"',
4039
]
4140
extras = {

tests/unit/test_datetime_helpers.py

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414

1515
import calendar
1616
import datetime
17+
from datetime import timezone, timedelta
1718

1819
import pytest
19-
import pytz
2020

2121
from google.api_core import datetime_helpers
2222
from google.protobuf import timestamp_pb2
2323

2424

25+
UTC = timezone.utc
2526
ONE_MINUTE_IN_MICROSECONDS = 60 * 1e6
2627

2728

@@ -31,7 +32,7 @@ def test_utcnow():
3132

3233

3334
def test_to_milliseconds():
34-
dt = datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
35+
dt = datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=UTC)
3536
assert datetime_helpers.to_milliseconds(dt) == 1000
3637

3738

@@ -42,7 +43,7 @@ def test_to_microseconds():
4243

4344

4445
def test_to_microseconds_non_utc():
45-
zone = pytz.FixedOffset(-1)
46+
zone = timezone(timedelta(minutes=-1))
4647
dt = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=zone)
4748
assert datetime_helpers.to_microseconds(dt) == ONE_MINUTE_IN_MICROSECONDS
4849

@@ -56,7 +57,7 @@ def test_to_microseconds_naive():
5657
def test_from_microseconds():
5758
five_mins_from_epoch_in_microseconds = 5 * ONE_MINUTE_IN_MICROSECONDS
5859
five_mins_from_epoch_datetime = datetime.datetime(
59-
1970, 1, 1, 0, 5, 0, tzinfo=pytz.utc
60+
1970, 1, 1, 0, 5, 0, tzinfo=UTC
6061
)
6162

6263
result = datetime_helpers.from_microseconds(five_mins_from_epoch_in_microseconds)
@@ -78,28 +79,28 @@ def test_from_iso8601_time():
7879
def test_from_rfc3339():
7980
value = "2009-12-17T12:44:32.123456Z"
8081
assert datetime_helpers.from_rfc3339(value) == datetime.datetime(
81-
2009, 12, 17, 12, 44, 32, 123456, pytz.utc
82+
2009, 12, 17, 12, 44, 32, 123456, UTC
8283
)
8384

8485

8586
def test_from_rfc3339_nanos():
8687
value = "2009-12-17T12:44:32.123456Z"
8788
assert datetime_helpers.from_rfc3339_nanos(value) == datetime.datetime(
88-
2009, 12, 17, 12, 44, 32, 123456, pytz.utc
89+
2009, 12, 17, 12, 44, 32, 123456, UTC
8990
)
9091

9192

9293
def test_from_rfc3339_without_nanos():
9394
value = "2009-12-17T12:44:32Z"
9495
assert datetime_helpers.from_rfc3339(value) == datetime.datetime(
95-
2009, 12, 17, 12, 44, 32, 0, pytz.utc
96+
2009, 12, 17, 12, 44, 32, 0, UTC
9697
)
9798

9899

99100
def test_from_rfc3339_nanos_without_nanos():
100101
value = "2009-12-17T12:44:32Z"
101102
assert datetime_helpers.from_rfc3339_nanos(value) == datetime.datetime(
102-
2009, 12, 17, 12, 44, 32, 0, pytz.utc
103+
2009, 12, 17, 12, 44, 32, 0, UTC
103104
)
104105

105106

@@ -119,7 +120,7 @@ def test_from_rfc3339_nanos_without_nanos():
119120
def test_from_rfc3339_with_truncated_nanos(truncated, micros):
120121
value = "2009-12-17T12:44:32.{}Z".format(truncated)
121122
assert datetime_helpers.from_rfc3339(value) == datetime.datetime(
122-
2009, 12, 17, 12, 44, 32, micros, pytz.utc
123+
2009, 12, 17, 12, 44, 32, micros, UTC
123124
)
124125

125126

@@ -148,7 +149,7 @@ def test_from_rfc3339_nanos_is_deprecated():
148149
def test_from_rfc3339_nanos_with_truncated_nanos(truncated, micros):
149150
value = "2009-12-17T12:44:32.{}Z".format(truncated)
150151
assert datetime_helpers.from_rfc3339_nanos(value) == datetime.datetime(
151-
2009, 12, 17, 12, 44, 32, micros, pytz.utc
152+
2009, 12, 17, 12, 44, 32, micros, UTC
152153
)
153154

154155

@@ -171,20 +172,20 @@ def test_to_rfc3339():
171172

172173

173174
def test_to_rfc3339_with_utc():
174-
value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=pytz.utc)
175+
value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=UTC)
175176
expected = "2016-04-05T13:30:00.000000Z"
176177
assert datetime_helpers.to_rfc3339(value, ignore_zone=False) == expected
177178

178179

179180
def test_to_rfc3339_with_non_utc():
180-
zone = pytz.FixedOffset(-60)
181+
zone = timezone(timedelta(minutes=-60))
181182
value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone)
182183
expected = "2016-04-05T14:30:00.000000Z"
183184
assert datetime_helpers.to_rfc3339(value, ignore_zone=False) == expected
184185

185186

186187
def test_to_rfc3339_with_non_utc_ignore_zone():
187-
zone = pytz.FixedOffset(-60)
188+
zone = timezone(timedelta(minutes=-60))
188189
value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone)
189190
expected = "2016-04-05T13:30:00.000000Z"
190191
assert datetime_helpers.to_rfc3339(value, ignore_zone=True) == expected
@@ -283,7 +284,7 @@ def test_from_rfc3339_w_invalid():
283284
def test_from_rfc3339_wo_fraction():
284285
timestamp = "2016-12-20T21:13:47Z"
285286
expected = datetime_helpers.DatetimeWithNanoseconds(
286-
2016, 12, 20, 21, 13, 47, tzinfo=pytz.UTC
287+
2016, 12, 20, 21, 13, 47, tzinfo=UTC
287288
)
288289
stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp)
289290
assert stamp == expected
@@ -292,7 +293,7 @@ def test_from_rfc3339_wo_fraction():
292293
def test_from_rfc3339_w_partial_precision():
293294
timestamp = "2016-12-20T21:13:47.1Z"
294295
expected = datetime_helpers.DatetimeWithNanoseconds(
295-
2016, 12, 20, 21, 13, 47, microsecond=100000, tzinfo=pytz.UTC
296+
2016, 12, 20, 21, 13, 47, microsecond=100000, tzinfo=UTC
296297
)
297298
stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp)
298299
assert stamp == expected
@@ -301,7 +302,7 @@ def test_from_rfc3339_w_partial_precision():
301302
def test_from_rfc3339_w_full_precision():
302303
timestamp = "2016-12-20T21:13:47.123456789Z"
303304
expected = datetime_helpers.DatetimeWithNanoseconds(
304-
2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=pytz.UTC
305+
2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=UTC
305306
)
306307
stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp)
307308
assert stamp == expected
@@ -332,7 +333,7 @@ def test_timestamp_pb_wo_nanos_naive():
332333
stamp = datetime_helpers.DatetimeWithNanoseconds(
333334
2016, 12, 20, 21, 13, 47, 123456
334335
)
335-
delta = stamp.replace(tzinfo=pytz.UTC) - datetime_helpers._UTC_EPOCH
336+
delta = stamp.replace(tzinfo=UTC) - datetime_helpers._UTC_EPOCH
336337
seconds = int(delta.total_seconds())
337338
nanos = 123456000
338339
timestamp = timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos)
@@ -341,7 +342,7 @@ def test_timestamp_pb_wo_nanos_naive():
341342
@staticmethod
342343
def test_timestamp_pb_w_nanos():
343344
stamp = datetime_helpers.DatetimeWithNanoseconds(
344-
2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=pytz.UTC
345+
2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=UTC
345346
)
346347
delta = stamp - datetime_helpers._UTC_EPOCH
347348
timestamp = timestamp_pb2.Timestamp(
@@ -351,7 +352,7 @@ def test_timestamp_pb_w_nanos():
351352

352353
@staticmethod
353354
def test_from_timestamp_pb_wo_nanos():
354-
when = datetime.datetime(2016, 12, 20, 21, 13, 47, 123456, tzinfo=pytz.UTC)
355+
when = datetime.datetime(2016, 12, 20, 21, 13, 47, 123456, tzinfo=UTC)
355356
delta = when - datetime_helpers._UTC_EPOCH
356357
seconds = int(delta.total_seconds())
357358
timestamp = timestamp_pb2.Timestamp(seconds=seconds)
@@ -361,11 +362,11 @@ def test_from_timestamp_pb_wo_nanos():
361362
assert _to_seconds(when) == _to_seconds(stamp)
362363
assert stamp.microsecond == 0
363364
assert stamp.nanosecond == 0
364-
assert stamp.tzinfo == pytz.UTC
365+
assert stamp.tzinfo == UTC
365366

366367
@staticmethod
367368
def test_from_timestamp_pb_w_nanos():
368-
when = datetime.datetime(2016, 12, 20, 21, 13, 47, 123456, tzinfo=pytz.UTC)
369+
when = datetime.datetime(2016, 12, 20, 21, 13, 47, 123456, tzinfo=UTC)
369370
delta = when - datetime_helpers._UTC_EPOCH
370371
seconds = int(delta.total_seconds())
371372
timestamp = timestamp_pb2.Timestamp(seconds=seconds, nanos=123456789)
@@ -375,7 +376,7 @@ def test_from_timestamp_pb_w_nanos():
375376
assert _to_seconds(when) == _to_seconds(stamp)
376377
assert stamp.microsecond == 123456
377378
assert stamp.nanosecond == 123456789
378-
assert stamp.tzinfo == pytz.UTC
379+
assert stamp.tzinfo == UTC
379380

380381

381382
def _to_seconds(value):
@@ -387,5 +388,5 @@ def _to_seconds(value):
387388
Returns:
388389
int: Microseconds since the unix epoch.
389390
"""
390-
assert value.tzinfo is pytz.UTC
391+
assert value.tzinfo is UTC
391392
return calendar.timegm(value.timetuple())

0 commit comments

Comments
 (0)