From 797d3bbfd80d2fe8a20130734fa1d0f83e879d00 Mon Sep 17 00:00:00 2001 From: Laurent Mazuel Date: Wed, 6 May 2020 16:56:52 -0700 Subject: [PATCH 1/5] Make RFC parsing not dependent of locale --- msrest/serialization.py | 31 +++++++++++++++++++++++++++---- tests/test_serialization.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/msrest/serialization.py b/msrest/serialization.py index 59187e1708..07377dbcdd 100644 --- a/msrest/serialization.py +++ b/msrest/serialization.py @@ -28,6 +28,7 @@ import calendar import datetime import decimal +import email from enum import Enum import json import logging @@ -78,6 +79,26 @@ def dst(self, dt): """No daylight saving for UTC.""" return datetime.timedelta(hours=1) +class _FixedOffset(datetime.tzinfo): + """Fixed offset in minutes east from UTC. + Copy/pasted from Python doc + :param int offset: offset in minutes + """ + + def __init__(self, offset): + self.__offset = datetime.timedelta(minutes=offset) + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return str(self.__offset.total_seconds()/3600) + + def __repr__(self): + return "".format(self.tzname(None)) + + def dst(self, dt): + return datetime.timedelta(0) try: from datetime import timezone @@ -1814,10 +1835,12 @@ def deserialize_rfc(attr): if isinstance(attr, ET.Element): attr = attr.text try: - date_obj = datetime.datetime.strptime( - attr, "%a, %d %b %Y %H:%M:%S %Z") - if not date_obj.tzinfo: - date_obj = date_obj.replace(tzinfo=TZ_UTC) + parsed_date = email.utils.parsedate_tz(attr) + date_obj = datetime.datetime( + *parsed_date[:6], + tzinfo=_FixedOffset(parsed_date[9]/60) + ) + date_obj.astimezone(tz=TZ_UTC) except ValueError as err: msg = "Cannot deserialize to rfc datetime object." raise_with_traceback(DeserializationError, msg, err) diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 12a0042657..a5110e325d 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -1974,6 +1974,35 @@ def test_deserialize_datetime(self): self.assertEqual(utc.tm_sec, 52) self.assertEqual(a.microsecond, 780000) + def test_deserialize_datetime_rfc(self): + d = "Mon, 20 Nov 1995 19:12:08 -0500" + + a = Deserializer.deserialize_rfc("Mon, 20 Nov 1995 19:12:08 -0500") + utc = a.utctimetuple() + + # UTC: 21 Nov, 00:12:08 + self.assertEqual(utc.tm_year, 1995) + self.assertEqual(utc.tm_mon, 11) + self.assertEqual(utc.tm_mday, 21) + self.assertEqual(utc.tm_hour, 0) + self.assertEqual(utc.tm_min, 12) + self.assertEqual(utc.tm_sec, 8) + self.assertEqual(a.microsecond, 0) + + d = "Mon, 20 Nov 1995 19:12:08 -0500" + + a = Deserializer.deserialize_rfc("Mon, 20 Nov 1995 19:12:08") + utc = a.utctimetuple() + + # UTC: No info is considered UTC + self.assertEqual(utc.tm_year, 1995) + self.assertEqual(utc.tm_mon, 11) + self.assertEqual(utc.tm_mday, 20) + self.assertEqual(utc.tm_hour, 19) + self.assertEqual(utc.tm_min, 12) + self.assertEqual(utc.tm_sec, 8) + self.assertEqual(a.microsecond, 0) + def test_polymorphic_deserialization(self): class Zoo(Model): From efd9b698a7453ced94dbe03840b84357e3c2a3a4 Mon Sep 17 00:00:00 2001 From: Laurent Mazuel Date: Wed, 6 May 2020 17:04:03 -0700 Subject: [PATCH 2/5] Add more tests --- tests/test_serialization.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/test_serialization.py b/tests/test_serialization.py index a5110e325d..fffd8a9b36 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -1975,7 +1975,6 @@ def test_deserialize_datetime(self): self.assertEqual(a.microsecond, 780000) def test_deserialize_datetime_rfc(self): - d = "Mon, 20 Nov 1995 19:12:08 -0500" a = Deserializer.deserialize_rfc("Mon, 20 Nov 1995 19:12:08 -0500") utc = a.utctimetuple() @@ -1989,7 +1988,17 @@ def test_deserialize_datetime_rfc(self): self.assertEqual(utc.tm_sec, 8) self.assertEqual(a.microsecond, 0) - d = "Mon, 20 Nov 1995 19:12:08 -0500" + a = Deserializer.deserialize_rfc("Mon, 20 Nov 1995 19:12:08 CDT") + utc = a.utctimetuple() + + # UTC: 21 Nov, 00:12:08 + self.assertEqual(utc.tm_year, 1995) + self.assertEqual(utc.tm_mon, 11) + self.assertEqual(utc.tm_mday, 21) + self.assertEqual(utc.tm_hour, 0) + self.assertEqual(utc.tm_min, 12) + self.assertEqual(utc.tm_sec, 8) + self.assertEqual(a.microsecond, 0) a = Deserializer.deserialize_rfc("Mon, 20 Nov 1995 19:12:08") utc = a.utctimetuple() @@ -2003,6 +2012,17 @@ def test_deserialize_datetime_rfc(self): self.assertEqual(utc.tm_sec, 8) self.assertEqual(a.microsecond, 0) + a = Deserializer.deserialize_rfc("Mon, 20 Nov 1995 19:12:08 GMT") + utc = a.utctimetuple() + + self.assertEqual(utc.tm_year, 1995) + self.assertEqual(utc.tm_mon, 11) + self.assertEqual(utc.tm_mday, 20) + self.assertEqual(utc.tm_hour, 19) + self.assertEqual(utc.tm_min, 12) + self.assertEqual(utc.tm_sec, 8) + self.assertEqual(a.microsecond, 0) + def test_polymorphic_deserialization(self): class Zoo(Model): From a24f09e3a68df0e77213349d5927255bcc0e12af Mon Sep 17 00:00:00 2001 From: Laurent Mazuel Date: Wed, 6 May 2020 17:24:12 -0700 Subject: [PATCH 3/5] Return as UTC always --- msrest/serialization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msrest/serialization.py b/msrest/serialization.py index 07377dbcdd..f03942deef 100644 --- a/msrest/serialization.py +++ b/msrest/serialization.py @@ -1840,7 +1840,7 @@ def deserialize_rfc(attr): *parsed_date[:6], tzinfo=_FixedOffset(parsed_date[9]/60) ) - date_obj.astimezone(tz=TZ_UTC) + date_obj = date_obj.astimezone(tz=TZ_UTC) except ValueError as err: msg = "Cannot deserialize to rfc datetime object." raise_with_traceback(DeserializationError, msg, err) From a8070b2b9c73ea3c5730b94b94e5e91207de5afc Mon Sep 17 00:00:00 2001 From: Laurent Mazuel Date: Wed, 6 May 2020 17:27:03 -0700 Subject: [PATCH 4/5] Keep timezone if any --- msrest/serialization.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/msrest/serialization.py b/msrest/serialization.py index f03942deef..41daa7f092 100644 --- a/msrest/serialization.py +++ b/msrest/serialization.py @@ -1840,7 +1840,8 @@ def deserialize_rfc(attr): *parsed_date[:6], tzinfo=_FixedOffset(parsed_date[9]/60) ) - date_obj = date_obj.astimezone(tz=TZ_UTC) + if not date_obj.tzinfo: + date_obj = date_obj.astimezone(tz=TZ_UTC) except ValueError as err: msg = "Cannot deserialize to rfc datetime object." raise_with_traceback(DeserializationError, msg, err) From 733e8a2030c86c3b92bb4d90d0b09e60a6bd6e01 Mon Sep 17 00:00:00 2001 From: Laurent Mazuel Date: Fri, 8 May 2020 13:19:39 -0700 Subject: [PATCH 5/5] Python 2.7 compat --- msrest/serialization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msrest/serialization.py b/msrest/serialization.py index 41daa7f092..0e65d8e5c8 100644 --- a/msrest/serialization.py +++ b/msrest/serialization.py @@ -1838,7 +1838,7 @@ def deserialize_rfc(attr): parsed_date = email.utils.parsedate_tz(attr) date_obj = datetime.datetime( *parsed_date[:6], - tzinfo=_FixedOffset(parsed_date[9]/60) + tzinfo=_FixedOffset((parsed_date[9] or 0)/60) ) if not date_obj.tzinfo: date_obj = date_obj.astimezone(tz=TZ_UTC)