[gnue] r9884 - in trunk/gnue-common/src: . lib utils

[email protected]
Newsgroups gmane.comp.cms.gnue.cvs
Message-ID <[email protected]>
Author: reinhard
Date: 2008-07-10 15:00:43 -0500 (Thu, 10 Jul 2008)
New Revision: 9884

Added:
   trunk/gnue-common/src/lib/
   trunk/gnue-common/src/lib/__init__.py
   trunk/gnue-common/src/lib/iso8601.py
Modified:
   trunk/gnue-common/src/utils/GDateTime.py
Log:
Added new "lib" package for code not specific to GNUe, and moved a cleaned-up version of the ISO date/time parsing 
routines there.



Property changes on: trunk/gnue-common/src/lib
___________________________________________________________________
Name: svn:ignore
   + *.pyc


Added: trunk/gnue-common/src/lib/__init__.py
===================================================================
--- trunk/gnue-common/src/lib/__init__.py	                        (rev 0)
+++ trunk/gnue-common/src/lib/__init__.py	2008-07-10 20:00:43 UTC (rev 9884)
@@ -0,0 +1,31 @@
+# GNU Enterprise Common Library - Gereral Library
+#
+# Copyright 2001-2008 Free Software Foundation
+#
+# This file is part of GNU Enterprise
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# $Id: __init__.py 9850 2008-01-03 17:21:25Z jcater $
+
+"""
+The lib package contains a number of modules providing very basic functions
+also usable independently of GNU Enterprise.
+
+None of the modules depends on any other module or on any aspect of the GNU
+Enterprise infrastructure. Each of them could be seen as a potential extension
+of the Python standard library.
+"""

