+class TimeZone(tzinfo, Repr):
+ def __init__(self, tzstring):
+ m = re.match(r'^([+-])(\d{2}):?(\d{2})$', tzstring)
+ if not m:
+ raise DateException(tzstring, 'time zone')
+ sign = int(m.group(1) + '1')
+ try:
+ self.__offset = timedelta(hours = sign*int(m.group(2)),
+ minutes = sign*int(m.group(3)))
+ except OverflowError:
+ raise DateException(tzstring, 'time zone')
+ self.__name = tzstring
+ def utcoffset(self, dt):
+ return self.__offset
+ def tzname(self, dt):
+ return self.__name
+ def dst(self, dt):
+ return timedelta(0)
+ def __str__(self):
+ return self.__name
+
+class Date(Repr):
+ """Immutable."""
+ def __init__(self, datestring):
+ # Try git-formatted date.
+ m = re.match(r'^(\d+)\s+([+-]\d\d:?\d\d)$', datestring)
+ if m:
+ try:
+ self.__time = datetime.fromtimestamp(int(m.group(1)),
+ TimeZone(m.group(2)))
+ except ValueError:
+ raise DateException(datestring, 'date')
+ return
+
+ # Try iso-formatted date.
+ m = re.match(r'^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})\s+'
+ + r'([+-]\d\d:?\d\d)$', datestring)
+ if m:
+ try:
+ self.__time = datetime(
+ *[int(m.group(i + 1)) for i in xrange(6)],
+ **{'tzinfo': TimeZone(m.group(7))})
+ except ValueError:
+ raise DateException(datestring, 'date')
+ return
+
+ raise DateException(datestring, 'date')
+ def __str__(self):
+ return self.isoformat()
+ def isoformat(self):
+ """Human-friendly ISO 8601 format."""
+ return '%s %s' % (self.__time.replace(tzinfo = None).isoformat(' '),
+ self.__time.tzinfo)
+ @classmethod
+ def maybe(cls, datestring):
+ if datestring in [None, NoValue]:
+ return datestring
+ return cls(datestring)
+