Commits

Kirill Simonov committed 0d0a0ce

Fix timestamp constructing and representing (close #25).

  • Participants
  • Parent commits f14733c

Comments (0)

Files changed (7)

File lib/yaml/constructor.py

 from error import *
 from nodes import *
 
-try:
-    import datetime
-    datetime_available = True
-except ImportError:
-    datetime_available = False
+import datetime
 
 try:
     set
                 (?P<hour>[0-9][0-9]?)
                 :(?P<minute>[0-9][0-9])
                 :(?P<second>[0-9][0-9])
-                (?:\.(?P<fraction>[0-9]*))?
-                (?:[ \t]*(?:Z|(?P<tz_hour>[-+][0-9][0-9]?)
-                (?::(?P<tz_minute>[0-9][0-9])?)?))?)?$''', re.X)
+                (?:(?P<fraction>\.[0-9]*))?
+                (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
+                (?::(?P<tz_minute>[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
         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',

File lib/yaml/representer.py

 from error import *
 from nodes import *
 
-try:
-    import datetime
-    datetime_available = True
-except ImportError:
-    datetime_available = False
+import datetime
 
 try:
     set
         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):
 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)

File tests/data/construct-timestamp.code

     "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),
 }

File tests/data/timestamp-bugs.code

+[
+    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")),
+]

File tests/data/timestamp-bugs.data

+- 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

File tests/test_constructor.py

 
 import test_appliance
-try:
-    import datetime
-except ImportError:
-    pass
+
+import datetime
 try:
     set
 except NameError:
     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
             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()
                         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

File tests/test_representer.py

             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()
                         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