Skip to content

Commit

Permalink
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 7a09ca5 commit 378b018
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 378b018

Please sign in to comment.