embedded/mx/DateTime/ISO.py
changeset 0 b97547f5f1fa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/embedded/mx/DateTime/ISO.py	Wed Nov 05 15:52:50 2008 +0100
@@ -0,0 +1,366 @@
+""" This module provides a set of constructors and routines to convert
+    between DateTime[Delta] instances and ISO representations of date
+    and time.
+
+    Note: Timezones are only interpreted by ParseDateTimeGMT(). All
+    other constructors silently ignore the time zone information.
+
+    Copyright (c) 1998-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
+    Copyright (c) 2000-2007, eGenix.com Software GmbH; mailto:info@egenix.com
+    See the documentation for further information on copyrights,
+    or contact the author.
+
+"""
+import DateTime,Timezone
+import re,string
+
+# Grammar: ISO 8601 (not all, but what we need from it)
+_year = '(?P<year>\d?\d\d\d)'
+_month = '(?P<month>\d?\d)'
+_day = '(?P<day>\d?\d)'
+_hour = '(?P<hour>\d?\d)'
+_minute = '(?P<minute>\d?\d)'
+_second = '(?P<second>\d?\d(?:\.\d+)?)'
+_sign = '(?P<sign>[-+])'
+_week = 'W(?P<week>\d?\d)'
+_zone = Timezone.isozone
+
+_weekdate = _year + '-?(?:' + _week + '-?' + _day + '?)?'
+_date = _year + '-?' + '(?:' + _month + '-?' + _day + '?)?'
+_time = _hour + ':?' + _minute + ':?' + _second + '?(?:' + _zone + ')?'
+
+isodatetimeRE = re.compile(_date + '(?:[ T]' + _time + ')?$')
+isodateRE = re.compile(_date + '$')
+isotimeRE = re.compile(_time + '$')
+isodeltaRE = re.compile(_sign + '?' + _time + '$')
+isoweekRE = re.compile(_weekdate + '$')
+isoweektimeRE = re.compile(_weekdate + '(?:[ T]' + _time + ')?$')
+
+def WeekTime(year,isoweek=1,isoday=1,hour=0,minute=0,second=0.0):
+
+    """Week(year,isoweek=1,isoday=1,hour=0,minute=0,second=0.0)
+
+       Returns a DateTime instance pointing to the given ISO week and
+       day.  isoday defaults to 1, which corresponds to Monday in the
+       ISO numbering. The time part is set as given.
+
+    """
+    d = DateTime.DateTime(year,1,1,hour,minute,second)
+    if d.iso_week[0] == year:
+        # 1.1. belongs to year (backup to Monday)
+        return d + (-d.day_of_week + 7 * (isoweek-1) + isoday-1)
+    else:
+        # 1.1. belongs to year-1 (advance to next Monday)
+        return d + (7-d.day_of_week + 7 * (isoweek-1) + isoday-1)
+
+# Alias
+Week = WeekTime
+
+# Aliases for the other constructors (they all happen to already use
+# ISO format)
+Date = DateTime.Date
+Time = DateTime.Time
+TimeDelta = DateTime.TimeDelta
+
+def ParseDateTime(isostring,parse_isodatetime=isodatetimeRE.match,
+
+                  strip=string.strip,atoi=string.atoi,atof=string.atof):
+
+    """ParseDateTime(isostring)
+
+       Returns a DateTime instance reflecting the given ISO date. A
+       time part is optional and must be delimited from the date by a
+       space or 'T'.
+
+       Time zone information is parsed, but not evaluated.
+
+    """
+    s = strip(isostring)
+    date = parse_isodatetime(s)
+    if not date:
+        raise ValueError,'wrong format, use YYYY-MM-DD HH:MM:SS'
+    year,month,day,hour,minute,second,zone = date.groups()
+    year = atoi(year)
+    if month is None:
+        month = 1
+    else:
+        month = atoi(month)
+    if day is None:
+        day = 1
+    else:
+        day = atoi(day)
+    if hour is None:
+        hour = 0
+    else:
+        hour = atoi(hour)
+    if minute is None:
+        minute = 0
+    else:
+        minute = atoi(minute)
+    if second is None:
+        second = 0.0
+    else:
+        second = atof(second)
+    return DateTime.DateTime(year,month,day,hour,minute,second)
+
+def ParseDateTimeGMT(isostring,parse_isodatetime=isodatetimeRE.match,
+
+                     strip=string.strip,atoi=string.atoi,atof=string.atof):
+
+    """ParseDateTimeGMT(isostring)
+
+       Returns a DateTime instance in UTC reflecting the given ISO
+       date. A time part is optional and must be delimited from the
+       date by a space or 'T'. Timezones are honored.
+
+    """
+    s = strip(isostring)
+    date = parse_isodatetime(s)
+    if not date:
+        raise ValueError,'wrong format, use YYYY-MM-DD HH:MM:SS'
+    year,month,day,hour,minute,second,zone = date.groups()
+    year = atoi(year)
+    if month is None:
+        month = 1
+    else:
+        month = atoi(month)
+    if day is None:
+        day = 1
+    else:
+        day = atoi(day)
+    if hour is None:
+        hour = 0
+    else:
+        hour = atoi(hour)
+    if minute is None:
+        minute = 0
+    else:
+        minute = atoi(minute)
+    if second is None:
+        second = 0.0
+    else:
+        second = atof(second)
+    offset = Timezone.utc_offset(zone)
+    return DateTime.DateTime(year,month,day,hour,minute,second) - offset
+
+# Alias
+ParseDateTimeUTC = ParseDateTimeGMT
+
+def ParseDate(isostring,parse_isodate=isodateRE.match,
+
+              strip=string.strip,atoi=string.atoi,atof=string.atof):
+
+    """ParseDate(isostring)
+
+       Returns a DateTime instance reflecting the given ISO date. A
+       time part may not be included.
+
+    """
+    s = strip(isostring)
+    date = parse_isodate(s)
+    if not date:
+        raise ValueError,'wrong format, use YYYY-MM-DD'
+    year,month,day = date.groups()
+    year = atoi(year)
+    if month is None:
+        month = 1
+    else:
+        month = atoi(month)
+    if day is None:
+        day = 1
+    else:
+        day = atoi(day)
+    return DateTime.DateTime(year,month,day)
+
+def ParseWeek(isostring,parse_isoweek=isoweekRE.match,
+
+              strip=string.strip,atoi=string.atoi,atof=string.atof):
+
+    """ParseWeek(isostring)
+
+       Returns a DateTime instance reflecting the given ISO date. A
+       time part may not be included.
+
+    """
+    s = strip(isostring)
+    date = parse_isoweek(s)
+    if not date:
+        raise ValueError,'wrong format, use yyyy-Www-d, e.g. 1998-W01-1'
+    year,week,day = date.groups()
+    year = atoi(year)
+    if week is None:
+        week = 1
+    else:
+        week = atoi(week)
+    if day is None:
+        day = 1
+    else:
+        day = atoi(day)
+    return Week(year,week,day)
+
+def ParseWeekTime(isostring,parse_isoweektime=isoweektimeRE.match,
+
+                  strip=string.strip,atoi=string.atoi,atof=string.atof):
+
+    """ParseWeekTime(isostring)
+
+       Returns a DateTime instance reflecting the given ISO date. A
+       time part is optional and must be delimited from the date by a
+       space or 'T'.
+
+    """
+    s = strip(isostring)
+    date = parse_isoweektime(s)
+    if not date:
+        raise ValueError,'wrong format, use e.g. "1998-W01-1 12:00:30"'
+    year,week,day,hour,minute,second,zone = date.groups()
+    year = atoi(year)
+    if week is None:
+        week = 1
+    else:
+        week = atoi(week)
+    if day is None:
+        day = 1
+    else:
+        day = atoi(day)
+    if hour is None:
+        hour = 0
+    else:
+        hour = atoi(hour)
+    if minute is None:
+        minute = 0
+    else:
+        minute = atoi(minute)
+    if second is None:
+        second = 0.0
+    else:
+        second = atof(second)
+    return WeekTime(year,week,day,hour,minute,second)
+
+def ParseTime(isostring,parse_isotime=isotimeRE.match,
+
+              strip=string.strip,atoi=string.atoi,atof=string.atof):
+
+    """ParseTime(isostring)
+
+       Returns a DateTimeDelta instance reflecting the given ISO time.
+       Hours and minutes must be given, seconds are
+       optional. Fractions of a second may also be used,
+       e.g. 12:23:12.34.
+
+    """
+    s = strip(isostring)
+    time = parse_isotime(s)
+    if not time:
+        raise ValueError,'wrong format, use HH:MM:SS'
+    hour,minute,second,zone = time.groups()
+    hour = atoi(hour)
+    minute = atoi(minute)
+    if second is not None:
+        second = atof(second)
+    else:
+        second = 0.0
+    return DateTime.TimeDelta(hour,minute,second)
+
+def ParseTimeDelta(isostring,parse_isodelta=isodeltaRE.match,
+
+                   strip=string.strip,atoi=string.atoi,atof=string.atof):
+
+    """ParseTimeDelta(isostring)
+
+       Returns a DateTimeDelta instance reflecting the given ISO time
+       as delta. Hours and minutes must be given, seconds are
+       optional. Fractions of a second may also be used,
+       e.g. 12:23:12.34. In addition to the ISO standard a sign may be
+       prepended to the time, e.g. -12:34.
+
+    """
+    s = strip(isostring)
+    time = parse_isodelta(s)
+    if not time:
+        raise ValueError,'wrong format, use [-]HH:MM:SS'
+    sign,hour,minute,second,zone = time.groups()
+    hour = atoi(hour)
+    minute = atoi(minute)
+    if second is not None:
+        second = atof(second)
+    else:
+        second = 0.0
+    if sign and sign == '-':
+        return -DateTime.TimeDelta(hour,minute,second)
+    else:
+        return DateTime.TimeDelta(hour,minute,second)
+
+def ParseAny(isostring):
+
+    """ParseAny(isostring)
+
+       Parses the given string and tries to convert it to a
+       DateTime[Delta] instance.
+
+    """
+    try:
+        return ParseDateTime(isostring)
+    except ValueError:
+        pass
+    try:
+        return ParseWeekTime(isostring)
+    except ValueError:
+        pass
+    try:
+        return ParseTimeDelta(isostring)
+    except ValueError:
+        raise ValueError,'unsupported format: "%s"' % isostring
+
+def str(datetime,tz=None):
+
+    """str(datetime,tz=DateTime.tz_offset(datetime))
+
+       Returns the datetime instance as ISO date string. tz can be
+       given as DateTimeDelta instance providing the time zone
+       difference from datetime's zone to UTC. It defaults to
+       DateTime.tz_offset(datetime) which assumes local time.
+
+    """
+    if tz is None:
+        tz = datetime.gmtoffset()
+    return '%04i-%02i-%02i %02i:%02i:%02i%+03i%02i' % (
+        datetime.year, datetime.month, datetime.day, 
+        datetime.hour, datetime.minute, datetime.second,
+        tz.hour,tz.minute)
+
+def strGMT(datetime):
+
+    """strGMT(datetime)
+
+       Returns the datetime instance as ISO date string assuming it is
+       given in GMT.
+
+    """
+    return '%04i-%02i-%02i %02i:%02i:%02i+0000' % (
+        datetime.year, datetime.month, datetime.day, 
+        datetime.hour, datetime.minute, datetime.second)
+
+def strUTC(datetime):
+
+    """strUTC(datetime)
+
+       Returns the datetime instance as ISO date string assuming it is
+       given in UTC.
+
+    """
+    return '%04i-%02i-%02i %02i:%02i:%02i+0000' % (
+        datetime.year, datetime.month, datetime.day, 
+        datetime.hour, datetime.minute, datetime.second)
+
+# Testing
+if __name__ == '__main__':
+    e = DateTime.Date(1900,1,1)
+    for i in range(100000):
+        d = e + i
+        year,week,day = d.iso_week
+        c = WeekTime(year,week,day)
+        if d != c:
+            print ' Check %s (given; %i) != %s (parsed)' % (d,d.day_of_week,c)
+        elif i % 1000 == 0:
+            print d,'ok'