Skip to content

Commit

Permalink
fix: Drop dependency on pytz
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
pganssle committed Aug 19, 2020
1 parent db5e8ea commit 25ffdb9
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 33 deletions.
16 changes: 7 additions & 9 deletions google/api_core/datetime_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@
import datetime
import re

import pytz

from google.protobuf import timestamp_pb2


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

Expand Down Expand Up @@ -156,7 +154,7 @@ def from_rfc3339(value):
nanos = int(fraction) * (10 ** scale)
micros = nanos // 1000

return bare_seconds.replace(microsecond=micros, tzinfo=pytz.utc)
return bare_seconds.replace(microsecond=micros, tzinfo=datetime.timezone.utc)


from_rfc3339_nanos = from_rfc3339 # from_rfc3339_nanos method was deprecated.
Expand Down Expand Up @@ -256,7 +254,7 @@ def from_rfc3339(cls, stamp):
bare.minute,
bare.second,
nanosecond=nanos,
tzinfo=pytz.UTC,
tzinfo=datetime.timezone.utc,
)

def timestamp_pb(self):
Expand All @@ -265,7 +263,7 @@ def timestamp_pb(self):
Returns:
(:class:`~google.protobuf.timestamp_pb2.Timestamp`): Timestamp message
"""
inst = self if self.tzinfo is not None else self.replace(tzinfo=pytz.UTC)
inst = self if self.tzinfo is not None else self.replace(tzinfo=datetime.timezone.utc)
delta = inst - _UTC_EPOCH
seconds = int(delta.total_seconds())
nanos = self._nanosecond or self.microsecond * 1000
Expand All @@ -292,5 +290,5 @@ def from_timestamp_pb(cls, stamp):
bare.minute,
bare.second,
nanosecond=stamp.nanos,
tzinfo=pytz.UTC,
tzinfo=datetime.timezone.utc,
)
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"requests >= 2.18.0, < 3.0.0dev",
"setuptools >= 34.0.0",
"six >= 1.10.0",
"pytz",
'futures >= 3.2.0; python_version < "3.2"',
]
extras = {
Expand Down
47 changes: 24 additions & 23 deletions tests/unit/test_datetime_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@

import calendar
import datetime
from datetime import timezone, timedelta

import pytest
import pytz

from google.api_core import datetime_helpers
from google.protobuf import timestamp_pb2


UTC = timezone.utc
ONE_MINUTE_IN_MICROSECONDS = 60 * 1e6


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


def test_to_milliseconds():
dt = datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
dt = datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=UTC)
assert datetime_helpers.to_milliseconds(dt) == 1000


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


def test_to_microseconds_non_utc():
zone = pytz.FixedOffset(-1)
zone = timezone(timedelta(minutes=-1))
dt = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=zone)
assert datetime_helpers.to_microseconds(dt) == ONE_MINUTE_IN_MICROSECONDS

Expand All @@ -56,7 +57,7 @@ def test_to_microseconds_naive():
def test_from_microseconds():
five_mins_from_epoch_in_microseconds = 5 * ONE_MINUTE_IN_MICROSECONDS
five_mins_from_epoch_datetime = datetime.datetime(
1970, 1, 1, 0, 5, 0, tzinfo=pytz.utc
1970, 1, 1, 0, 5, 0, tzinfo=UTC
)

result = datetime_helpers.from_microseconds(five_mins_from_epoch_in_microseconds)
Expand All @@ -78,28 +79,28 @@ def test_from_iso8601_time():
def test_from_rfc3339():
value = "2009-12-17T12:44:32.123456Z"
assert datetime_helpers.from_rfc3339(value) == datetime.datetime(
2009, 12, 17, 12, 44, 32, 123456, pytz.utc
2009, 12, 17, 12, 44, 32, 123456, UTC
)


def test_from_rfc3339_nanos():
value = "2009-12-17T12:44:32.123456Z"
assert datetime_helpers.from_rfc3339_nanos(value) == datetime.datetime(
2009, 12, 17, 12, 44, 32, 123456, pytz.utc
2009, 12, 17, 12, 44, 32, 123456, UTC
)


def test_from_rfc3339_without_nanos():
value = "2009-12-17T12:44:32Z"
assert datetime_helpers.from_rfc3339(value) == datetime.datetime(
2009, 12, 17, 12, 44, 32, 0, pytz.utc
2009, 12, 17, 12, 44, 32, 0, UTC
)


def test_from_rfc3339_nanos_without_nanos():
value = "2009-12-17T12:44:32Z"
assert datetime_helpers.from_rfc3339_nanos(value) == datetime.datetime(
2009, 12, 17, 12, 44, 32, 0, pytz.utc
2009, 12, 17, 12, 44, 32, 0, UTC
)


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


Expand Down Expand Up @@ -148,7 +149,7 @@ def test_from_rfc3339_nanos_is_deprecated():
def test_from_rfc3339_nanos_with_truncated_nanos(truncated, micros):
value = "2009-12-17T12:44:32.{}Z".format(truncated)
assert datetime_helpers.from_rfc3339_nanos(value) == datetime.datetime(
2009, 12, 17, 12, 44, 32, micros, pytz.utc
2009, 12, 17, 12, 44, 32, micros, UTC
)


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


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


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


def test_to_rfc3339_with_non_utc_ignore_zone():
zone = pytz.FixedOffset(-60)
zone = timezone(timedelta(minutes=-60))
value = datetime.datetime(2016, 4, 5, 13, 30, 0, tzinfo=zone)
expected = "2016-04-05T13:30:00.000000Z"
assert datetime_helpers.to_rfc3339(value, ignore_zone=True) == expected
Expand Down Expand Up @@ -283,7 +284,7 @@ def test_from_rfc3339_w_invalid():
def test_from_rfc3339_wo_fraction():
timestamp = "2016-12-20T21:13:47Z"
expected = datetime_helpers.DatetimeWithNanoseconds(
2016, 12, 20, 21, 13, 47, tzinfo=pytz.UTC
2016, 12, 20, 21, 13, 47, tzinfo=UTC
)
stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp)
assert stamp == expected
Expand All @@ -292,7 +293,7 @@ def test_from_rfc3339_wo_fraction():
def test_from_rfc3339_w_partial_precision():
timestamp = "2016-12-20T21:13:47.1Z"
expected = datetime_helpers.DatetimeWithNanoseconds(
2016, 12, 20, 21, 13, 47, microsecond=100000, tzinfo=pytz.UTC
2016, 12, 20, 21, 13, 47, microsecond=100000, tzinfo=UTC
)
stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp)
assert stamp == expected
Expand All @@ -301,7 +302,7 @@ def test_from_rfc3339_w_partial_precision():
def test_from_rfc3339_w_full_precision():
timestamp = "2016-12-20T21:13:47.123456789Z"
expected = datetime_helpers.DatetimeWithNanoseconds(
2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=pytz.UTC
2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=UTC
)
stamp = datetime_helpers.DatetimeWithNanoseconds.from_rfc3339(timestamp)
assert stamp == expected
Expand Down Expand Up @@ -332,7 +333,7 @@ def test_timestamp_pb_wo_nanos_naive():
stamp = datetime_helpers.DatetimeWithNanoseconds(
2016, 12, 20, 21, 13, 47, 123456
)
delta = stamp.replace(tzinfo=pytz.UTC) - datetime_helpers._UTC_EPOCH
delta = stamp.replace(tzinfo=UTC) - datetime_helpers._UTC_EPOCH
seconds = int(delta.total_seconds())
nanos = 123456000
timestamp = timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos)
Expand All @@ -341,7 +342,7 @@ def test_timestamp_pb_wo_nanos_naive():
@staticmethod
def test_timestamp_pb_w_nanos():
stamp = datetime_helpers.DatetimeWithNanoseconds(
2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=pytz.UTC
2016, 12, 20, 21, 13, 47, nanosecond=123456789, tzinfo=UTC
)
delta = stamp - datetime_helpers._UTC_EPOCH
timestamp = timestamp_pb2.Timestamp(
Expand All @@ -351,7 +352,7 @@ def test_timestamp_pb_w_nanos():

@staticmethod
def test_from_timestamp_pb_wo_nanos():
when = datetime.datetime(2016, 12, 20, 21, 13, 47, 123456, tzinfo=pytz.UTC)
when = datetime.datetime(2016, 12, 20, 21, 13, 47, 123456, tzinfo=UTC)
delta = when - datetime_helpers._UTC_EPOCH
seconds = int(delta.total_seconds())
timestamp = timestamp_pb2.Timestamp(seconds=seconds)
Expand All @@ -361,11 +362,11 @@ def test_from_timestamp_pb_wo_nanos():
assert _to_seconds(when) == _to_seconds(stamp)
assert stamp.microsecond == 0
assert stamp.nanosecond == 0
assert stamp.tzinfo == pytz.UTC
assert stamp.tzinfo == UTC

@staticmethod
def test_from_timestamp_pb_w_nanos():
when = datetime.datetime(2016, 12, 20, 21, 13, 47, 123456, tzinfo=pytz.UTC)
when = datetime.datetime(2016, 12, 20, 21, 13, 47, 123456, tzinfo=UTC)
delta = when - datetime_helpers._UTC_EPOCH
seconds = int(delta.total_seconds())
timestamp = timestamp_pb2.Timestamp(seconds=seconds, nanos=123456789)
Expand All @@ -375,7 +376,7 @@ def test_from_timestamp_pb_w_nanos():
assert _to_seconds(when) == _to_seconds(stamp)
assert stamp.microsecond == 123456
assert stamp.nanosecond == 123456789
assert stamp.tzinfo == pytz.UTC
assert stamp.tzinfo == UTC


def _to_seconds(value):
Expand All @@ -387,5 +388,5 @@ def _to_seconds(value):
Returns:
int: Microseconds since the unix epoch.
"""
assert value.tzinfo is pytz.UTC
assert value.tzinfo is UTC
return calendar.timegm(value.timetuple())

0 comments on commit 25ffdb9

Please sign in to comment.