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 _______________________________________________ commit-gnue mailing list commit-gnue@gnu.org http://lists.gnu.org/mailman/listinfo/commit-gnue