Copied: trunk/gnue-common/src/lib/iso8601.py (from rev 9881, trunk/gnue-common/src/utils/GDateTime.py)
===================================================================
--- trunk/gnue-common/src/lib/iso8601.py	                        (rev 0)
+++ trunk/gnue-common/src/lib/iso8601.py	2008-07-10 20:00:43 UTC (rev 9884)
@@ -0,0 +1,508 @@
+# GNU Enterprise Common Library - ISO 8601 Parser
+#
+# Copyright 2001-2008 Free Software Foundation
+#
+# This file is part of GNU Enterprise
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise is distributed in the hope that it will be
+# useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# $Id$
+
+"""
+Functions to parse ISO 8601 conformant date and time strings.
+
+This module extends the standard Python datetime module.
+"""
+
+import calendar
+import datetime
+import re
+
+__all__ = ['parse_iso', 'date_parse_iso', 'time_parse_iso', 'date_fromisoweek',
+        'date_fromdaynumber']
+
+
+# =============================================================================
+# Implementation of tzinfo with a fixed offset to UTC
+# =============================================================================
+
+class Timezone(datetime.tzinfo):
+    """
+    Timezone defined through an offset from UTC.
+
+    This class implements the abstract datetime.tzinfo class for a timezone
+    defined through a fixed offset to UTC.  Instances of this class yield a DST
+    difference of 0, and a name like "-02:00".
+    """
+
+    # -------------------------------------------------------------------------
+    # Constructor
+    # -------------------------------------------------------------------------
+
+    def __init__(self, offset = 0):
+
+        datetime.tzinfo.__init__(self)
+        self.__offset = datetime.timedelta(minutes = offset)
+
+
+    # -------------------------------------------------------------------------
+    # Return the offset from UTC
+    # -------------------------------------------------------------------------
+
+    def utcoffset(self, dt):
+        """
+        Implementation of the abstract "utcoffset" method of the base class.
+
+        Simply returns the offset given at initialization.
+        """
+
+        return self.__offset
+
+
+    # -------------------------------------------------------------------------
+    # Return the name of the timezone
+    # -------------------------------------------------------------------------
+
+    def tzname(self, dt):
+        """
+        Implementation of the abstract "tzname" method of the base class.
+
+        This implementation returns a name based on the UTC offset like
+        "+0200".
+        """
+
+        if self.__offset:
+            minutes = self.__offset.seconds / 60 + self.__offset.days * 1440
+            if minutes > 0:
+                mm = minutes % 60
+                hh = (minutes - mm) / 60
+            else:
+                mm = -minutes % 60
+                hh = -((-minutes - mm) / 60)
+            return "%+03d:%02d" % (hh, mm)
+        else:
+            return "Z"
+
+
+    # -------------------------------------------------------------------------
+    # Return the daylight saving time adjustment
+    # -------------------------------------------------------------------------
+
+    def dst(self, dt):
+        """
+        Implementation of the abstract "dst" method of the base class.
+
+        This implementation returns a zero DST offset.
+        """
+
+        return datetime.timedelta(0)
+
+
+# =============================================================================
+# Parsing routines
+# =============================================================================
+
+# -----------------------------------------------------------------------------
+# Parse an arbitary ISO 8601 conformant date/time representation
+# -----------------------------------------------------------------------------
+
+def parse_iso(isostring):
+    """
+    Parse a string containing any ISO 8601 conformant representation of a date
+    and/or time.
+
+    @param isostring: string to be parsed
+    @type isostring: string
+    @return: value of the date and/or time
+    @rtype: datetime.date or datetime.time or datetime.datetime
+
+    @raises ValueError: The datestring parameter is no ISO 8601 conformant
+        representation of a valid date and/or time.
+    """
+
+    s = isostring.strip()
+    match = re.match('^(.*?)[ T](.*)$', s)
+    if match is not None:
+        (datepart, timepart) = match.groups()
+        date = date_parse_iso(datepart)
+        time = time_parse_iso(timepart)
+        return datetime.datetime.combine(date, time)
+    else:
+        # This doesn't look very elegant, but it probably is the most
+        # performant way to find out whether we have a date or a time.
+        try:
+            return time_parse_iso(s)
+        except ValueError:
+            return date_parse_iso(s)
+
+
+# -----------------------------------------------------------------------------
+# Parse an ISO 8601 conformant date representation
+# -----------------------------------------------------------------------------
+
+def date_parse_iso(datestring):
+    """
+    Parse a string containing any ISO 8601 conformant representation of a date.
+
+    Basic formats:
+        * yyyymmdd (year, month, day)
+        * yyyymm (assumes first day of the month)
+        * yyyyWwwd (year, week number, day of week)
+        * yyyyWww (assumes first day of the week, that is Monday)
+        * yyyyddd (year, ordinal day in year)
+
+    Extended formats:
+        * yyyy-mm-dd (year, month, day)
+        * yyyy-mm (assumes first day of the month)
+        * yyyy-Www-d (year, week number, day of week)
+        * yyyy-Www (assumes first day of the week, that is Monday)
+        * yyyy-ddd (year, ordinal day in year)
+
+    The extended formats tolerate missing leading zeroes.
+
+    @param datestring: string to be parsed
+    @type datestring: string
+    @return: value of the date
+    @rtype: datetime.date
+
+    @raises ValueError: The datestring parameter is no ISO 8601 conformant
+        representation of a valid date.
+    """
+
+    # This could certainly be done with regular expressions, too, but doing a
+    # re.match().groups() takes around 5 times more time than doing a split()
+    if "-" in datestring:
+        parts = datestring.split('-')
+
+        if len(parts) > 3:
+            raise ValueError("invalid date format: %s" % datestring)
+
+        year = int(parts[0])
+
+        if parts[1][0] == 'W':
+            week = int(parts[1][1:])
+            if len(parts) == 3:
+                # yyyy-Www-d
+                day = int(parts[2])
+            else:
+                # yyyy-Www
+                day = 1
+            return date_fromisoweek(year, week, day)
+
+        if len(parts) == 2 and len(parts[1]) == 3:
+            # yyyy-ddd
+            return date_fromdaynumber(year, int(parts[1]))
+
+        month = int(parts[1])
+        if len(parts) == 3:
+            # yyyy-mm-dd
+            day = int(parts[2])
+        else:
+            # yyyy-mm
+            day = 1
+
+        return datetime.date(year, month, day)
+
+    if datestring[4] == 'W':
+        if len(datestring) < 7 or len(datestring) > 8:
+            raise ValueError("invalid date format: %s" % datestring)
+
+        year = int(datestring[:4])
+        week = int(datestring[5:7])
+
+        if len(datestring) == 8:
+            # yyyyWwwd
+            day = int(datestring[7])
+        else:
+            # yyyyWww
+            day = 1
+
+        return date_fromisoweek(year, week, day)
+
+    if len(datestring) == 6:
+        # yyyymm
+        return datetime.date(int(datestring[:4]), int(datestring[4:6]), 1)
+
+    if len(datestring) == 7:
+        # yyyyddd
+        return date_fromdaynumber(int(datestring[:4]), int(datestring[4:7]))
+
+    if len(datestring) == 8:
+        # yyyymmdd
+        return datetime.date(int(datestring[:4]), int(datestring[4:6]),
+            int(datestring[6:8]))
+
+    raise ValueError("invalid date format: %s" % datestring)
+
+
+# -----------------------------------------------------------------------------
+# Parse an ISO 8601 conformant time representation
+# -----------------------------------------------------------------------------
+
+def time_parse_iso(timestring):
+    """
+    Parse a string containing any ISO 8601 conformant representation of a time.
+
+    Basic formats:
+        * hhmm (hour, minute)
+        * hhmmss (hour, minute, seconds)
+        * hhmmss.fff (arbitary number of fractional digits)
+        * hhmmss,fff (arbitary number of fractional digits)
+
+    Extended formats:
+        * hh:mm (hour, minute)
+        * hh:mm:ss (hour, minute, seconds)
+        * hh:mm:ss.fff (arbitary number of fractional digits)
+        * hh:mm:ss,fff (arbitary number of fractional digits)
+
+    The extended formats tolerate missing leading zeroes.
+
+    To each of the formats, one of the following timezone indicators can be
+    appended:
+        * Z (for UTC)
+        * +hh or -hh
+        * +hhmm or -hhmm
+        * +hh:mm or -hh:mm
+
+    If a timezone indicator is used, the function yields a timezone aware time
+    object, otherwise a naive time object.
+
+    @param timestring: string to be parsed
+    @type timestring: string
+    @return: value of the time
+    @rtype: datetime.time
+
+    @raises ValueError: The timestring parameter is no ISO 8601 conformant
+        representation of a valid time.
+    """
+
+    # Split into time and timezone.
+    match = re.match('^(.*?)(?:([Z+-])(.*)){0,1}$', timestring)
+    if match is None:
+        raise ValueError("invalid time format: %s" % timestring)
+    (timepart, zone, offset) = match.groups()
+
+    # Part 1: parse time without timezone
+
+    if ':' in timepart:
+        parts = timepart.split(':')
+
+        if len(parts) > 3:
+            raise ValueError("invalid time format: %s" % timestring)
+
+        hour = int(parts[0])
+        minute = int(parts[1])
+
+        if len(parts) == 3:
+            # hh:mm:ss or hh:mm:ss.ffffff or hh:mm:ss,ffffff
+            (second, micro) = divmod(float(parts[2].replace(',', '.')), 1)
+            second = int(second)
+            micro = int(round(micro * 1000000))
+        else:
+            # hh:mm
+            second = 0
+            micro = 0
+
+    else:
+        if len(timepart) < 4 or len(timepart) == 5:
+            raise ValueError("invalid time format: %s" % timestring)
+
+        hour = int(timepart[:2])
+        minute = int(timepart[2:4])
+
+        if len(timepart) == 4:
+            # hhmm
+            second = 0
+            micro = 0
+        else:
+            second = int(timepart[4:6])
+
+            if len(timepart) == 6:
+                # hhmmss
+                micro = 0
+            else:
+                # hhmmss.ffffff or hhmmss,ffffff
+                if not timepart[6] in '.,':
+                    raise ValueError("invalid time format: %s" % timestring)
+                micro = int(round(float('.' + timepart[7:]) * 1000000))
+
+    # Part 2: parse timezone
+
+    if zone is None and not offset:
+        # No timezone given
+        tzinfo = None
+
+    elif zone == 'Z' and not offset:
+        # UTC time given
+        tzinfo = Timezone(0)
+
+    # otherwise if a timezone offset is defined, transform it into a tzinfo
+    # instance
+    elif offset:
+        match = re.match('^(\d\d):?(\d\d)?$', offset)
+        if match is None:
+            raise ValueError("invalid timezone format: %s" % timestring)
+
+        parts = match.groups()
+
+        zhour = int(parts[0])
+        if parts[1] is None:
+            zminute = 0
+        else:
+            zminute = int(parts[1])
+
+        if zone == '+':
+            tzinfo = Timezone(zhour * 60 + zminute)
+        elif zone == '-':
+            tzinfo = Timezone(- zhour * 60 - zminute)
+
+    else:
+        raise ValueError("invalid timezone format: %s" % timestring)
+
+    return datetime.time(hour, minute, second, micro, tzinfo)
+
+
+# =============================================================================
+# New datetime.date constructors
+# =============================================================================
+
+# -----------------------------------------------------------------------------
+# Create a date from year, week number, and day in the week
+# -----------------------------------------------------------------------------
+
+def date_fromisoweek(year, week, day):
+    """
+    Create a date from year, week number, and day in week.
+
+    @param year: year
+    @type year: int
+    @param week: week number
+    @type week: int
+    @param day: day in week (1 = Monday, 7 = Sunday)
+    @type day: int
+    @return: resulting date
+    @rtype: datetime.date
+
+    @raises TypeError: One of the parameters is not an integer.
+    @raises ValueError: The year or the day number is invalid.
+    """
+
+    if week < 1 or week > 53:
+        raise ValueError('week must be in 1..53')
+    if week == 53 and datetime.date(year, 12, 28).isocalendar()[1] == 52:
+        raise ValueError('this year does not have 53 weeks')
+    if day < 1 or day > 7:
+        raise ValueError('day must be in 1..7')
+
+    # First create a date for the first day in the given year ...
+    firstday = datetime.date(year, 1, 1)
+
+    # ... and calculate an offset depending on wether or not January 1 is
+    # in this year's week 1, or in last year's week 52 or 53.
+    if firstday.isocalendar()[0] == year:
+        offset = 7 * (week - 1) + day - firstday.isoweekday()
+    else:
+        offset = 7 * week + day - firstday.isoweekday()
+
+    return firstday + datetime.timedelta(offset)
+
+
+# -----------------------------------------------------------------------------
+# Create a date from year and the day number within year
+# -----------------------------------------------------------------------------
+
+def date_fromdaynumber(year, day):
+    """
+    Create a date from year and day number within year.
+
+    @param year: year
+    @type year: int
+    @param day: day in year (1 = January 1)
+    @type day: int
+    @return: resulting date
+    @rtype: datetime.date
+
+    @raises TypeError: One of the parameters is not an integer.
+    @raises ValueError: The year number is not within 1..9999 or the day number
+        is not within 1..366 or the day number is 366 but the year is not a
+        leap year.
+    """
+
+    if day < 1 or day > 366:
+        raise ValueError('day must be in 1..366')
+    if day == 366 and not calendar.isleap(year):
+        raise ValueError('this year does not have 366 days')
+
+    return datetime.date(year, 1, 1) + datetime.timedelta(day - 1)
+
+
+# =============================================================================
+# Module self test code
+# =============================================================================
+
+if __name__ == '__main__':
+
+    # -------------------------------------------------------------------------
+    # Timezone class
+    # -------------------------------------------------------------------------
+
+    def __test_timezone():
+        now = datetime.datetime.now()
+        tz = Timezone(140)
+        print tz.utcoffset(now)
+        print tz.tzname(now)
+        tz = Timezone(-140)
+        print tz.utcoffset(now)
+        print tz.tzname(now)
+
+    # -------------------------------------------------------------------------
+    # parse function
+    # -------------------------------------------------------------------------
+
+    def __test_parse():
+        def __check(value):
+            try:
+                print "%-20s: %s" % (value, parse_iso(value).isoformat())
+            except ValueError, e:
+                print "%-20s: %s: %s" % (value, type(e).__name__, e)
+
+        __check('14230301')               # 1st March 1423
+        __check('1423-03-01')
+        __check('1981-04-05')
+        __check('14:23:21.233')
+        __check('19810405 17:23:15')
+        __check('1981-W14-7 17:23:15')
+        __check('1981-W14')
+        __check('1981W147')
+        __check('1981W14')
+        __check('2005-W01-1')
+        __check('2005-W53-6')
+        __check('1981-095 121314Z')
+        __check('1321')
+        __check('132123.5')
+        __check('132123.3+0312')
+        __check('1321-12:34')
+        __check('2005-07-27 23:59+00')
+        __check('6:2:1.233')
+        __check('2004-366')
+        __check('2005-366')
+
+    # -------------------------------------------------------------------------
+    # Call all tests
+    # -------------------------------------------------------------------------
+
+    __test_timezone()
+    __test_parse()

