Skip to content

Commit

Permalink
Use standard library for date and time when possible
Browse files Browse the repository at this point in the history
pytz is no longer needed. isodate is still needed for ISO durations and
missing features in Python's ISO datetime parser.
  • Loading branch information
JuneStepp committed Aug 27, 2024
1 parent 2f6ffce commit c16230e
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 62 deletions.
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"requests>=2.32.0",
"requests-toolbelt>=0.7.1",
"requests-file>=1.5.1",
"pytz",
]

docs_require = [
Expand Down
1 change: 0 additions & 1 deletion src/zeep/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from contextlib import contextmanager

import platformdirs
import pytz

# The sqlite3 is not available on Google App Engine so we handle the
# ImportError here and set the sqlite3 var to None.
Expand Down
3 changes: 1 addition & 2 deletions src/zeep/wsse/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import datetime
from uuid import uuid4

import pytz
from lxml import etree
from lxml.builder import ElementMaker

Expand Down Expand Up @@ -29,7 +28,7 @@ def get_security_header(doc):

def get_timestamp(timestamp=None, zulu_timestamp=None):
timestamp = timestamp or datetime.datetime.now(datetime.timezone.utc)
timestamp = timestamp.replace(tzinfo=pytz.utc, microsecond=0)
timestamp = timestamp.replace(tzinfo=datetime.timezone.utc, microsecond=0)
if zulu_timestamp:
return timestamp.isoformat().replace("+00:00", "Z")
else:
Expand Down
41 changes: 9 additions & 32 deletions src/zeep/xsd/types/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from decimal import Decimal as _Decimal

import isodate
import pytz

from zeep.xsd.const import xsd_ns
from zeep.xsd.types.any import AnyType
Expand Down Expand Up @@ -151,22 +150,7 @@ def xmlvalue(self, value):
if isinstance(value, str):
return value

# Bit of a hack, since datetime is a subclass of date we can't just
# test it with an isinstance(). And actually, we should not really
# care about the type, as long as it has the required attributes
if not all(hasattr(value, attr) for attr in ("hour", "minute", "second")):
value = datetime.datetime.combine(
value,
datetime.time(
getattr(value, "hour", 0),
getattr(value, "minute", 0),
getattr(value, "second", 0),
),
)

if getattr(value, "microsecond", 0):
return isodate.isostrf.strftime(value, "%Y-%m-%dT%H:%M:%S.%f%Z")
return isodate.isostrf.strftime(value, "%Y-%m-%dT%H:%M:%S%Z")
return value.isoformat().replace("+00:00", "Z")

@treat_whitespace("collapse")
def pythonvalue(self, value):
Expand All @@ -189,9 +173,7 @@ def xmlvalue(self, value):
if isinstance(value, str):
return value

if value.microsecond:
return isodate.isostrf.strftime(value, "%H:%M:%S.%f%Z")
return isodate.isostrf.strftime(value, "%H:%M:%S%Z")
return value.isoformat().replace("+00:00", "Z")

@treat_whitespace("collapse")
def pythonvalue(self, value):
Expand All @@ -206,7 +188,7 @@ class Date(BuiltinType):
def xmlvalue(self, value):
if isinstance(value, str):
return value
return isodate.isostrf.strftime(value, "%Y-%m-%d")
return value.strftime("%Y-%m-%d")

@treat_whitespace("collapse")
def pythonvalue(self, value):
Expand Down Expand Up @@ -538,35 +520,30 @@ class PositiveInteger(NonNegativeInteger):
##
# Other
def _parse_timezone(val):
"""Return a pytz.tzinfo object"""
"""Return a timezone object"""
if not val:
return

if val == "Z" or val == "+00:00":
return pytz.utc
return datetime.timezone.utc

negative = val.startswith("-")
minutes = int(val[-2:])
minutes += int(val[1:3]) * 60

if negative:
minutes = 0 - minutes
return pytz.FixedOffset(minutes)
return datetime.timezone(offset=datetime.timedelta(minutes=minutes))


def _unparse_timezone(tzinfo):
def _unparse_timezone(tzinfo: datetime.timezone):
if not tzinfo:
return ""

if tzinfo == pytz.utc:
if tzinfo == datetime.timezone.utc:
return "Z"

hours = math.floor(tzinfo._minutes / 60)
minutes = tzinfo._minutes % 60

if hours > 0:
return "+%02d:%02d" % (hours, minutes)
return "-%02d:%02d" % (abs(hours), minutes)
return datetime.datetime.now(tz=tzinfo).isoformat()[-6:]


_types = [
Expand Down
80 changes: 54 additions & 26 deletions tests/test_xsd_builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import isodate
import pytest
import pytz

from zeep.xsd.types import builtins

Expand Down Expand Up @@ -161,14 +160,16 @@ def test_xmlvalue(self):
value = datetime.datetime(2016, 3, 4, 21, 14, 42)
assert instance.xmlvalue(value) == "2016-03-04T21:14:42"

value = datetime.datetime(2016, 3, 4, 21, 14, 42, tzinfo=pytz.utc)
value = datetime.datetime(2016, 3, 4, 21, 14, 42, tzinfo=datetime.timezone.utc)
assert instance.xmlvalue(value) == "2016-03-04T21:14:42Z"

value = datetime.datetime(2016, 3, 4, 21, 14, 42, 123456, tzinfo=pytz.utc)
value = datetime.datetime(
2016, 3, 4, 21, 14, 42, 123456, tzinfo=datetime.timezone.utc
)
assert instance.xmlvalue(value) == "2016-03-04T21:14:42.123456Z"

value = datetime.datetime(2016, 3, 4, 21, 14, 42, tzinfo=pytz.utc)
value = value.astimezone(pytz.timezone("Europe/Amsterdam"))
value = datetime.datetime(2016, 3, 4, 21, 14, 42, tzinfo=datetime.timezone.utc)
value = value.astimezone(datetime.timezone(datetime.timedelta(hours=1)))
assert instance.xmlvalue(value) == "2016-03-04T22:14:42+01:00"

assert (
Expand Down Expand Up @@ -260,18 +261,22 @@ class TestgYearMonth:
def test_xmlvalue(self):
instance = builtins.gYearMonth()
assert instance.xmlvalue((2012, 10, None)) == "2012-10"
assert instance.xmlvalue((2012, 10, pytz.utc)) == "2012-10Z"
assert instance.xmlvalue((2012, 10, datetime.timezone.utc)) == "2012-10Z"

def test_pythonvalue(self):
instance = builtins.gYearMonth()
assert instance.pythonvalue("2001-10") == (2001, 10, None)
assert instance.pythonvalue("2001-10+02:00") == (
2001,
10,
pytz.FixedOffset(120),
datetime.timezone(datetime.timedelta(minutes=120)),
)
assert instance.pythonvalue("2001-10Z") == (2001, 10, datetime.timezone.utc)
assert instance.pythonvalue("2001-10+00:00") == (
2001,
10,
datetime.timezone.utc,
)
assert instance.pythonvalue("2001-10Z") == (2001, 10, pytz.utc)
assert instance.pythonvalue("2001-10+00:00") == (2001, 10, pytz.utc)
assert instance.pythonvalue("-2001-10") == (-2001, 10, None)
assert instance.pythonvalue("-20001-10") == (-20001, 10, None)

Expand All @@ -283,19 +288,22 @@ class TestgYear:
def test_xmlvalue(self):
instance = builtins.gYear()
assert instance.xmlvalue((2001, None)) == "2001"
assert instance.xmlvalue((2001, pytz.utc)) == "2001Z"
assert instance.xmlvalue((2001, datetime.timezone.utc)) == "2001Z"

def test_pythonvalue(self):
instance = builtins.gYear()
assert instance.pythonvalue("2001") == (2001, None)
assert instance.pythonvalue("2001+02:00") == (2001, pytz.FixedOffset(120))
assert instance.pythonvalue("2001Z") == (2001, pytz.utc)
assert instance.pythonvalue("2001+00:00") == (2001, pytz.utc)
assert instance.pythonvalue("2001+02:00") == (
2001,
datetime.timezone(datetime.timedelta(minutes=120)),
)
assert instance.pythonvalue("2001Z") == (2001, datetime.timezone.utc)
assert instance.pythonvalue("2001+00:00") == (2001, datetime.timezone.utc)
assert instance.pythonvalue("-2001") == (-2001, None)
assert instance.pythonvalue("-20000") == (-20000, None)
assert instance.pythonvalue(" \t2001+02:00\r\n ") == (
2001,
pytz.FixedOffset(120),
datetime.timezone(datetime.timedelta(minutes=120)),
)

with pytest.raises(builtins.ParseError):
Expand All @@ -310,9 +318,17 @@ def test_xmlvalue(self):
def test_pythonvalue(self):
instance = builtins.gMonthDay()
assert instance.pythonvalue("--05-01") == (5, 1, None)
assert instance.pythonvalue("--11-01Z") == (11, 1, pytz.utc)
assert instance.pythonvalue("--11-01+02:00") == (11, 1, pytz.FixedOffset(120))
assert instance.pythonvalue("--11-01-04:00") == (11, 1, pytz.FixedOffset(-240))
assert instance.pythonvalue("--11-01Z") == (11, 1, datetime.timezone.utc)
assert instance.pythonvalue("--11-01+02:00") == (
11,
1,
datetime.timezone(datetime.timedelta(minutes=120)),
)
assert instance.pythonvalue("--11-01-04:00") == (
11,
1,
datetime.timezone(datetime.timedelta(minutes=-240)),
)
assert instance.pythonvalue("--11-15") == (11, 15, None)
assert instance.pythonvalue("--02-29") == (2, 29, None)
assert instance.pythonvalue("\t\r\n --05-01 ") == (5, 1, None)
Expand All @@ -329,12 +345,18 @@ def test_xmlvalue(self):
def test_pythonvalue(self):
instance = builtins.gMonth()
assert instance.pythonvalue("--05") == (5, None)
assert instance.pythonvalue("--11Z") == (11, pytz.utc)
assert instance.pythonvalue("--11+02:00") == (11, pytz.FixedOffset(120))
assert instance.pythonvalue("--11-04:00") == (11, pytz.FixedOffset(-240))
assert instance.pythonvalue("--11Z") == (11, datetime.timezone.utc)
assert instance.pythonvalue("--11+02:00") == (
11,
datetime.timezone(datetime.timedelta(minutes=120)),
)
assert instance.pythonvalue("--11-04:00") == (
11,
datetime.timezone(datetime.timedelta(minutes=-240)),
)
assert instance.pythonvalue("--11") == (11, None)
assert instance.pythonvalue("--02") == (2, None)
assert instance.pythonvalue("\n\t --11Z \r") == (11, pytz.utc)
assert instance.pythonvalue("\n\t --11Z \r") == (11, datetime.timezone.utc)

with pytest.raises(builtins.ParseError):
assert instance.pythonvalue("99")
Expand All @@ -347,18 +369,24 @@ def test_xmlvalue(self):
value = (1, None)
assert instance.xmlvalue(value) == "---01"

value = (1, pytz.FixedOffset(120))
value = (1, datetime.timezone(datetime.timedelta(minutes=120)))
assert instance.xmlvalue(value) == "---01+02:00"

value = (1, pytz.FixedOffset(-240))
value = (1, datetime.timezone(datetime.timedelta(minutes=-240)))
assert instance.xmlvalue(value) == "---01-04:00"

def test_pythonvalue(self):
instance = builtins.gDay()
assert instance.pythonvalue("---01") == (1, None)
assert instance.pythonvalue("---01Z") == (1, pytz.utc)
assert instance.pythonvalue("---01+02:00") == (1, pytz.FixedOffset(120))
assert instance.pythonvalue("---01-04:00") == (1, pytz.FixedOffset(-240))
assert instance.pythonvalue("---01Z") == (1, datetime.timezone.utc)
assert instance.pythonvalue("---01+02:00") == (
1,
datetime.timezone(datetime.timedelta(minutes=120)),
)
assert instance.pythonvalue("---01-04:00") == (
1,
datetime.timezone(datetime.timedelta(minutes=-240)),
)
assert instance.pythonvalue("---15") == (15, None)
assert instance.pythonvalue("---31") == (31, None)
assert instance.pythonvalue("\r\n \t---31 ") == (31, None)
Expand Down

0 comments on commit c16230e

Please sign in to comment.