From 3193c0fd9dd09c961e7f4a528ba01e0d732a36ab Mon Sep 17 00:00:00 2001 From: xi Date: Wed, 16 Aug 2006 18:22:38 +0000 Subject: [PATCH] Fix timestamp constructing and representing (close #25). git-svn-id: http://svn.pyyaml.org/pyyaml/trunk@225 18f92427-320e-0410-9341-c67f048884a3 --- lib/yaml/constructor.py | 55 +++++++++++++++++++------------------ lib/yaml/representer.py | 25 +++++------------ tests/data/construct-timestamp.code | 2 +- tests/data/timestamp-bugs.code | 7 +++++ tests/data/timestamp-bugs.data | 5 ++++ tests/test_constructor.py | 28 +++++++++++++++---- tests/test_representer.py | 6 +++- 7 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 tests/data/timestamp-bugs.code create mode 100644 tests/data/timestamp-bugs.data diff --git a/lib/yaml/constructor.py b/lib/yaml/constructor.py index cbbcd78..d1d4f6a 100644 --- a/lib/yaml/constructor.py +++ b/lib/yaml/constructor.py @@ -5,11 +5,7 @@ __all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor', from error import * from nodes import * -try: - import datetime - datetime_available = True -except ImportError: - datetime_available = False +import datetime try: set @@ -304,28 +300,36 @@ class SafeConstructor(BaseConstructor): (?P[0-9][0-9]?) :(?P[0-9][0-9]) :(?P[0-9][0-9]) - (?:\.(?P[0-9]*))? - (?:[ \t]*(?:Z|(?P[-+][0-9][0-9]?) - (?::(?P[0-9][0-9])?)?))?)?$''', re.X) + (?:(?P\.[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)?$''', re.X) def construct_yaml_timestamp(self, node): value = self.construct_scalar(node) match = self.timestamp_regexp.match(node.value) values = match.groupdict() - for key in values: - if values[key]: - values[key] = int(values[key]) - else: - values[key] = 0 - fraction = values['fraction'] - if fraction: - while 10*fraction < 1000000: - fraction *= 10 - values['fraction'] = fraction - stamp = datetime.datetime(values['year'], values['month'], values['day'], - values['hour'], values['minute'], values['second'], values['fraction']) - diff = datetime.timedelta(hours=values['tz_hour'], minutes=values['tz_minute']) - return stamp-diff + year = int(values['year']) + month = int(values['month']) + day = int(values['day']) + if not values['hour']: + return datetime.date(year, month, day) + hour = int(values['hour']) + minute = int(values['minute']) + second = int(values['second']) + fraction = 0 + if values['fraction']: + fraction = int(float(values['fraction'])*1000000) + 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 def construct_yaml_omap(self, node): # Note: we do not check for duplicate keys, because it's too @@ -429,10 +433,9 @@ SafeConstructor.add_constructor( u'tag:yaml.org,2002:binary', SafeConstructor.construct_yaml_binary) -if datetime_available: - SafeConstructor.add_constructor( - u'tag:yaml.org,2002:timestamp', - SafeConstructor.construct_yaml_timestamp) +SafeConstructor.add_constructor( + u'tag:yaml.org,2002:timestamp', + SafeConstructor.construct_yaml_timestamp) SafeConstructor.add_constructor( u'tag:yaml.org,2002:omap', diff --git a/lib/yaml/representer.py b/lib/yaml/representer.py index 44957c4..3563a4c 100644 --- a/lib/yaml/representer.py +++ b/lib/yaml/representer.py @@ -5,11 +5,7 @@ __all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer', from error import * from nodes import * -try: - import datetime - datetime_available = True -except ImportError: - datetime_available = False +import datetime try: set @@ -241,17 +237,11 @@ class SafeRepresenter(BaseRepresenter): return self.represent_mapping(u'tag:yaml.org,2002:set', value) def represent_date(self, data): - value = u'%04d-%02d-%02d' % (data.year, data.month, data.day) + value = unicode(data.isoformat()) return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value) def represent_datetime(self, data): - value = u'%04d-%02d-%02d %02d:%02d:%02d' \ - % (data.year, data.month, data.day, - data.hour, data.minute, data.second) - if data.microsecond: - value += u'.' + unicode(data.microsecond/1000000.0).split(u'.')[1] - if data.utcoffset(): - value += unicode(data.utcoffset()) + value = unicode(data.isoformat(' ')) return self.represent_scalar(u'tag:yaml.org,2002:timestamp', value) def represent_yaml_object(self, tag, data, cls, flow_style=None): @@ -297,11 +287,10 @@ SafeRepresenter.add_representer(dict, SafeRepresenter.add_representer(set, SafeRepresenter.represent_set) -if datetime_available: - SafeRepresenter.add_representer(datetime.date, - SafeRepresenter.represent_date) - SafeRepresenter.add_representer(datetime.datetime, - SafeRepresenter.represent_datetime) +SafeRepresenter.add_representer(datetime.date, + SafeRepresenter.represent_date) +SafeRepresenter.add_representer(datetime.datetime, + SafeRepresenter.represent_datetime) SafeRepresenter.add_representer(None, SafeRepresenter.represent_undefined) diff --git a/tests/data/construct-timestamp.code b/tests/data/construct-timestamp.code index 288022e..ffc3b2f 100644 --- a/tests/data/construct-timestamp.code +++ b/tests/data/construct-timestamp.code @@ -3,5 +3,5 @@ "valid iso8601": datetime.datetime(2001, 12, 15, 2, 59, 43, 100000), "space separated": datetime.datetime(2001, 12, 15, 2, 59, 43, 100000), "no time zone (Z)": datetime.datetime(2001, 12, 15, 2, 59, 43, 100000), - "date (00:00:00Z)": datetime.datetime(2002, 12, 14), + "date (00:00:00Z)": datetime.date(2002, 12, 14), } diff --git a/tests/data/timestamp-bugs.code b/tests/data/timestamp-bugs.code new file mode 100644 index 0000000..47ac491 --- /dev/null +++ b/tests/data/timestamp-bugs.code @@ -0,0 +1,7 @@ +[ + datetime.datetime(2001, 12, 15, 3, 29, 43, 100000), + datetime.datetime(2001, 12, 14, 16, 29, 43, 100000), + datetime.datetime(2001, 12, 14, 21, 59, 43, 1010), + datetime.datetime(2001, 12, 14, 21, 59, 43, 0, FixedOffset(60, "+1")), + datetime.datetime(2001, 12, 14, 21, 59, 43, 0, FixedOffset(-90, "-1:30")), +] diff --git a/tests/data/timestamp-bugs.data b/tests/data/timestamp-bugs.data new file mode 100644 index 0000000..bc3223f --- /dev/null +++ b/tests/data/timestamp-bugs.data @@ -0,0 +1,5 @@ +- 2001-12-14 21:59:43.10 -5:30 +- 2001-12-14 21:59:43.10 +5:30 +- 2001-12-14 21:59:43.00101 +- 2001-12-14 21:59:43+1 +- 2001-12-14 21:59:43-1:30 diff --git a/tests/test_constructor.py b/tests/test_constructor.py index 200b112..0c873e9 100644 --- a/tests/test_constructor.py +++ b/tests/test_constructor.py @@ -1,9 +1,7 @@ import test_appliance -try: - import datetime -except ImportError: - pass + +import datetime try: set except NameError: @@ -240,6 +238,22 @@ class MyDict(dict): def __eq__(self, other): return type(self) is type(other) and dict(self) == dict(other) +class FixedOffset(datetime.tzinfo): + + def __init__(self, offset, name): + self.__offset = datetime.timedelta(minutes=offset) + self.__name = name + + def utcoffset(self, dt): + return self.__offset + + def tzname(self, dt): + return self.__name + + def dst(self, dt): + return datetime.timedelta(0) + + def execute(code): exec code return value @@ -257,7 +271,7 @@ class TestConstructorTypes(test_appliance.TestAppliance): self.failUnlessEqual(type(data1), type(data2)) try: self.failUnlessEqual(data1, data2) - except AssertionError: + except (AssertionError, TypeError): if isinstance(data1, dict): data1 = [(repr(key), value) for key, value in data1.items()] data1.sort() @@ -274,6 +288,10 @@ class TestConstructorTypes(test_appliance.TestAppliance): if (item1 != item1 or (item1 == 0.0 and item1 == 1.0)) and \ (item2 != item2 or (item2 == 0.0 and item2 == 1.0)): continue + if isinstance(item1, datetime.datetime): + item1 = item1.utctimetuple() + if isinstance(item2, datetime.datetime): + item2 = item2.utctimetuple() self.failUnlessEqual(item1, item2) else: raise diff --git a/tests/test_representer.py b/tests/test_representer.py index 8097733..961b477 100644 --- a/tests/test_representer.py +++ b/tests/test_representer.py @@ -19,7 +19,7 @@ class TestRepresenterTypes(test_appliance.TestAppliance): self.failUnlessEqual(type(data1), type(data2)) try: self.failUnlessEqual(data1, data2) - except AssertionError: + except (AssertionError, TypeError): if isinstance(data1, dict): data1 = [(repr(key), value) for key, value in data1.items()] data1.sort() @@ -36,6 +36,10 @@ class TestRepresenterTypes(test_appliance.TestAppliance): if (item1 != item1 or (item1 == 0.0 and item1 == 1.0)) and \ (item2 != item2 or (item2 == 0.0 and item2 == 1.0)): continue + if isinstance(item1, datetime.datetime): + item1 = item1.utctimetuple() + if isinstance(item2, datetime.datetime): + item2 = item2.utctimetuple() self.failUnlessEqual(item1, item2) else: raise -- 2.11.4.GIT