From 20d84421158bf3090369368a3641c16a7297e2d2 Mon Sep 17 00:00:00 2001 From: Chris Davoren Date: Mon, 18 Dec 2017 12:57:52 +1000 Subject: [PATCH 1/8] On load, now use aware datetimes if possible On loading data, if timestamps have an ISO "+HH:MM" UTC offset then the resultant datetime is converted to UTC. This change adds that timezone information to the datetime objects. Importantly, this addresses a Django warning (and potential error) that appears when using both YAML fixtures in a timezone-aware project. It was raised as a Django issue (https://code.djangoproject.com/ticket/18867), but subsequently closed because the Django devs felt that this is a PyYAML problem. --- lib3/yaml/constructor.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib3/yaml/constructor.py b/lib3/yaml/constructor.py index fb4f1e9f..cd8a2f80 100644 --- a/lib3/yaml/constructor.py +++ b/lib3/yaml/constructor.py @@ -336,9 +336,15 @@ def construct_yaml_timestamp(self, node): delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) if values['tz_sign'] == '-': delta = -delta - data = datetime.datetime(year, month, day, hour, minute, second, fraction) - if delta: - data -= delta + data = None + # If we are correcting to UTC, we can make the datetime object + # timezone-aware + if delta is not None: + data = datetime.datetime(year, month, day, hour, minute, second, fraction, datetime.timezone.utc) + if delta: + data -= delta + else: + data = datetime.datetime(year, month, day, hour, minute, second, fraction) return data def construct_yaml_omap(self, node): From f7e81e6bb34bf2f14de440636d8fdc5bf08abcc5 Mon Sep 17 00:00:00 2001 From: Mattijs Ugen <144798+akaIDIOT@users.noreply.github.com> Date: Mon, 7 May 2018 13:58:06 +0200 Subject: [PATCH 2/8] Create timezone-aware datetime in timezone from data --- lib3/yaml/constructor.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lib3/yaml/constructor.py b/lib3/yaml/constructor.py index cd8a2f80..bb169e3f 100644 --- a/lib3/yaml/constructor.py +++ b/lib3/yaml/constructor.py @@ -329,23 +329,16 @@ def construct_yaml_timestamp(self, node): while len(fraction) < 6: fraction += '0' fraction = int(fraction) - delta = None if values['tz_sign']: tz_hour = int(values['tz_hour']) tz_minute = int(values['tz_minute'] or 0) delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) if values['tz_sign'] == '-': delta = -delta - data = None - # If we are correcting to UTC, we can make the datetime object - # timezone-aware - if delta is not None: - data = datetime.datetime(year, month, day, hour, minute, second, fraction, datetime.timezone.utc) - if delta: - data -= delta + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=datetime.timezone(delta)) else: - data = datetime.datetime(year, month, day, hour, minute, second, fraction) - return data + return datetime.datetime(year, month, day, hour, minute, second, fraction) def construct_yaml_omap(self, node): # Note: we do not check for duplicate keys, because it's too From 413a4df61179230be44a501343d59b312b25b715 Mon Sep 17 00:00:00 2001 From: Mattijs Ugen <144798+akaIDIOT@users.noreply.github.com> Date: Mon, 7 May 2018 14:44:55 +0200 Subject: [PATCH 3/8] Create timezone-aware datetime in timezone from data for python2 --- lib/yaml/constructor.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py index 859c9494..30003899 100644 --- a/lib/yaml/constructor.py +++ b/lib/yaml/constructor.py @@ -293,7 +293,7 @@ def construct_yaml_binary(self, node): return str(value).decode('base64') except (binascii.Error, UnicodeEncodeError), exc: raise ConstructorError(None, None, - "failed to decode base64 data: %s" % exc, node.start_mark) + "failed to decode base64 data: %s" % exc, node.start_mark) timestamp_regexp = re.compile( ur'''^(?P[0-9][0-9][0-9][0-9]) @@ -307,6 +307,13 @@ def construct_yaml_binary(self, node): (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) (?::(?P[0-9][0-9]))?))?)?$''', re.X) + def timezone(self, offset): + class tz(datetime.tzinfo): + def utcoffset(self, dt=None): + return offset + + return tz() + def construct_yaml_timestamp(self, node): value = self.construct_scalar(node) match = self.timestamp_regexp.match(node.value) @@ -325,17 +332,16 @@ def construct_yaml_timestamp(self, node): while len(fraction) < 6: fraction += '0' fraction = int(fraction) - delta = None if values['tz_sign']: tz_hour = int(values['tz_hour']) tz_minute = int(values['tz_minute'] or 0) delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) if values['tz_sign'] == '-': delta = -delta - data = datetime.datetime(year, month, day, hour, minute, second, fraction) - if delta: - data -= delta - return data + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=self.timezone(delta)) + else: + return datetime.datetime(year, month, day, hour, minute, second, fraction) def construct_yaml_omap(self, node): # Note: we do not check for duplicate keys, because it's too From c78a22d63f28c1c74f6768478c0a2da8b8586e1a Mon Sep 17 00:00:00 2001 From: Mattijs Ugen <144798+akaIDIOT@users.noreply.github.com> Date: Wed, 24 Apr 2019 10:59:32 +0200 Subject: [PATCH 4/8] Define better timezone implementation for python2 --- lib/yaml/constructor.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py index 30003899..dd5e4337 100644 --- a/lib/yaml/constructor.py +++ b/lib/yaml/constructor.py @@ -18,6 +18,29 @@ class ConstructorError(MarkedYAMLError): pass + +class timezone(datetime.tzinfo): + def __init__(self, offset): + self._offset = offset + seconds = abs(offset).total_seconds() + self._name = '%s%02d:%02d' % ( + '-' if offset.days < 0 else '+', + seconds // 3600, + seconds % 3600 // 60 + ) + + def tzname(self, dt=None): + return self._name + + def utcoffset(self, dt=None): + return self._offset + + def dst(self, dt=None): + return datetime.timedelta(0) + + __repr__ = __str__ = tzname + + class BaseConstructor(object): yaml_constructors = {} @@ -307,13 +330,6 @@ def construct_yaml_binary(self, node): (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) (?::(?P[0-9][0-9]))?))?)?$''', re.X) - def timezone(self, offset): - class tz(datetime.tzinfo): - def utcoffset(self, dt=None): - return offset - - return tz() - def construct_yaml_timestamp(self, node): value = self.construct_scalar(node) match = self.timestamp_regexp.match(node.value) @@ -339,7 +355,7 @@ def construct_yaml_timestamp(self, node): if values['tz_sign'] == '-': delta = -delta return datetime.datetime(year, month, day, hour, minute, second, fraction, - tzinfo=self.timezone(delta)) + tzinfo=timezone(delta)) else: return datetime.datetime(year, month, day, hour, minute, second, fraction) From 09705f829b95e21a17cb161e4661109500854460 Mon Sep 17 00:00:00 2001 From: Mattijs Ugen <144798+akaIDIOT@users.noreply.github.com> Date: Wed, 24 Apr 2019 11:01:34 +0200 Subject: [PATCH 5/8] Handle timezone "Z" for python 3 --- lib3/yaml/constructor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib3/yaml/constructor.py b/lib3/yaml/constructor.py index bb169e3f..717cbf41 100644 --- a/lib3/yaml/constructor.py +++ b/lib3/yaml/constructor.py @@ -337,6 +337,9 @@ def construct_yaml_timestamp(self, node): delta = -delta return datetime.datetime(year, month, day, hour, minute, second, fraction, tzinfo=datetime.timezone(delta)) + elif values['tz']: + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=datetime.timezone.utc) else: return datetime.datetime(year, month, day, hour, minute, second, fraction) From 31c176f2a2af384c76d9801c85f3f1bdee2b35a2 Mon Sep 17 00:00:00 2001 From: Mattijs Ugen <144798+akaIDIOT@users.noreply.github.com> Date: Wed, 24 Apr 2019 11:01:46 +0200 Subject: [PATCH 6/8] Handle timezone "Z" for python 2 --- lib/yaml/constructor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py index dd5e4337..4a1aca83 100644 --- a/lib/yaml/constructor.py +++ b/lib/yaml/constructor.py @@ -356,6 +356,9 @@ def construct_yaml_timestamp(self, node): delta = -delta return datetime.datetime(year, month, day, hour, minute, second, fraction, tzinfo=timezone(delta)) + elif values['tz']: + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=timezone(datetime.timedelta(0))) else: return datetime.datetime(year, month, day, hour, minute, second, fraction) From f2f461782f1d00e25b1a087805970517b74648f8 Mon Sep 17 00:00:00 2001 From: Mattijs Ugen <144798+akaIDIOT@users.noreply.github.com> Date: Tue, 10 Dec 2019 11:29:14 +0100 Subject: [PATCH 7/8] Fix code structure for Python 3 Call datetime.datetime constructor once at return. --- lib3/yaml/constructor.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib3/yaml/constructor.py b/lib3/yaml/constructor.py index 717cbf41..2a56ffea 100644 --- a/lib3/yaml/constructor.py +++ b/lib3/yaml/constructor.py @@ -324,6 +324,7 @@ def construct_yaml_timestamp(self, node): minute = int(values['minute']) second = int(values['second']) fraction = 0 + tzinfo = None if values['fraction']: fraction = values['fraction'][:6] while len(fraction) < 6: @@ -335,13 +336,11 @@ def construct_yaml_timestamp(self, node): delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) if values['tz_sign'] == '-': delta = -delta - return datetime.datetime(year, month, day, hour, minute, second, fraction, - tzinfo=datetime.timezone(delta)) + tzinfo = datetime.timezone(delta) elif values['tz']: - return datetime.datetime(year, month, day, hour, minute, second, fraction, - tzinfo=datetime.timezone.utc) - else: - return datetime.datetime(year, month, day, hour, minute, second, fraction) + tzinfo = datetime.timezone.utc + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=tzinfo) def construct_yaml_omap(self, node): # Note: we do not check for duplicate keys, because it's too From 5218216b3ae2b7521883a10ad8a2b20f201a65c5 Mon Sep 17 00:00:00 2001 From: Mattijs Ugen <144798+akaIDIOT@users.noreply.github.com> Date: Tue, 10 Dec 2019 11:29:37 +0100 Subject: [PATCH 8/8] Fix code structure for Python 2 Call datetime.datetime constructor once at return. --- lib/yaml/constructor.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py index 4a1aca83..141941dc 100644 --- a/lib/yaml/constructor.py +++ b/lib/yaml/constructor.py @@ -343,6 +343,7 @@ def construct_yaml_timestamp(self, node): minute = int(values['minute']) second = int(values['second']) fraction = 0 + tzinfo = None if values['fraction']: fraction = values['fraction'][:6] while len(fraction) < 6: @@ -354,13 +355,11 @@ def construct_yaml_timestamp(self, node): delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute) if values['tz_sign'] == '-': delta = -delta - return datetime.datetime(year, month, day, hour, minute, second, fraction, - tzinfo=timezone(delta)) + tzinfo = timezone(delta) elif values['tz']: - return datetime.datetime(year, month, day, hour, minute, second, fraction, - tzinfo=timezone(datetime.timedelta(0))) - else: - return datetime.datetime(year, month, day, hour, minute, second, fraction) + tzinfo = timezone(datetime.timedelta(0)) + return datetime.datetime(year, month, day, hour, minute, second, fraction, + tzinfo=tzinfo) def construct_yaml_omap(self, node): # Note: we do not check for duplicate keys, because it's too