Modified: trunk/gnue-common/src/utils/GDateTime.py
===================================================================
--- trunk/gnue-common/src/utils/GDateTime.py	2008-07-10 12:16:34 UTC (rev 9883)
+++ trunk/gnue-common/src/utils/GDateTime.py	2008-07-10 20:00:43 UTC (rev 9884)
@@ -21,403 +21,14 @@
 #
 # $Id$
 
-import datetime
-import calendar
-import re
+"""
+Deprecated module. Use gnue.common.lib.iso8601 instead.
+"""
 
-from gnue.common.apps import errors
+# TODO: Deprecate with 0.8, remove with 0.9
 
+from gnue.common.lib import iso8601
 
-# =============================================================================
-# Exceptions
-# =============================================================================
-
-class InvalidDateError (errors.ApplicationError):
-  def __init__ (self, datestring):
-    msg = "'%s' is not valid literal for a date" % datestring
-    errors.ApplicationError.__init__ (self, msg)
-
-class InvalidTimeError (errors.ApplicationError):
-  def __init__ (self, timestring):
-    msg = "'%s' is not valid literal for a time" % timestring
-    errors.ApplicationError.__init__ (self, msg)
-
-class NotSupportedError (errors.ApplicationError):
-  pass
-
-# =============================================================================
-# tzinfo implementation for timezones with a fixed offset
-# =============================================================================
-
-class FixedOffsetZone (datetime.tzinfo):
-
-  # ---------------------------------------------------------------------------
-  # Create a new tzinfo instance with the given offset and an optional name
-  # ---------------------------------------------------------------------------
-
-  def __init__ (self, offset = 0, name = None):
-
-    self.__offset = datetime.timedelta (minutes = offset)
-    self.__name   = name
-
-    # A timezone with a zero offset is always 'UTC'
-    if not offset and name is None:
-      self.__name = 'UTC'
-
-  # ---------------------------------------------------------------------------
-  # return the offset of this timezone to UTC
-  # ---------------------------------------------------------------------------
-
-  def utcoffset (self, dt):
-    """
-    Return offset of local time from UTC, in minutes east of UTC. If local time
-    is west of UTC, this should be negative.
-    """
-    return self.__offset
-
-
-  # ---------------------------------------------------------------------------
-  # Return the name of the timezone
-  # ---------------------------------------------------------------------------
-
-  def tzname (self, dt):
-    return self.__name
-
-
-  # ---------------------------------------------------------------------------
-  # Return the daylight saving time adjustment 
-  # ---------------------------------------------------------------------------
-
-  def dst (self, dt):
-    """
-    Return the daylight saving time (DST) adjustment, in minutes east of UTC,
-    or None if DST information isn't known.
-    """
-    return datetime.timedelta (0)
-
-
-# =============================================================================
-# Parse a string in ISO 8601 format into a date-, time- or datetime instance
-# =============================================================================
-
-def parseISO (isostring):
-  """
-  Parse a given string with an ISO-date, -time or -datetime and create an
-  apropriate datetime.* type. The ISO string migth be given in the extended
-  format (including separators) or the basic format as well.
-
-  @param isostring: string with the date, time or datetime to be parsed
-  @return: datetime.date/time/datetime instance representing the given string
-
-  @raises InvalidDateError: if the given string cannot be parsed
-  """
-
-  match = re.match ('^(.*?)[ T](.*)$', isostring.strip ())
-  if match is not None:
-    (datepart, timepart) = match.groups ()
-    date = parseISODate (datepart)
-    (time, date) = parseISOTime (timepart, date)
-    return datetime.datetime.combine (date, time)
-
-  else:
-    try:
-      return parseISOTime (isostring.strip ())
-
-    except InvalidTimeError:
-      return parseISODate (isostring.strip ())
-
-
-# =============================================================================
-# Parse a date given as ISO string into a datetime.date instance
-# =============================================================================
-
-def parseISODate (datestring):
-  """
-  Parse a date given as string in ISO 8601 format into a datetime.date
-  instance. The date might be given in the basic- or extended format.
-  Possible forms are: ordinal dates (1981-095, 1981095), weeks (1981-W14-7,
-  1981W147) or full dates (1981-05-03, 19810503). If a week is given without a
-  day of the week, Monday of that week will be used (see L{parseISOWeek}).
-  NOTE: datetime library does not support dates earlier than 1st January 1 AD
-
-  @param datestring: string conforming to ISO 8601 date format
-  @return: datetime.date instance with the given date
-
-  @raises NotSupportedError: if the given string represents a date before 1st
-    january 1 AD.
-  @raises InvalidDateError: if the given datestring cannot be transformed into
-    a datetime.date instance
-  """
-
-  result = None
-
-  if datestring [0] == '-':
-    raise NotSupportedError, \
-        u_("Dates before 0001/01/01 are not supported by datetime library")
-
-  if "W" in datestring:
-    result = parseISOWeek (datestring)
-
-  elif "-" in datestring:
-    parts = datestring.split ('-')
-    year  = int (parts [0])
-
-    if len (parts) == 2 and len (parts [1]) == 3:
-      result = parseOrdinal (year, int (parts [1]))
-    else:
-      month = int (parts [1])
-      day   = len (parts) > 2 and int (parts [2]) or 1
-
-      result = datetime.date (year, month, day)
-
-  elif len (datestring) == 7:
-    result = parseOrdinal (int (datestring [:4]), int (datestring [4:7]))
-
-  elif len (datestring) == 8:
-    parts = map (int, [datestring [:4], datestring [4:6], datestring [6:8]])
-    result = datetime.date (*parts)
-
-  if result is None:
-    raise InvalidDateError, datestring
-
-  return result
-
-
-# =============================================================================
-# Parse an ISO string representing a week
-# =============================================================================
-
-def parseISOWeek (weekstring):
-  """
-  Parses an ISO week string given as 'YYYY-Www-d' (extended) or 'YYYYWwwd'
-  (basic) into a datetime.date instance reflecting the given date. The day of
-  the week part is optional and defaults to Monday of the week if omitted. The
-  ISO week day is defined as 1 for Monday and 7 for Sunday.
-
-  @param weekstring: ISO string with the week to be parsed
-  @returns: datetime.date instance reflecting the given date
-  """
-
-  if '-' in weekstring:
-    parts = weekstring.split ('-')
-    parts [1] = parts [1][1:]
-  else:
-    parts = weekstring [:4], weekstring [5:7], weekstring [7:]
-
-  parts = map (int, filter (None, parts))
-  year, week, day = parts [0], parts [1], len (parts) > 2 and parts [2] or 1
-
-  # First create a date for the first day in the given year ...
-  january = datetime.date (year, 1, 1)
-
-  # ... and calculate an offset depending on wether the first january is within
-  # the same year or not
-  if january.isocalendar () [0] == year:
-    offset = -january.isoweekday () + 7 * (week - 1) + day
-
-  else:
-    offset = 7 - january.isoweekday () + 7 * (week - 1) + day
-
-  return january + datetime.timedelta (offset)
-
-
-# =============================================================================
-# Build a date instance for a given date within a given year
-# =============================================================================
-
-def parseOrdinal (year, day):
-  """
-  Return a datetime.date instance for the given date within the given year,
-  where day 1 is the first of January, day 32 is the first of February and so
-  on.
-
-  @param year: the year
-  @param day: the day within the year to create a datetime.date instance for
-  @return: datetime.date reflecting the requested day within the year
-  """
-
-  return datetime.date (year, 1, 1) + datetime.timedelta (day - 1)
-
-
-# =============================================================================
-# Parse a time given as ISO string into a datetime.time instance
-# =============================================================================
-
-def parseISOTime (timestring, date = None):
-  """
-  Return a datetime.time instance for the given string in ISO 8601 format. The
-  timestring could be given in basic- or extended format and might contain a
-  timezone. Examples: 14:30:21Z, 14:30:21+02:00, 13:20:12.1234-0130, 14.3+02
-
-  @param timestring: string in ISO 8601 format to parse
-  @param date: datetime.date instance for which the timestring should be
-    parsed. This argument is optional. If the time is '24:00:00' one day will
-    be added to this date.
-  @return: If not date arguemnt is given the result is a datetime.time
-    reflecting the given string. If a date is given, the result is a tuple
-    (datetime.time, datetime.date) reflecting the requested time and the given
-    date (optionally incremented by one day)
-
-  @raises InvalidTimeError: if the given timestring cannot be transformed into
-    a datetime.time instance
-  """
-
-  timezone = None
-
-  parts = re.match ('^(.*?)(?:([Z+-])(.*)){0,1}$', timestring)
-  if parts is None:
-    raise InvalidTimeError, timestring
-
-  (timepart, zone, offset) = parts.groups ()
-
-  # Make sure to have the timepart in basic format
-  if ':' in timepart:
-    items = timepart.split (':')
-    for (ix, i) in enumerate (items):
-      if '.' in i:
-        full, frac = i.split ('.')
-        items [ix] = "%02d.%s" % (int (full), frac)
-      else:
-        items [ix] = "%02d" % int (i)
-        
-    timepart = ''.join (items)
-
-  # Use UTC timezone if the string contains a Z (=Zulu time)
-  if zone == 'Z':
-    timezone = FixedOffsetZone (0, 'UTC')
-
-  # otherwise if a timezone offset is defined, transform it into a tzinfo
-  # instance
-  if offset:
-    zoneMatch = re.match ('(\d\d)?:?(\d\d)$', offset)
-    if zoneMatch is None:
-      raise InvalidTimeError, timestring
-
-    items = filter (None, zoneMatch.groups ())
-    zhour = int (items [0])
-    zmin  = len (items) > 1 and int (items [1]) or 0
-    mult  = zone == '-' and -1 or 1
-
-    timezone = FixedOffsetZone (mult * (zhour * 60 + zmin))
-
-  # If the timestring contains a fractional part (e.g. 14.3 which means 14
-  # hours and 20 minutes) split the string into the full and the fractional
-  # part
-  match = re.match ('^(\d+)(?:[\.,](\d+)){0,1}$', timepart)
-  if match is None:
-    raise InvalidTimeError, timepart
-
-  # The full part cannot contain more than 6 characters (=HHMMSS)
-  (full, fractions) = match.groups ()
-  if len (full) > 6:
-    raise InvalidTimeError, timestring
-
-  elements = []
-  while len (full):
-    elements.append (int (full [:2]))
-    full = full [2:]
-
-  # Get an apropriate factor for the given fractions, which is 60 for hours,
-  # and minutes, and 1000000 (microseconds) for seconds.
-  if fractions:
-    factor = len (elements) < 3 and 60 or 1000000
-    elements.append (int (float ("0.%s" % fractions) * factor))
-
-  # Finally make sure to have 4 elements (where missing items are 0)
-  while len (elements) < 4:
-    elements.append (0)
-
-  (hour, minute, second, micro) = elements
-  if hour > 24 or minute > 59 or second > 60:
-    raise InvalidTimeError, timestring
-
-  # 24 in an hour is only valid as 24:00:00
-  if hour == 24 and (minute + second + micro) != 0:
-    raise InvalidTimeError, timestring
-
-  # 24:00:00 is the same as 00:00:00 of the next day
-  if hour == 24:
-    hour = minute = second = micro = 0
-    if date is not None:
-      date += datetime.timedelta (days = 1)
-
-  result = datetime.time (hour, minute, second, micro, tzinfo = timezone)
-
-  if date is not None:
-    return (result, date)
-  else:
-    return result
-
-
-# 
-def isLeapYear (year):
-  return calendar.isleap (year)
-
-class InvalidDate (errors.UserError):
-  pass
-
-class GDateTime:
-  def __init__(self):
-    self.month = 0
-    self.day = 0
-    self.year = 0
-    self.hour = 0
-    self.minute = 0
-    self.second = 0
-
-  def __repr__(self):
-    return "%04d/%02d/%02d %02d:%02d:%02d" % \
-      (self.year, self.month, self.day, self.hour, self.minute, self.second)
-
-  def getDayOfWeek(self):
-    # from the Calendar FAQ (http://www.pauahtun.org/CalendarFAQ/)
-    # 0 = Sunday
-    a = int((14 - self.month) / 12)
-    y = self.year - a
-    m = self.month + 12*a - 2
-    return divmod(self.day + y + int(y/4) - int(y/100) + int(y/400) + (31*m)/12,7)[1]
-
-
-  def validate(self):
-    if not (\
-        self.month >= 1 and self.month <= 12 and \
-        self.year >= 0 and \
-        self.day >= 1 and self.day <= ( \
-             (self.month in (1,3,5,7,8,10,12) and 31) or \
-             (self.month == 2 and (28 + isLeapYear(self.year))) \
-             or 30) and \
-        self.hour >= 0 and self.hour <= 23 and \
-        self.minute >= 0 and self.minute <= 59 and \
-        self.second >= 0 and self.second <= 59 ):
-      raise InvalidDate, u_("Not a valid date")
-
-
-
-# =============================================================================
-# Module self test code
-# =============================================================================
-
-def check (value):
-  print "%-20s: %s" % (value, parseISO (value).isoformat ())
-
-if __name__ == '__main__':
-
-  check ('14230301')           # 1st March 1423
-  check ('1423-03-01')           # 1st March 1423
-  check ('1981-04-05')
-  check ('14:23:21.233')
-  check ('19810405 17:23:15')
-  check ('1981-W14-7 17:23:15')
-  check ('1981-W14')
-  check ('1981W147')
-  check ('1981W14')
-  check ('2005-W01-1')
-  check ('2005-W53-6')
-  check ('1981-095 121314Z')
-  check ('1321')
-  check ('1321.5')
-  check ('1321.3+0312')
-  check ('1321-12:34')
-  check ('2005-07-27 24:00+02')
-  check ('6:2:1.233')
-
+parseISO = iso8601.parse_iso
+parseISODate = iso8601.date_parse_iso
+parseISOTime = iso8601.time_parse_iso
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.