isodate-0.4.6/.hg_archival.txt0000644000000000000000000000023411701464611014365 0ustar 00000000000000repo: b8b3628077e2d5fc513caa08b3e7aa1254fd79c4 node: 8e37a5ab1100234ff09d3bf8960616a2b2a1cd61 branch: default latesttag: RELEASE-0.4.6 latesttagdistance: 1 isodate-0.4.6/.hgignore0000644000000000000000000000016611701464611013106 0ustar 00000000000000syntax: glob *.pyc isodate.egg-info build/ .installed.cfg .pydevproject .settings bin develop-eggs dist parts MANIFESTisodate-0.4.6/.hgtags0000644000000000000000000000033411701464611012556 0ustar 00000000000000e6cd4b2e543e37ed278321374b63c41c3b4e9e68 RELEASE-0.3.0 010c27908bd8ba7b0c1564abc7840d962f700812 RELEASE-0.4.0 2b6b173b7feb80eb49e06c828d64e381aca17756 RELEASE-0.4.5 22cb3855ca1e55931d5c0a3cf7b070f2475963b7 RELEASE-0.4.6 isodate-0.4.6/.project0000644000000000000000000000055111701464611012750 0ustar 00000000000000 isodate org.python.pydev.PyDevBuilder org.python.pydev.pythonNature isodate-0.4.6/CHANGES.txt0000644000000000000000000000165511701464611013120 0ustar 00000000000000 CHANGES ======= 0.4.6 (2012-01-06) ------------------ - added Python 3 compatibility via 2to3 0.4.5 (2012-01-06) ------------------ - made setuptools dependency optional 0.4.4 (2011-04-16) ------------------ - Fixed formatting of microseconds for datetime objects 0.4.3 (2010-10-29) ------------------ - Fixed problem with %P formating and fractions (supplied by David Brooks) 0.4.2 (2010-10-28) ------------------ - Implemented unary - for Duration (supplied by David Brooks) - Output fractional seconds with '%P' format. (partly supplied by David Brooks) 0.4.1 (2010-10-13) ------------------ - fixed bug in comparison between timedelta and Duration. - fixed precision problem with microseconds (reported by Tommi Virtanen) 0.4.0 (2009-02-09) ------------------ - added method to parse ISO 8601 time zone strings - added methods to create ISO 8601 conforming strings 0.3.0 (2009-1-05) ------------------ - Initial release isodate-0.4.6/MANIFEST.in0000644000000000000000000000004511701464611013035 0ustar 00000000000000include CHANGES.txt include TODO.txt isodate-0.4.6/README.txt0000644000000000000000000001022011701464611012771 0ustar 00000000000000 ISO 8601 date/time parser ========================= This module implements ISO 8601 date, time and duration parsing. The implementation follows ISO8601:2004 standard, and implements only date/time representations mentioned in the standard. If something is not mentioned there, then it is treated as non existent, and not as an allowed option. For instance, ISO8601:2004 never mentions 2 digit years. So, it is not intended by this module to support 2 digit years. (while it may still be valid as ISO date, because it is not explicitly forbidden.) Another example is, when no time zone information is given for a time, then it should be interpreted as local time, and not UTC. As this module maps ISO 8601 dates/times to standard Python data types, like *date*, *time*, *datetime* and *timedelta*, it is not possible to convert all possible ISO 8601 dates/times. For instance, dates before 0001-01-01 are not allowed by the Python *date* and *datetime* classes. Additionally fractional seconds are limited to microseconds. That means if the parser finds for instance nanoseconds it will round it to microseconds. Documentation ------------- Currently there are four parsing methods available. * parse_time: parses an ISO 8601 time string into a *time* object * parse_date: parses an ISO 8601 date string into a *date* object * parse_datetime: parses an ISO 8601 date-time string into a *datetime* object * parse_duration: parses an ISO 8601 duration string into a *timedelta* or *Duration* object. * parse_tzinfo: parses the time zone info part of an ISO 8601 string into a *tzinfo* object. As ISO 8601 allows to define durations in years and months, and *timedelta* does not handle years and months, this module provides a *Duration* class, which can be used almost like a *timedelta* object (with some limitations). However, a *Duration* object can be converted into a *timedelta* object. There are also ISO formating methods for all supported data types. Each *xxx_isoformat* method accepts a format parameter. The default format is always the ISO 8601 expanded format. This is the same format used by *datetime.isoformat*: * time_isoformat: Intended to create ISO time strings with default format *hh:mm:ssZ*. * date_isoformat: Intended to create ISO date strings with default format *yyyy-mm-dd*. * datetime_isoformat: Intended to create ISO date-time strings with default format *yyyy-mm-ddThh:mm:ssZ*. * duration_isoformat: Intended to create ISO duration strings with default format *PnnYnnMnnDTnnHnnMnnS*. * tz_isoformat: Intended to create ISO time zone strings with default format *hh:mm*. * strftime: A re-implementation mostly compatible with Python's *strftime*, but supports only those format strings, which can also be used for dates prior 1900. This method also understands how to format *datetime* and *Duration* instances. Installation: ------------- This module can easily be installed with Python standard installation methods. Either use *python setup.py install* or in case you have *setuptools* or *distribute* available, you can also use *easy_install*. Limitations: ------------ * The parser accepts several date/time representation which should be invalid according to ISO 8601 standard. 1. for date and time together, this parser accepts a mixture of basic and extended format. e.g. the date could be in basic format, while the time is accepted in extended format. It also allows short dates and times in date-time strings. 2. For incomplete dates, the first day is chosen. e.g. 19th century results in a date of 1901-01-01. 3. negative *Duration* and *timedelta* value are not fully supported yet. Further information: -------------------- The doc strings and unit tests should provide rather detailed information about the methods and their limitations. The source release provides a *setup.py* script and a *buildout.cfg*. Both can be used to run the unit tests included. Source code is available at ``_. isodate-0.4.6/TODO.txt0000644000000000000000000000254211701464611012611 0ustar 00000000000000 TODOs ===== This to do list contains some thoughts and ideas about missing features, and parts to think about, whether to implement them or not. This list is probably not complete. Missing features: ----------------- * time formating does not allow to create fractional representations. * parser for ISO intervals. * currently microseconds are always padded to a length of 6 characters. trailing 0s should be optional Documentation: -------------- * parse_datetime: - complete documentation to show what this function allows, but ISO forbids. and vice verse. - support other separators between date and time than 'T' * parse_date: - yeardigits should be always greater than 4 - dates before 0001-01-01 are not supported * parse_duration: - alternative formats are not fully supported due to parse_date restrictions - standard duration format is fully supported but not very restrictive. * Duration: - support fractional years and month in calculations - implement w3c order relation? (``_) - refactor to have duration mathematics only at one place. - localize __str__ method (does timedelta do this?) - when is a Duration negative? - normalize Durations. months [00-12] and years ]-inf,+inf[ isodate-0.4.6/bootstrap.py0000644000000000000000000000364311701464611013675 0ustar 00000000000000############################################################################## # # Copyright (c) 2006 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. $Id$ """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() try: import pkg_resources except ImportError: ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources if sys.platform == 'win32': def quote(c): if ' ' in c: return '"%s"' % c # work around spawn lamosity on windows else: return c else: def quote (c): return c cmd = 'from setuptools.command.easy_install import main; main()' ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, quote (sys.executable), '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) isodate-0.4.6/buildout.cfg0000644000000000000000000000114511701464611013611 0ustar 00000000000000[buildout] develop = . parts = isodate importchecker test pydev coverage coverage-report [isodate] recipe = zc.recipe.egg eggs = isodate interpreter = python [pydev] recipe = pb.recipes.pydev eggs = ${isodate:eggs} [test] recipe = zc.recipe.testrunner eggs = isodate [coverage] recipe = zc.recipe.testrunner eggs = isodate defaults = ['--coverage', '.'] [coverage-report] recipe = zc.recipe.egg eggs = z3c.coverage scripts = coverage=coverage-report arguments = ('parts/coverage', 'parts/coverage-report') [importchecker] recipe = zc.recipe.egg eggs = importchecker arguments = "${buildout:directory}/src" isodate-0.4.6/setup.py0000644000000000000000000000631511701464611013017 0ustar 00000000000000#!/usr/bin/env python ############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## import os setupargs = {} try: from distutils.command.build_py import build_py_2to3 as build_py except ImportError: # 2.x from distutils.command.build_py import build_py try: from setuptools import setup setupargs['test_suite'] = 'isodate.tests.test_suite' setupargs['use_2to3'] = True except ImportError: from distutils.core import setup setupargs['cmdclass'] = {'build_py': build_py} def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='isodate', version='0.4.6', packages=['isodate', 'isodate.tests'], package_dir={'': 'src'}, # dependencies: # install_requires = [], # PyPI metadata author='Gerhard Weis', author_email='gerhard.weis@proclos.com', description='An ISO 8601 date/time/duration parser and formater', license='BSD', #keywords = '', url='http://cheeseshop.python.org/pypi/isodate', long_description=read('README.txt') + read('CHANGES.txt') + read('TODO.txt'), classifiers=['Development Status :: 4 - Beta', # 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Topic :: Internet', 'Topic :: Software Development :: Libraries :: Python Modules', ], **setupargs ) isodate-0.4.6/src/isodate/__init__.py0000644000000000000000000000577611701464611015647 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' Import all essential functions and constants to re-export them here for easy access. This module contains also various pre-defined ISO 8601 format strings. ''' from isodate.isodates import parse_date, date_isoformat from isodate.isotime import parse_time, time_isoformat from isodate.isodatetime import parse_datetime, datetime_isoformat from isodate.isoduration import parse_duration, duration_isoformat, Duration from isodate.isoerror import ISO8601Error from isodate.isotzinfo import parse_tzinfo, tz_isoformat from isodate.tzinfo import UTC, FixedOffset, LOCAL from isodate.duration import Duration from isodate.isostrf import strftime from isodate.isostrf import DATE_BAS_COMPLETE, DATE_BAS_ORD_COMPLETE from isodate.isostrf import DATE_BAS_WEEK, DATE_BAS_WEEK_COMPLETE from isodate.isostrf import DATE_CENTURY, DATE_EXT_COMPLETE from isodate.isostrf import DATE_EXT_ORD_COMPLETE, DATE_EXT_WEEK from isodate.isostrf import DATE_EXT_WEEK_COMPLETE, DATE_MONTH, DATE_YEAR from isodate.isostrf import TIME_BAS_COMPLETE, TIME_BAS_MINUTE from isodate.isostrf import TIME_EXT_COMPLETE, TIME_EXT_MINUTE from isodate.isostrf import TIME_HOUR from isodate.isostrf import TZ_BAS, TZ_EXT, TZ_HOUR from isodate.isostrf import DT_BAS_COMPLETE, DT_EXT_COMPLETE from isodate.isostrf import DT_BAS_ORD_COMPLETE, DT_EXT_ORD_COMPLETE from isodate.isostrf import DT_BAS_WEEK_COMPLETE, DT_EXT_WEEK_COMPLETE from isodate.isostrf import D_DEFAULT, D_WEEK, D_ALT_EXT, D_ALT_BAS from isodate.isostrf import D_ALT_BAS_ORD, D_ALT_EXT_ORD isodate-0.4.6/src/isodate/duration.py0000644000000000000000000002417711701464611015731 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' This module defines a Duration class. The class Duration allows to define durations in years and months and can be used as limited replacement for timedelta objects. ''' from datetime import date, datetime, timedelta def fquotmod(val, low, high): ''' A divmod function with boundaries. ''' div, mod = divmod(val - low, high - low) mod += low return int(div), mod def max_days_in_month(year, month): ''' Determines the number of days of a specific month in a specific year. ''' if month in (1, 3, 5, 7, 8, 10, 12): return 31 if month in (4, 6, 9, 11): return 30 if ((year % 400) == 0) or ((year % 100) != 0) and ((year % 4) == 0): return 29 return 28 class Duration(object): ''' A class which represents a duration. The difference to datetime.timedelta is, that this class handles also differences given in years and months. A Duration treats differences given in year, months separately from all other components. A Duration can be used almost like any timedelta object, however there are some restrictions: * It is not really possible to compare Durations, because it is unclear, whether a duration of 1 year is bigger than 365 days or not. * Equality is only tested between the two (year, month vs. timedelta) basic components. A Duration can also be converted into a datetime object, but this requires a start date or an end date. The algorithm to add a duration to a date is defined at http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes ''' def __init__(self, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0, months=0, years=0): ''' Initialise this Duration instance with the given parameters. ''' self.months = months self.years = years self.tdelta = timedelta(days, seconds, microseconds, milliseconds, minutes, hours, weeks) def __getattr__(self, name): ''' Provide direct access to attributes of included timedelta instance. ''' return getattr(self.tdelta, name) def __str__(self): ''' Return a string representation of this duration similar to timedelta. ''' params = [] if self.years: params.append('%d years' % self.years) if self.months: params.append('%d months' % self.months) params.append(str(self.tdelta)) return ', '.join(params) def __repr__(self): ''' Return a string suitable for repr(x) calls. ''' return "%s.%s(%d, %d, %d, years=%d, months=%d)" % ( self.__class__.__module__, self.__class__.__name__, self.tdelta.days, self.tdelta.seconds, self.tdelta.microseconds, self.years, self.months) def __neg__(self): """ A simple unary minus. Returns a new Duration instance with all it's negated. """ negduration = Duration(years=-self.years, months=-self.months) negduration.tdelta = -self.tdelta return negduration def __add__(self, other): ''' Durations can be added with Duration, timedelta, date and datetime objects. ''' if isinstance(other, timedelta): newduration = Duration(years=self.years, months=self.months) newduration.tdelta = self.tdelta + other return newduration if isinstance(other, Duration): newduration = Duration(years=self.years + other.years, months=self.months + other.months) newduration.tdelta = self.tdelta + other.tdelta return newduration if isinstance(other, (date, datetime)): newmonth = other.month + self.months carry, newmonth = fquotmod(newmonth, 1, 13) newyear = other.year + self.years + carry maxdays = max_days_in_month(newyear, newmonth) if other.day > maxdays: newday = maxdays else: newday = other.day newdt = other.replace(year=newyear, month=newmonth, day=newday) return self.tdelta + newdt raise TypeError('unsupported operand type(s) for +: %s and %s' % (self.__class__, other.__class__)) def __radd__(self, other): ''' Add durations to timedelta, date and datetime objects. ''' if isinstance(other, timedelta): newduration = Duration(years=self.years, months=self.months) newduration.tdelta = self.tdelta + other return newduration if isinstance(other, (date, datetime)): newmonth = other.month + self.months carry, newmonth = fquotmod(newmonth, 1, 13) newyear = other.year + self.years + carry maxdays = max_days_in_month(newyear, newmonth) if other.day > maxdays: newday = maxdays else: newday = other.day newdt = other.replace(year=newyear, month=newmonth, day=newday) return newdt + self.tdelta raise TypeError('unsupported operand type(s) for +: %s and %s' % (other.__class__, self.__class__)) def __sub__(self, other): ''' It is possible to subtract Duration and timedelta objects from Duration objects. ''' if isinstance(other, Duration): newduration = Duration(years=self.years - other.years, months=self.months - other.months) newduration.tdelta = self.tdelta - other.tdelta return newduration if isinstance(other, timedelta): newduration = Duration(years=self.years, months=self.months) newduration.tdelta = self.tdelta - other return newduration raise TypeError('unsupported operand type(s) for -: %s and %s' % (self.__class__, other.__class__)) def __rsub__(self, other): ''' It is possible to subtract Duration objecs from date, datetime and timedelta objects. ''' #print '__rsub__:', self, other if isinstance(other, (date, datetime)): newmonth = other.month - self.months carry, newmonth = fquotmod(newmonth, 1, 13) newyear = other.year - self.years + carry maxdays = max_days_in_month(newyear, newmonth) if other.day > maxdays: newday = maxdays else: newday = other.day newdt = other.replace(year=newyear, month=newmonth, day=newday) return newdt - self.tdelta if isinstance(other, timedelta): tmpdur = Duration() tmpdur.tdelta = other return tmpdur - self raise TypeError('unsupported operand type(s) for -: %s and %s' % (other.__class__, self.__class__)) def __eq__(self, other): ''' If the years, month part and the timedelta part are both equal, then the two Durations are considered equal. ''' if (isinstance(other, timedelta) and self.years == 0 and self.months == 0): return self.tdelta == other if not isinstance(other, Duration): return NotImplemented if ((self.years * 12 + self.months) == (other.years * 12 + other.months) and self.tdelta == other.tdelta): return True return False def __ne__(self, other): ''' If the years, month part or the timedelta part is not equal, then the two Durations are considered not equal. ''' if isinstance(other, timedelta) and self.years == 0 and self.months == 0: return self.tdelta != other if not isinstance(other, Duration): return NotImplemented if ((self.years * 12 + self.months) != (other.years * 12 + other.months) or self.tdelta != other.tdelta): return True return False def todatetime(self, start=None, end=None): ''' Convert this duration into a timedelta object. This method requires a start datetime or end datetimem, but raises an exception if both are given. ''' if start is None and end is None: raise ValueError("start or end required") if start is not None and end is not None: raise ValueError("only start or end allowed") if start is not None: return (start + self) - start return end - (end - self) isodate-0.4.6/src/isodate/isodates.py0000644000000000000000000002366511701464611015720 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' This modules provides a method to parse an ISO 8601:2004 date string to a python datetime.date instance. It supports all basic, extended and expanded formats as described in the ISO standard. The only limitations it has, are given by the Python datetime.date implementation, which does not support dates before 0001-01-01. ''' import re from datetime import date, timedelta from isodate.isostrf import strftime, DATE_EXT_COMPLETE from isodate.isoerror import ISO8601Error DATE_REGEX_CACHE = {} # A dictionary to cache pre-compiled regular expressions. # A set of regular expressions is identified, by number of year digits allowed # and whether a plus/minus sign is required or not. (This option is changeable # only for 4 digit years). def build_date_regexps(yeardigits=4, expanded=False): ''' Compile set of regular expressions to parse ISO dates. The expressions will be created only if they are not already in REGEX_CACHE. It is necessary to fix the number of year digits, else it is not possible to automatically distinguish between various ISO date formats. ISO 8601 allows more than 4 digit years, on prior agreement, but then a +/- sign is required (expanded format). To support +/- sign for 4 digit years, the expanded parameter needs to be set to True. ''' if yeardigits != 4: expanded = True if (yeardigits, expanded) not in DATE_REGEX_CACHE: cache_entry = [] # ISO 8601 expanded DATE formats allow an arbitrary number of year # digits with a leading +/- sign. if expanded: sign = 1 else: sign = 0 # 1. complete dates: # YYYY-MM-DD or +- YYYYYY-MM-DD... extended date format cache_entry.append(re.compile(r"(?P[+-]){%d}(?P[0-9]{%d})" r"-(?P[0-9]{2})-(?P[0-9]{2})" % (sign, yeardigits))) # YYYYMMDD or +- YYYYYYMMDD... basic date format cache_entry.append(re.compile(r"(?P[+-]){%d}(?P[0-9]{%d})" r"(?P[0-9]{2})(?P[0-9]{2})" % (sign, yeardigits))) # 2. complete week dates: # YYYY-Www-D or +-YYYYYY-Www-D ... extended week date cache_entry.append(re.compile(r"(?P[+-]){%d}(?P[0-9]{%d})" r"-W(?P[0-9]{2})-(?P[0-9]{1})" % (sign, yeardigits))) # YYYYWwwD or +-YYYYYYWwwD ... basic week date cache_entry.append(re.compile(r"(?P[+-]){%d}(?P[0-9]{%d})W" r"(?P[0-9]{2})(?P[0-9]{1})" % (sign, yeardigits))) # 3. ordinal dates: # YYYY-DDD or +-YYYYYY-DDD ... extended format cache_entry.append(re.compile(r"(?P[+-]){%d}(?P[0-9]{%d})" r"-(?P[0-9]{3})" % (sign, yeardigits))) # YYYYDDD or +-YYYYYYDDD ... basic format cache_entry.append(re.compile(r"(?P[+-]){%d}(?P[0-9]{%d})" r"(?P[0-9]{3})" % (sign, yeardigits))) # 4. week dates: # YYYY-Www or +-YYYYYY-Www ... extended reduced accuracy week date cache_entry.append(re.compile(r"(?P[+-]){%d}(?P[0-9]{%d})" r"-W(?P[0-9]{2})" % (sign, yeardigits))) # YYYYWww or +-YYYYYYWww ... basic reduced accuracy week date cache_entry.append(re.compile(r"(?P[+-]){%d}(?P[0-9]{%d})W" r"(?P[0-9]{2})" % (sign, yeardigits))) # 5. month dates: # YYY-MM or +-YYYYYY-MM ... reduced accuracy specific month cache_entry.append(re.compile(r"(?P[+-]){%d}(?P[0-9]{%d})" r"-(?P[0-9]{2})" % (sign, yeardigits))) # 6. year dates: # YYYY or +-YYYYYY ... reduced accuracy specific year cache_entry.append(re.compile(r"(?P[+-]){%d}(?P[0-9]{%d})" % (sign, yeardigits))) # 7. century dates: # YY or +-YYYY ... reduced accuracy specific century cache_entry.append(re.compile(r"(?P[+-]){%d}" r"(?P[0-9]{%d})" % (sign, yeardigits - 2))) DATE_REGEX_CACHE[(yeardigits, expanded)] = cache_entry return DATE_REGEX_CACHE[(yeardigits, expanded)] def parse_date(datestring, yeardigits=4, expanded=False): ''' Parse an ISO 8601 date string into a datetime.date object. As the datetime.date implementation is limited to dates starting from 0001-01-01, negative dates (BC) and year 0 can not be parsed by this method. For incomplete dates, this method chooses the first day for it. For instance if only a century is given, this method returns the 1st of January in year 1 of this century. supported formats: (expanded formats are shown with 6 digits for year) YYYYMMDD +-YYYYYYMMDD basic complete date YYYY-MM-DD +-YYYYYY-MM-DD extended complete date YYYYWwwD +-YYYYYYWwwD basic complete week date YYYY-Www-D +-YYYYYY-Www-D extended complete week date YYYYDDD +-YYYYYYDDD basic ordinal date YYYY-DDD +-YYYYYY-DDD extended ordinal date YYYYWww +-YYYYYYWww basic incomplete week date YYYY-Www +-YYYYYY-Www extended incomplete week date YYY-MM +-YYYYYY-MM incomplete month date YYYY +-YYYYYY incomplete year date YY +-YYYY incomplete century date @param datestring: the ISO date string to parse @param yeardigits: how many digits are used to represent a year @param expanded: if True then +/- signs are allowed. This parameter is forced to True, if yeardigits != 4 @return: a datetime.date instance represented by datestring @raise ISO8601Error: if this function can not parse the datestring @raise ValueError: if datestring can not be represented by datetime.date ''' if yeardigits != 4: expanded = True isodates = build_date_regexps(yeardigits, expanded) for pattern in isodates: match = pattern.match(datestring) if match: groups = match.groupdict() # sign, century, year, month, week, day, # FIXME: negative dates not possible with python standard types sign = (groups['sign'] == '-' and -1) or 1 if 'century' in groups: return date(sign * (int(groups['century']) * 100 + 1), 1, 1) if not 'month' in groups: # weekdate or ordinal date ret = date(sign * int(groups['year']), 1, 1) if 'week' in groups: isotuple = ret.isocalendar() if 'day' in groups: days = int(groups['day'] or 1) else: days = 1 # if first week in year, do weeks-1 return ret + timedelta(weeks=int(groups['week']) - (((isotuple[1] == 1) and 1) or 0), days = -isotuple[2] + days) elif 'day' in groups: # ordinal date return ret + timedelta(days=int(groups['day'])-1) else: # year date return ret # year-, month-, or complete date if 'day' not in groups or groups['day'] is None: day = 1 else: day = int(groups['day']) return date(sign * int(groups['year']), int(groups['month']) or 1, day) raise ISO8601Error('Unrecognised ISO 8601 date format: %r' % datestring) def date_isoformat(tdate, format=DATE_EXT_COMPLETE, yeardigits=4): ''' Format date strings. This method is just a wrapper around isodate.isostrf.strftime and uses Date-Extended-Complete as default format. ''' return strftime(tdate, format, yeardigits) isodate-0.4.6/src/isodate/isodatetime.py0000644000000000000000000000527511701464611016411 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' This module defines a method to parse an ISO 8601:2004 date time string. For this job it uses the parse_date and parse_time methods defined in date and time module. ''' from datetime import datetime from isodate.isostrf import strftime from isodate.isostrf import DATE_EXT_COMPLETE, TIME_EXT_COMPLETE, TZ_EXT from isodate.isodates import parse_date from isodate.isotime import parse_time def parse_datetime(datetimestring): ''' Parses ISO 8601 date-times into datetime.datetime objects. This function uses parse_date and parse_time to do the job, so it allows more combinations of date and time representations, than the actual ISO 8601:2004 standard allows. ''' datestring, timestring = datetimestring.split('T') tmpdate = parse_date(datestring) tmptime = parse_time(timestring) return datetime.combine(tmpdate, tmptime) def datetime_isoformat(tdt, format=DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + TZ_EXT): ''' Format datetime strings. This method is just a wrapper around isodate.isostrf.strftime and uses Extended-Complete as default format. ''' return strftime(tdt, format) isodate-0.4.6/src/isodate/isoduration.py0000644000000000000000000001456611701464611016445 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' This module provides an ISO 8601:2004 duration parser. It also provides a wrapper to strftime. This wrapper makes it easier to format timedelta or Duration instances as ISO conforming strings. ''' from datetime import timedelta import re from isodate.duration import Duration from isodate.isoerror import ISO8601Error from isodate.isodatetime import parse_datetime from isodate.isostrf import strftime, D_DEFAULT ISO8601_PERIOD_REGEX = re.compile(r"^(?P[+-])?" r"P(?P[0-9]+([,.][0-9]+)?Y)?" r"(?P[0-9]+([,.][0-9]+)?M)?" r"(?P[0-9]+([,.][0-9]+)?W)?" r"(?P[0-9]+([,.][0-9]+)?D)?" r"((?PT)(?P[0-9]+([,.][0-9]+)?H)?" r"(?P[0-9]+([,.][0-9]+)?M)?" r"(?P[0-9]+([,.][0-9]+)?S)?)?$") # regular expression to parse ISO duartion strings. def parse_duration(datestring): """ Parses an ISO 8601 durations into datetime.timedelta or Duration objects. If the ISO date string does not contain years or months, a timedelta instance is returned, else a Duration instance is returned. The following duration formats are supported: -PnnW duration in weeks -PnnYnnMnnDTnnHnnMnnS complete duration specification -PYYYYMMDDThhmmss basic alternative complete date format -PYYYY-MM-DDThh:mm:ss extended alternative complete date format -PYYYYDDDThhmmss basic alternative ordinal date format -PYYYY-DDDThh:mm:ss extended alternative ordinal date format The '-' is optional. Limitations: ISO standard defines some restrictions about where to use fractional numbers and which component and format combinations are allowed. This parser implementation ignores all those restrictions and returns something when it is able to find all necessary components. In detail: it does not check, whether only the last component has fractions. it allows weeks specified with all other combinations The alternative format does not support durations with years, months or days set to 0. """ if not isinstance(datestring, basestring): raise TypeError("Expecting a string %r" % datestring) match = ISO8601_PERIOD_REGEX.match(datestring) if not match: # try alternative format: if datestring.startswith("P"): durdt = parse_datetime(datestring[1:]) if durdt.year != 0 or durdt.month != 0: # create Duration ret = Duration(days=durdt.day, seconds=durdt.second, microseconds=durdt.microsecond, minutes=durdt.minute, hours=durdt.hour, months=durdt.month, years=durdt.year) else: # FIXME: currently not possible in alternative format # create timedelta ret = timedelta(days=durdt.day, seconds=durdt.second, microseconds=durdt.microsecond, minutes=durdt.minute, hours=durdt.hour) return ret raise ISO8601Error("Unable to parse duration string %r" % datestring) groups = match.groupdict() for key, val in groups.items(): if key not in ('separator', 'sign'): if val is None: groups[key] = "0n" #print groups[key] groups[key] = float(groups[key][:-1].replace(',', '.')) if groups["years"] == 0 and groups["months"] == 0: ret = timedelta(days=groups["days"], hours=groups["hours"], minutes=groups["minutes"], seconds=groups["seconds"], weeks=groups["weeks"]) if groups["sign"] == '-': ret = timedelta(0) - ret else: ret = Duration(years=groups["years"], months=groups["months"], days=groups["days"], hours=groups["hours"], minutes=groups["minutes"], seconds=groups["seconds"], weeks=groups["weeks"]) if groups["sign"] == '-': ret = Duration(0) - ret return ret def duration_isoformat(tduration, format=D_DEFAULT): ''' Format duration strings. This method is just a wrapper around isodate.isostrf.strftime and uses P%P (D_DEFAULT) as default format. ''' # TODO: implement better decision for negative Durations. # should be done in Duration class in consistent way with timedelta. if ((isinstance(tduration, Duration) and (tduration.years < 0 or tduration.months < 0 or tduration.tdelta < timedelta(0))) or (isinstance(tduration, timedelta) and (tduration < timedelta(0)))): ret = '-' else: ret = '' ret += strftime(tduration, format) return ret isodate-0.4.6/src/isodate/isoerror.py0000644000000000000000000000326211701464611015740 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' This module defines all exception classes in the whole package. ''' class ISO8601Error(Exception): '''Raised when the given ISO string can not be parsed.''' isodate-0.4.6/src/isodate/isostrf.py0000644000000000000000000002161311701464611015565 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## """ This module provides an alternative strftime method. The strftime method in this module allows only a subset of Python's strftime format codes, plus a few additional. It supports the full range of date values possible with standard Python date/time objects. Furthermore there are several pr-defined format strings in this module to make ease producing of ISO 8601 conforming strings. """ import re from datetime import date, timedelta from isodate.duration import Duration from isodate.isotzinfo import tz_isoformat # Date specific format strings DATE_BAS_COMPLETE = '%Y%m%d' DATE_EXT_COMPLETE = '%Y-%m-%d' DATE_BAS_WEEK_COMPLETE = '%YW%W%w' DATE_EXT_WEEK_COMPLETE = '%Y-W%W-%w' DATE_BAS_ORD_COMPLETE = '%Y%j' DATE_EXT_ORD_COMPLETE = '%Y-%j' DATE_BAS_WEEK = '%YW%W' DATE_EXT_WEEK = '%Y-W%W' DATE_MONTH = '%Y-%m' DATE_YEAR = '%Y' DATE_CENTURY = '%C' # Time specific format strings TIME_BAS_COMPLETE = '%H%M%S' TIME_EXT_COMPLETE = '%H:%M:%S' TIME_BAS_MINUTE = '%H%M' TIME_EXT_MINUTE = '%H:%M' TIME_HOUR = '%H' # Time zone formats TZ_BAS = '%z' TZ_EXT = '%Z' TZ_HOUR = '%h' # DateTime formats DT_EXT_COMPLETE = DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + TZ_EXT DT_BAS_COMPLETE = DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + TZ_BAS DT_EXT_ORD_COMPLETE = DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_COMPLETE + TZ_EXT DT_BAS_ORD_COMPLETE = DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_COMPLETE + TZ_BAS DT_EXT_WEEK_COMPLETE = DATE_EXT_WEEK_COMPLETE + 'T' + TIME_EXT_COMPLETE +\ TZ_EXT DT_BAS_WEEK_COMPLETE = DATE_BAS_WEEK_COMPLETE + 'T' + TIME_BAS_COMPLETE +\ TZ_BAS # Duration formts D_DEFAULT = 'P%P' D_WEEK = 'P%p' D_ALT_EXT = 'P' + DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE D_ALT_BAS = 'P' + DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE D_ALT_EXT_ORD = 'P' + DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_COMPLETE D_ALT_BAS_ORD = 'P' + DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_COMPLETE STRF_DT_MAP = {'%d': lambda tdt, yds: '%02d' % tdt.day, '%f': lambda tdt, yds: '%d' % tdt.microsecond, '%H': lambda tdt, yds: '%02d' % tdt.hour, '%j': lambda tdt, yds: '%03d' % (tdt.toordinal() - date(tdt.year, 1, 1).toordinal() + 1), '%m': lambda tdt, yds: '%02d' % tdt.month, '%M': lambda tdt, yds: '%02d' % tdt.minute, '%S': lambda tdt, yds: '%02d' % tdt.second, '%w': lambda tdt, yds: '%1d' % tdt.isoweekday(), '%W': lambda tdt, yds: '%02d' % tdt.isocalendar()[1], '%Y': lambda tdt, yds: (((yds != 4) and '+') or '') +\ (('%%0%dd' % yds) % tdt.year), '%C': lambda tdt, yds: (((yds != 4) and '+') or '') +\ (('%%0%dd' % (yds - 2)) % (tdt.year / 100)), '%h': lambda tdt, yds: tz_isoformat(tdt.tzinfo, '%h'), '%Z': lambda tdt, yds: tz_isoformat(tdt.tzinfo, '%Z'), '%z': lambda tdt, yds: tz_isoformat(tdt.tzinfo, '%z'), '%%': lambda tdt, yds: '%'} STRF_D_MAP = {'%d': lambda tdt, yds: '%02d' % tdt.days, '%f': lambda tdt, yds: '%d' % tdt.microseconds, '%H': lambda tdt, yds: '%02d' % (tdt.seconds / 60 / 60), '%m': lambda tdt, yds: '%02d' % tdt.months, '%M': lambda tdt, yds: '%02d' % ((tdt.seconds / 60) % 60), '%S': lambda tdt, yds: '%02d' % (tdt.seconds % 60), '%W': lambda tdt, yds: '%02d' % (abs(tdt.days / 7)), '%Y': lambda tdt, yds: (((yds != 4) and '+') or '') +\ (('%%0%dd' % yds) % tdt.years), '%C': lambda tdt, yds: (((yds != 4) and '+') or '') +\ (('%%0%dd' % (yds - 2)) % (tdt.years / 100)), '%%': lambda tdt, yds: '%'} def _strfduration(tdt, format, yeardigits=4): ''' this is the work method for timedelta and Duration instances. see strftime for more details. ''' def repl(match): ''' lookup format command and return corresponding replacement. ''' if match.group(0) in STRF_D_MAP: return STRF_D_MAP[match.group(0)](tdt, yeardigits) elif match.group(0) == '%P': ret = [] if isinstance(tdt, Duration): if tdt.years: ret.append('%sY' % abs(tdt.years)) if tdt.months: ret.append('%sM' % abs(tdt.months)) usecs = abs((tdt.days * 24 * 60 * 60 + tdt.seconds) * 1000000 + tdt.microseconds) seconds, usecs = divmod(usecs, 1000000) minutes, seconds = divmod(seconds, 60) hours, minutes = divmod(minutes, 60) days, hours = divmod(hours, 24) if days: ret.append('%sD' % days) if hours or minutes or seconds or usecs: ret.append('T') if hours: ret.append('%sH' % hours) if minutes: ret.append('%sM' % minutes) if seconds or usecs: if usecs: ret.append(("%d.%06d" % (seconds, usecs)).rstrip('0')) else: ret.append("%d" % seconds) ret.append('S') # at least one component has to be there. return ret and ''.join(ret) or '0D' elif match.group(0) == '%p': return str(abs(tdt.days // 7)) + 'W' return match.group(0) return re.sub('%d|%f|%H|%m|%M|%S|%W|%Y|%C|%%|%P|%p', repl, format) def _strfdt(tdt, format, yeardigits=4): ''' this is the work method for time and date instances. see strftime for more details. ''' def repl(match): ''' lookup format command and return corresponding replacement. ''' if match.group(0) in STRF_DT_MAP: return STRF_DT_MAP[match.group(0)](tdt, yeardigits) return match.group(0) return re.sub('%d|%f|%H|%j|%m|%M|%S|%w|%W|%Y|%C|%z|%Z|%h|%%', repl, format) def strftime(tdt, format, yeardigits=4): ''' Directive Meaning Notes %d Day of the month as a decimal number [01,31]. %f Microsecond as a decimal number [0,999999], zero-padded on the left (1) %H Hour (24-hour clock) as a decimal number [00,23]. %j Day of the year as a decimal number [001,366]. %m Month as a decimal number [01,12]. %M Minute as a decimal number [00,59]. %S Second as a decimal number [00,61]. (3) %w Weekday as a decimal number [0(Monday),6]. %W Week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0. (4) %Y Year with century as a decimal number. [0000,9999] %C Century as a decimal number. [00,99] %z UTC offset in the form +HHMM or -HHMM (empty string if the the object is naive). (5) %Z Time zone name (empty string if the object is naive). %P ISO8601 duration format. %p ISO8601 duration format in weeks. %% A literal '%' character. ''' if isinstance(tdt, (timedelta, Duration)): return _strfduration(tdt, format, yeardigits) return _strfdt(tdt, format, yeardigits) isodate-0.4.6/src/isodate/isotime.py0000644000000000000000000001556611701464611015557 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' This modules provides a method to parse an ISO 8601:2004 time string to a Python datetime.time instance. It supports all basic and extended formats including time zone specifications as described in the ISO standard. ''' import re from decimal import Decimal from datetime import time from isodate.isostrf import strftime, TIME_EXT_COMPLETE, TZ_EXT from isodate.isoerror import ISO8601Error from isodate.isotzinfo import TZ_REGEX, build_tzinfo TIME_REGEX_CACHE = [] # used to cache regular expressions to parse ISO time strings. def build_time_regexps(): ''' Build regular expressions to parse ISO time string. The regular expressions are compiled and stored in TIME_REGEX_CACHE for later reuse. ''' if not TIME_REGEX_CACHE: # ISO 8601 time representations allow decimal fractions on least # significant time component. Command and Full Stop are both valid # fraction separators. # The letter 'T' is allowed as time designator in front of a time # expression. # Immediately after a time expression, a time zone definition is # allowed. # a TZ may be missing (local time), be a 'Z' for UTC or a string of # +-hh:mm where the ':mm' part can be skipped. # TZ information patterns: # '' # Z # +-hh:mm # +-hhmm # +-hh => # isotzinfo.TZ_REGEX # 1. complete time: # hh:mm:ss.ss ... extended format TIME_REGEX_CACHE.append(re.compile(r"T?(?P[0-9]{2}):" r"(?P[0-9]{2}):" r"(?P[0-9]{2}([,.][0-9]+)?)" + TZ_REGEX)) # hhmmss.ss ... basic format TIME_REGEX_CACHE.append(re.compile(r"T?(?P[0-9]{2})" r"(?P[0-9]{2})" r"(?P[0-9]{2}([,.][0-9]+)?)" + TZ_REGEX)) # 2. reduced accuracy: # hh:mm.mm ... extended format TIME_REGEX_CACHE.append(re.compile(r"T?(?P[0-9]{2}):" r"(?P[0-9]{2}([,.][0-9]+)?)" + TZ_REGEX)) # hhmm.mm ... basic format TIME_REGEX_CACHE.append(re.compile(r"T?(?P[0-9]{2})" r"(?P[0-9]{2}([,.][0-9]+)?)" + TZ_REGEX)) # hh.hh ... basic format TIME_REGEX_CACHE.append(re.compile(r"T?(?P[0-9]{2}([,.][0-9]+)?)" + TZ_REGEX)) return TIME_REGEX_CACHE def parse_time(timestring): ''' Parses ISO 8601 times into datetime.time objects. Following ISO 8601 formats are supported: (as decimal separator a ',' or a '.' is allowed) hhmmss.ssTZD basic complete time hh:mm:ss.ssTZD extended compelte time hhmm.mmTZD basic reduced accuracy time hh:mm.mmTZD extended reduced accuracy time hh.hhTZD basic reduced accuracy time TZD is the time zone designator which can be in the following format: no designator indicates local time zone Z UTC +-hhmm basic hours and minutes +-hh:mm extended hours and minutes +-hh hours ''' isotimes = build_time_regexps() for pattern in isotimes: match = pattern.match(timestring) if match: groups = match.groupdict() for key, value in groups.items(): if value is not None: groups[key] = value.replace(',', '.') tzinfo = build_tzinfo(groups['tzname'], groups['tzsign'], int(groups['tzhour'] or 0), int(groups['tzmin'] or 0)) if 'second' in groups: second = Decimal(groups['second']) microsecond = (second - int(second)) * long(1e6) # int(...) ... no rounding # to_integral() ... rounding return time(int(groups['hour']), int(groups['minute']), int(second), microsecond.to_integral(), tzinfo) if 'minute' in groups: minute = Decimal(groups['minute']) second = (minute - int(minute)) * 60 microsecond = (second - int(second)) * long(1e6) return time(int(groups['hour']), int(minute), int(second), microsecond.to_integral(), tzinfo) else: microsecond, second, minute = 0, 0, 0 hour = Decimal(groups['hour']) minute = (hour - int(hour)) * 60 second = (minute - int(minute)) * 60 microsecond = (second - int(second)) * long(1e6) return time(int(hour), int(minute), int(second), microsecond.to_integral(), tzinfo) raise ISO8601Error('Unrecognised ISO 8601 time format: %r' % timestring) def time_isoformat(ttime, format=TIME_EXT_COMPLETE + TZ_EXT): ''' Format time strings. This method is just a wrapper around isodate.isostrf.strftime and uses Time-Extended-Complete with extended time zone as default format. ''' return strftime(ttime, format) isodate-0.4.6/src/isodate/isotzinfo.py0000644000000000000000000001020111701464611016107 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' This module provides an ISO 8601:2004 time zone info parser. It offers a function to parse the time zone offset as specified by ISO 8601. ''' import re from isodate.isoerror import ISO8601Error from isodate.tzinfo import UTC, FixedOffset, ZERO TZ_REGEX = r"(?P(Z|(?P[+-])"\ r"(?P[0-9]{2})(:(?P[0-9]{2}))?)?)" TZ_RE = re.compile(TZ_REGEX) def build_tzinfo(tzname, tzsign='+', tzhour=0, tzmin=0): ''' create a tzinfo instance according to given parameters. tzname: 'Z' ... return UTC '' | None ... return None other ... return FixedOffset ''' if tzname is None or tzname == '': return None if tzname == 'Z': return UTC tzsign = ((tzsign == '-') and -1) or 1 return FixedOffset(tzsign * tzhour, tzsign * tzmin, tzname) def parse_tzinfo(tzstring): ''' Parses ISO 8601 time zone designators to tzinfo objecs. A time zone designator can be in the following format: no designator indicates local time zone Z UTC +-hhmm basic hours and minutes +-hh:mm extended hours and minutes +-hh hours ''' match = TZ_RE.match(tzstring) if match: groups = match.groupdict() return build_tzinfo(groups['tzname'], groups['tzsign'], int(groups['tzhour'] or 0), int(groups['tzmin'] or 0)) raise ISO8601Error('%s not a valid time zone info' % tzstring) def tz_isoformat(tzinfo, format='%Z'): ''' return time zone offset ISO 8601 formatted. The various ISO formats can be chosen with the format parameter. if tzinfo is None returns '' if tzinfo is UTC returns 'Z' else the offset is rendered to the given format. format: %h ... +-HH %z ... +-HHMM %Z ... +-HH:MM ''' if (tzinfo is None) or (tzinfo.utcoffset(None) is None): return '' if tzinfo.utcoffset(None) == ZERO and tzinfo.dst(None) == ZERO: return 'Z' tdelta = tzinfo.utcoffset(None) seconds = tdelta.days * 24 * 60 * 60 + tdelta.seconds sign = ((seconds < 0) and '-') or '+' seconds = abs(seconds) minutes, seconds = divmod(seconds, 60) hours, minutes = divmod(minutes, 60) if hours > 99: raise OverflowError('can not handle differences > 99 hours') if format == '%Z': return '%s%02d:%02d' % (sign, hours, minutes) elif format == '%z': return '%s%02d%02d' % (sign, hours, minutes) elif format == '%h': return '%s%02d' % (sign, hours) raise AttributeError('unknown format string "%s"' % format) isodate-0.4.6/src/isodate/tests/__init__.py0000644000000000000000000000400711701464611016773 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' Collect all test suites into one TestSuite instance. ''' import unittest from isodate.tests import test_date, test_time, test_datetime, test_duration def test_suite(): ''' Return a new TestSuite instance consisting of all available TestSuites. ''' return unittest.TestSuite([ test_date.test_suite(), test_time.test_suite(), test_datetime.test_suite(), test_duration.test_suite()]) if __name__ == '__main__': unittest.main(defaultTest='test_suite') isodate-0.4.6/src/isodate/tests/test_date.py0000644000000000000000000001347711701464611017223 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' Test cases for the isodate module. ''' import unittest from datetime import date from isodate import parse_date, ISO8601Error, date_isoformat from isodate import DATE_CENTURY, DATE_YEAR, DATE_MONTH from isodate import DATE_EXT_COMPLETE, DATE_BAS_COMPLETE from isodate import DATE_BAS_ORD_COMPLETE, DATE_EXT_ORD_COMPLETE from isodate import DATE_BAS_WEEK, DATE_BAS_WEEK_COMPLETE from isodate import DATE_EXT_WEEK, DATE_EXT_WEEK_COMPLETE # the following list contains tuples of ISO date strings and the expected # result from the parse_date method. A result of None means an ISO8601Error # is expected. The test cases are grouped into dates with 4 digit years # and 6 digit years. TEST_CASES = {4: [('19', date(1901, 1, 1), DATE_CENTURY), ('1985', date(1985, 1, 1), DATE_YEAR), ('1985-04', date(1985, 4, 1), DATE_MONTH), ('1985-04-12', date(1985, 4, 12), DATE_EXT_COMPLETE), ('19850412', date(1985, 4, 12), DATE_BAS_COMPLETE), ('1985102', date(1985, 4, 12), DATE_BAS_ORD_COMPLETE), ('1985-102', date(1985, 4, 12), DATE_EXT_ORD_COMPLETE), ('1985W155', date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE), ('1985-W15-5', date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE), ('1985W15', date(1985, 4, 8), DATE_BAS_WEEK), ('1985-W15', date(1985, 4, 8), DATE_EXT_WEEK), ('1989-W15', date(1989, 4, 10), DATE_EXT_WEEK), ('1989-W15-5', date(1989, 4, 14), DATE_EXT_WEEK_COMPLETE), ('1-W1-1', None, DATE_BAS_WEEK_COMPLETE)], 6: [('+0019', date(1901, 1, 1), DATE_CENTURY), ('+001985', date(1985, 1, 1), DATE_YEAR), ('+001985-04', date(1985, 4, 1), DATE_MONTH), ('+001985-04-12', date(1985, 4, 12), DATE_EXT_COMPLETE), ('+0019850412', date(1985, 4, 12), DATE_BAS_COMPLETE), ('+001985102', date(1985, 4, 12), DATE_BAS_ORD_COMPLETE), ('+001985-102', date(1985, 4, 12), DATE_EXT_ORD_COMPLETE), ('+001985W155', date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE), ('+001985-W15-5', date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE), ('+001985W15', date(1985, 4, 8), DATE_BAS_WEEK), ('+001985-W15', date(1985, 4, 8), DATE_EXT_WEEK)]} def create_testcase(yeardigits, datestring, expectation, format): ''' Create a TestCase class for a specific test. This allows having a separate TestCase for each test tuple from the TEST_CASES list, so that a failed test won't stop other tests. ''' class TestDate(unittest.TestCase): ''' A test case template to parse an ISO date string into a date object. ''' def test_parse(self): ''' Parse an ISO date string and compare it to the expected value. ''' if expectation is None: self.assertRaises(ISO8601Error, parse_date, datestring, yeardigits) else: result = parse_date(datestring, yeardigits) self.assertEqual(result, expectation) def test_format(self): ''' Take date object and create ISO string from it. This is the reverse test to test_parse. ''' if expectation is None: self.assertRaises(AttributeError, date_isoformat, expectation, format, yeardigits) else: self.assertEqual(date_isoformat(expectation, format, yeardigits), datestring) return unittest.TestLoader().loadTestsFromTestCase(TestDate) def test_suite(): ''' Construct a TestSuite instance for all test cases. ''' suite = unittest.TestSuite() for yeardigits, tests in TEST_CASES.items(): for datestring, expectation, format in tests: suite.addTest(create_testcase(yeardigits, datestring, expectation, format)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') isodate-0.4.6/src/isodate/tests/test_datetime.py0000644000000000000000000001172111701464611020070 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' Test cases for the isodatetime module. ''' import unittest from datetime import datetime from isodate import parse_datetime, UTC, FixedOffset, datetime_isoformat from isodate import DATE_BAS_COMPLETE, TIME_BAS_MINUTE, TIME_BAS_COMPLETE from isodate import DATE_EXT_COMPLETE, TIME_EXT_MINUTE from isodate import TZ_BAS, TZ_EXT, TZ_HOUR from isodate import DATE_BAS_ORD_COMPLETE, DATE_EXT_ORD_COMPLETE from isodate import DATE_BAS_WEEK_COMPLETE, DATE_EXT_WEEK_COMPLETE # the following list contains tuples of ISO datetime strings and the expected # result from the parse_datetime method. A result of None means an ISO8601Error # is expected. TEST_CASES = [('19850412T1015', datetime(1985, 4, 12, 10, 15), DATE_BAS_COMPLETE + 'T' + TIME_BAS_MINUTE), ('1985-04-12T10:15', datetime(1985, 4, 12, 10, 15), DATE_EXT_COMPLETE + 'T' + TIME_EXT_MINUTE), ('1985102T1015Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC), DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS), ('1985-102T10:15Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC), DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_EXT), ('1985W155T1015+0400', datetime(1985, 4, 12, 10, 15, tzinfo=FixedOffset(4, 0, '+0400')), DATE_BAS_WEEK_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS), ('1985-W15-5T10:15+04', datetime(1985, 4, 12, 10, 15, tzinfo=FixedOffset(4, 0, '+0400')), DATE_EXT_WEEK_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_HOUR), ('20110410T101225.123000Z', datetime(2011, 4, 10, 10, 12, 25, 123000, tzinfo=UTC), DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + ".%f" + TZ_BAS)] def create_testcase(datetimestring, expectation, format): """ Create a TestCase class for a specific test. This allows having a separate TestCase for each test tuple from the TEST_CASES list, so that a failed test won't stop other tests. """ class TestDateTime(unittest.TestCase): ''' A test case template to parse an ISO datetime string into a datetime object. ''' def test_parse(self): ''' Parse an ISO datetime string and compare it to the expected value. ''' result = parse_datetime(datetimestring) self.assertEqual(result, expectation) def test_format(self): ''' Take datetime object and create ISO string from it. This is the reverse test to test_parse. ''' if expectation is None: self.assertRaises(AttributeError, datetime_isoformat, expectation, format) else: self.assertEqual(datetime_isoformat(expectation, format), datetimestring) return unittest.TestLoader().loadTestsFromTestCase(TestDateTime) def test_suite(): ''' Construct a TestSuite instance for all test cases. ''' suite = unittest.TestSuite() for datetimestring, expectation, format in TEST_CASES: suite.addTest(create_testcase(datetimestring, expectation, format)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') isodate-0.4.6/src/isodate/tests/test_duration.py0000644000000000000000000005036211701464611020125 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' Test cases for the isoduration module. ''' import unittest import operator from datetime import timedelta, date, datetime from isodate import Duration, parse_duration, ISO8601Error from isodate import D_DEFAULT, D_WEEK, D_ALT_EXT, duration_isoformat # the following list contains tuples of ISO duration strings and the expected # result from the parse_duration method. A result of None means an ISO8601Error # is expected. PARSE_TEST_CASES = {'P18Y9M4DT11H9M8S': (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18), D_DEFAULT, None), 'P2W': (timedelta(weeks=2), D_WEEK, None), 'P3Y6M4DT12H30M5S': (Duration(4, 5, 0, 0, 30, 12, 0, 6, 3), D_DEFAULT, None), 'P23DT23H': (timedelta(hours=23, days=23), D_DEFAULT, None), 'P4Y': (Duration(years=4), D_DEFAULT, None), 'P1M': (Duration(months=1), D_DEFAULT, None), 'PT1M': (timedelta(minutes=1), D_DEFAULT, None), 'P0.5Y': (Duration(years=0.5), D_DEFAULT, None), 'PT36H': (timedelta(hours=36), D_DEFAULT, 'P1DT12H'), 'P1DT12H': (timedelta(days=1, hours=12), D_DEFAULT, None), '+P11D': (timedelta(days=11), D_DEFAULT, 'P11D'), '-P2W': (timedelta(weeks=-2), D_WEEK, None), '-P2.2W': (timedelta(weeks=-2.2), D_DEFAULT, '-P15DT9H36M'), 'P1DT2H3M4S': (timedelta(days=1, hours=2, minutes=3, seconds=4), D_DEFAULT, None), 'P1DT2H3M': (timedelta(days=1, hours=2, minutes=3), D_DEFAULT, None), 'P1DT2H': (timedelta(days=1, hours=2), D_DEFAULT, None), 'PT2H': (timedelta(hours=2), D_DEFAULT, None), 'PT2.3H': (timedelta(hours=2.3), D_DEFAULT, 'PT2H18M'), 'PT2H3M4S': (timedelta(hours=2, minutes=3, seconds=4), D_DEFAULT, None), 'PT3M4S': (timedelta(minutes=3, seconds=4), D_DEFAULT, None), 'PT22S': (timedelta(seconds=22), D_DEFAULT, None), 'PT22.22S': (timedelta(seconds=22.22), 'PT%S.%fS', 'PT22.220000S'), '-P2Y': (Duration(years=-2), D_DEFAULT, None), '-P3Y6M4DT12H30M5S': (Duration(-4, -5, 0, 0, -30, -12, 0, -6, -3), D_DEFAULT, None), '-P1DT2H3M4S': (timedelta(days=-1, hours=-2, minutes=-3, seconds=-4), D_DEFAULT, None), # alternative format 'P0018-09-04T11:09:08': (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18), D_ALT_EXT, None), #'PT000022.22': timedelta(seconds=22.22), } # d1 d2 '+', '-', '>' # A list of test cases to test addition and subtraction between datetime and # Duration objects. # each tuple contains 2 duration strings, and a result string for addition and # one for subtraction. The last value says, if the first duration is greater # than the second. MATH_TEST_CASES = (('P5Y7M1DT9H45M16.72S', 'PT27M24.68S', 'P5Y7M1DT10H12M41.4S', 'P5Y7M1DT9H17M52.04S', None), ('PT28M12.73S', 'PT56M29.92S', 'PT1H24M42.65S', '-PT28M17.19S', False), ('P3Y7M23DT5H25M0.33S', 'PT1H1.95S', 'P3Y7M23DT6H25M2.28S', 'P3Y7M23DT4H24M58.38S', None), ('PT1H1.95S', 'P3Y7M23DT5H25M0.33S', 'P3Y7M23DT6H25M2.28S', '-P3Y7M23DT4H24M58.38S', None), ('P1332DT55M0.33S', 'PT1H1.95S', 'P1332DT1H55M2.28S', 'P1331DT23H54M58.38S', True), ('PT1H1.95S', 'P1332DT55M0.33S', 'P1332DT1H55M2.28S', '-P1331DT23H54M58.38S', False)) # A list of test cases to test addition and subtraction of date/datetime # and Duration objects. They are tested against the results of an # equal long timedelta duration. DATE_TEST_CASES = ( (date(2008, 2, 29), timedelta(days=10, hours=12, minutes=20), Duration(days=10, hours=12, minutes=20)), (date(2008, 1, 31), timedelta(days=10, hours=12, minutes=20), Duration(days=10, hours=12, minutes=20)), (datetime(2008, 2, 29), timedelta(days=10, hours=12, minutes=20), Duration(days=10, hours=12, minutes=20)), (datetime(2008, 1, 31), timedelta(days=10, hours=12, minutes=20), Duration(days=10, hours=12, minutes=20)), (datetime(2008, 4, 21), timedelta(days=10, hours=12, minutes=20), Duration(days=10, hours=12, minutes=20)), (datetime(2008, 5, 5), timedelta(days=10, hours=12, minutes=20), Duration(days=10, hours=12, minutes=20)), (datetime(2000, 1, 1), timedelta(hours=-33), Duration(hours=-33)), (datetime(2008, 5, 5), Duration(years=1, months=1, days=10, hours=12, minutes=20), Duration(months=13, days=10, hours=12, minutes=20)), (datetime(2000, 3, 30), Duration(years=1, months=1, days=10, hours=12, minutes=20), Duration(months=13, days=10, hours=12, minutes=20)), ) # A list of test cases of additon of date/datetime and Duration. The results # are compared against a given expected result. DATE_CALC_TEST_CASES = ( (date(2000, 2, 1), Duration(years=1, months=1), date(2001, 3, 1)), (date(2000, 2, 29), Duration(years=1, months=1), date(2001, 3, 29)), (date(2000, 2, 29), Duration(years=1), date(2001, 2, 28)), (date(1996, 2, 29), Duration(years=4), date(2000, 2, 29)), (date(2096, 2, 29), Duration(years=4), date(2100, 2, 28)), (date(2000, 2, 1), Duration(years=-1, months=-1), date(1999, 1, 1)), (date(2000, 2, 29), Duration(years=-1, months=-1), date(1999, 1, 29)), (date(2000, 2, 1), Duration(years=1, months=1, days=1), date(2001, 3, 2)), (date(2000, 2, 29), Duration(years=1, months=1, days=1), date(2001, 3, 30)), (date(2000, 2, 29), Duration(years=1, days=1), date(2001, 3, 1)), (date(1996, 2, 29), Duration(years=4, days=1), date(2000, 3, 1)), (date(2096, 2, 29), Duration(years=4, days=1), date(2100, 3, 1)), (date(2000, 2, 1), Duration(years=-1, months=-1, days=-1), date(1998, 12, 31)), (date(2000, 2, 29), Duration(years=-1, months=-1, days=-1), date(1999, 1, 28)), (date(2001, 4, 1), Duration(years=-1, months=-1, days=-1), date(2000, 2, 29)), (date(2000, 4, 1), Duration(years=-1, months=-1, days=-1), date(1999, 2, 28)), (Duration(years=1, months=2), Duration(years=0, months=0, days=1), Duration(years=1, months=2, days=1)), (Duration(years=-1, months=-1, days=-1), date(2000, 4, 1), date(1999, 2, 28)), (Duration(years=1, months=1, weeks=5), date(2000, 1, 30), date(2001, 4, 4)), (Duration(years=1, months=1, weeks=5), 'raise exception', None), ('raise exception', Duration(years=1, months=1, weeks=5), None), (Duration(years=1, months=2), timedelta(days=1), Duration(years=1, months=2, days=1)), (timedelta(days=1), Duration(years=1, months=2), Duration(years=1, months=2, days=1)), #(date(2000, 1, 1), # Duration(years=1.5), # date(2001, 6, 1)), #(date(2000, 1, 1), # Duration(years=1, months=1.5), # date(2001, 2, 14)), ) class DurationTest(unittest.TestCase): ''' This class tests various other aspects of the isoduration module, which are not covered with the test cases listed above. ''' def test_associative(self): ''' Adding 2 durations to a date is not associative. ''' days1 = Duration(days=1) months1 = Duration(months=1) start = date(2000, 3, 30) res1 = start + days1 + months1 res2 = start + months1 + days1 self.assertNotEqual(res1, res2) def test_typeerror(self): ''' Test if TypError is raised with certain parameters. ''' self.assertRaises(TypeError, parse_duration, date(2000, 1, 1)) self.assertRaises(TypeError, operator.sub, Duration(years=1), date(2000, 1, 1)) self.assertRaises(TypeError, operator.sub, 'raise exc', Duration(years=1)) def test_parseerror(self): ''' Test for unparseable duration string. ''' self.assertRaises(ISO8601Error, parse_duration, 'T10:10:10') def test_repr(self): ''' Test __repr__ and __str__ for Duration obqects. ''' dur = Duration(10, 10, years=10, months=10) self.assertEqual('10 years, 10 months, 10 days, 0:00:10', str(dur)) self.assertEqual('isodate.duration.Duration(10, 10, 0,' ' years=10, months=10)', repr(dur)) def test_neg(self): ''' Test __neg__ for Duration objects. ''' self.assertEqual(-Duration(0), Duration(0)) self.assertEqual(-Duration(years=1, months=1), Duration(years=-1, months=-1)) self.assertEqual(-Duration(years=1, months=1), Duration(months=-13)) self.assertNotEqual(-Duration(years=1), timedelta(days=-365)) self.assertNotEqual(-timedelta(days=365), Duration(years=-1)) # FIXME: this test fails in python 3... it seems like python3 # treats a == b the same b == a #self.assertNotEqual(-timedelta(days=10), -Duration(days=10)) def test_format(self): ''' Test various other strftime combinations. ''' self.assertEqual(duration_isoformat(Duration(0)), 'P0D') self.assertEqual(duration_isoformat(-Duration(0)), 'P0D') self.assertEqual(duration_isoformat(Duration(seconds=10)), 'PT10S') self.assertEqual(duration_isoformat(Duration(years=-1, months=-1)), '-P1Y1M') self.assertEqual(duration_isoformat(-Duration(years=1, months=1)), '-P1Y1M') self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)), 'P1Y1M') self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)), 'P1Y1M') dur = Duration(years=3, months=7, days=23, hours=5, minutes=25, milliseconds=330) self.assertEqual(duration_isoformat(dur), 'P3Y7M23DT5H25M0.33S') self.assertEqual(duration_isoformat(-dur), '-P3Y7M23DT5H25M0.33S') def test_equal(self): ''' Test __eq__ and __ne__ methods. ''' self.assertEqual(Duration(years=1, months=1), Duration(years=1, months=1)) self.assertEqual(Duration(years=1, months=1), Duration(months=13)) self.assertNotEqual(Duration(years=1, months=2), Duration(years=1, months=1)) self.assertNotEqual(Duration(years=1, months=1), Duration(months=14)) self.assertNotEqual(Duration(years=1), timedelta(days=365)) self.assertFalse(Duration(years=1, months=1) != Duration(years=1, months=1)) self.assertFalse(Duration(years=1, months=1) != Duration(months=13)) self.assertTrue(Duration(years=1, months=2) != Duration(years=1, months=1)) self.assertTrue(Duration(years=1, months=1) != Duration(months=14)) self.assertTrue(Duration(years=1) != timedelta(days=365)) self.assertEqual(Duration(days=1), timedelta(days=1)) # FIXME: this test fails in python 3... it seems like python3 # treats a != b the same b != a #self.assertNotEqual(timedelta(days=1), Duration(days=1)) def create_parsetestcase(durationstring, expectation, format, altstr): """ Create a TestCase class for a specific test. This allows having a separate TestCase for each test tuple from the PARSE_TEST_CASES list, so that a failed test won't stop other tests. """ class TestParseDuration(unittest.TestCase): ''' A test case template to parse an ISO duration string into a timedelta or Duration object. ''' def test_parse(self): ''' Parse an ISO duration string and compare it to the expected value. ''' result = parse_duration(durationstring) self.assertEqual(result, expectation) def test_format(self): ''' Take duration/timedelta object and create ISO string from it. This is the reverse test to test_parse. ''' if altstr: self.assertEqual(duration_isoformat(expectation, format), altstr) else: # if durationstring == '-P2W': # import pdb; pdb.set_trace() self.assertEqual(duration_isoformat(expectation, format), durationstring) return unittest.TestLoader().loadTestsFromTestCase(TestParseDuration) def create_mathtestcase(dur1, dur2, resadd, ressub, resge): """ Create a TestCase class for a specific test. This allows having a separate TestCase for each test tuple from the MATH_TEST_CASES list, so that a failed test won't stop other tests. """ dur1 = parse_duration(dur1) dur2 = parse_duration(dur2) resadd = parse_duration(resadd) ressub = parse_duration(ressub) class TestMathDuration(unittest.TestCase): ''' A test case template test addition, subtraction and > operators for Duration objects. ''' def test_add(self): ''' Test operator + (__add__, __radd__) ''' self.assertEqual(dur1 + dur2, resadd) def test_sub(self): ''' Test operator - (__sub__, __rsub__) ''' self.assertEqual(dur1 - dur2, ressub) def test_ge(self): ''' Test operator > and < ''' def dogetest(): ''' Test greater than.''' return dur1 > dur2 def doletest(): ''' Test less than.''' return dur1 < dur2 if resge is None: self.assertRaises(TypeError, dogetest) self.assertRaises(TypeError, doletest) else: self.assertEqual(dogetest(), resge) self.assertEqual(doletest(), not resge) return unittest.TestLoader().loadTestsFromTestCase(TestMathDuration) def create_datetestcase(start, tdelta, duration): """ Create a TestCase class for a specific test. This allows having a separate TestCase for each test tuple from the DATE_TEST_CASES list, so that a failed test won't stop other tests. """ class TestDateCalc(unittest.TestCase): ''' A test case template test addition, subtraction operators for Duration objects. ''' def test_add(self): ''' Test operator +. ''' self.assertEqual(start + tdelta, start + duration) def test_sub(self): ''' Test operator -. ''' self.assertEqual(start - tdelta, start - duration) return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc) def create_datecalctestcase(start, duration, expectation): """ Create a TestCase class for a specific test. This allows having a separate TestCase for each test tuple from the DATE_CALC_TEST_CASES list, so that a failed test won't stop other tests. """ class TestDateCalc(unittest.TestCase): ''' A test case template test addition operators for Duration objects. ''' def test_calc(self): ''' Test operator +. ''' if expectation is None: self.assertRaises(TypeError, operator.add, start, duration) else: self.assertEqual(start + duration, expectation) return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc) def test_suite(): ''' Return a test suite containing all test defined above. ''' suite = unittest.TestSuite() for durationstring, (expectation, format, altstr) in PARSE_TEST_CASES.items(): suite.addTest(create_parsetestcase(durationstring, expectation, format, altstr)) for testdata in MATH_TEST_CASES: suite.addTest(create_mathtestcase(*testdata)) for testdata in DATE_TEST_CASES: suite.addTest(create_datetestcase(*testdata)) for testdata in DATE_CALC_TEST_CASES: suite.addTest(create_datecalctestcase(*testdata)) suite.addTest(unittest.TestLoader().loadTestsFromTestCase(DurationTest)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') isodate-0.4.6/src/isodate/tests/test_time.py0000644000000000000000000001450211701464611017232 0ustar 00000000000000############################################################################## # Copyright 2009, Gerhard Weis # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of the authors nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT ############################################################################## ''' Test cases for the isotime module. ''' import unittest from datetime import time from isodate import parse_time, UTC, FixedOffset, ISO8601Error, time_isoformat from isodate import TIME_BAS_COMPLETE, TIME_BAS_MINUTE from isodate import TIME_EXT_COMPLETE, TIME_EXT_MINUTE from isodate import TIME_HOUR from isodate import TZ_BAS, TZ_EXT, TZ_HOUR # the following list contains tuples of ISO time strings and the expected # result from the parse_time method. A result of None means an ISO8601Error # is expected. TEST_CASES = [('232050', time(23, 20, 50), TIME_BAS_COMPLETE + TZ_BAS), ('23:20:50', time(23, 20, 50), TIME_EXT_COMPLETE + TZ_EXT), ('2320', time(23, 20), TIME_BAS_MINUTE), ('23:20', time(23, 20), TIME_EXT_MINUTE), ('23', time(23), TIME_HOUR), ('232050,5', time(23, 20, 50, 500000), None), ('23:20:50.5', time(23, 20, 50, 500000), None), # test precision ('15:33:42.123456', time(15, 33, 42, 123456), None), ('15:33:42.1234564', time(15, 33, 42, 123456), None), ('15:33:42.1234557', time(15, 33, 42, 123456), None), ('2320,8', time(23, 20, 48), None), ('23:20,8', time(23, 20, 48), None), ('23,3', time(23, 18), None), ('232030Z', time(23, 20, 30, tzinfo=UTC), TIME_BAS_COMPLETE + TZ_BAS), ('2320Z', time(23, 20, tzinfo=UTC), TIME_BAS_MINUTE + TZ_BAS), ('23Z', time(23, tzinfo=UTC), TIME_HOUR + TZ_BAS), ('23:20:30Z', time(23, 20, 30, tzinfo=UTC), TIME_EXT_COMPLETE + TZ_EXT), ('23:20Z', time(23, 20, tzinfo=UTC), TIME_EXT_MINUTE + TZ_EXT), ('152746+0100', time(15, 27, 46, tzinfo=FixedOffset(1, 0, '+0100')), TIME_BAS_COMPLETE + TZ_BAS), ('152746-0500', time(15, 27, 46, tzinfo=FixedOffset(-5, 0, '-0500')), TIME_BAS_COMPLETE + TZ_BAS), ('152746+01', time(15, 27, 46, tzinfo=FixedOffset(1, 0, '+01:00')), TIME_BAS_COMPLETE + TZ_HOUR), ('152746-05', time(15, 27, 46, tzinfo=FixedOffset(-5, -0, '-05:00')), TIME_BAS_COMPLETE + TZ_HOUR), ('15:27:46+01:00', time(15, 27, 46, tzinfo=FixedOffset(1, 0, '+01:00')), TIME_EXT_COMPLETE + TZ_EXT), ('15:27:46-05:00', time(15, 27, 46, tzinfo=FixedOffset(-5, -0, '-05:00')), TIME_EXT_COMPLETE + TZ_EXT), ('15:27:46+01', time(15, 27, 46, tzinfo=FixedOffset(1, 0, '+01:00')), TIME_EXT_COMPLETE + TZ_HOUR), ('15:27:46-05', time(15, 27, 46, tzinfo=FixedOffset(-5, -0, '-05:00')), TIME_EXT_COMPLETE + TZ_HOUR), ('1:17:30', None, TIME_EXT_COMPLETE)] def create_testcase(timestring, expectation, format): """ Create a TestCase class for a specific test. This allows having a separate TestCase for each test tuple from the TEST_CASES list, so that a failed test won't stop other tests. """ class TestTime(unittest.TestCase): ''' A test case template to parse an ISO time string into a time object. ''' def test_parse(self): ''' Parse an ISO time string and compare it to the expected value. ''' if expectation is None: self.assertRaises(ISO8601Error, parse_time, timestring) else: result = parse_time(timestring) self.assertEqual(result, expectation) def test_format(self): ''' Take time object and create ISO string from it. This is the reverse test to test_parse. ''' if expectation is None: self.assertRaises(AttributeError, time_isoformat, expectation, format) elif format is not None: self.assertEqual(time_isoformat(expectation, format), timestring) return unittest.TestLoader().loadTestsFromTestCase(TestTime) def test_suite(): ''' Construct a TestSuite instance for all test cases. ''' suite = unittest.TestSuite() for timestring, expectation, format in TEST_CASES: suite.addTest(create_testcase(timestring, expectation, format)) return suite if __name__ == '__main__': unittest.main(defaultTest='test_suite') isodate-0.4.6/src/isodate/tzinfo.py0000644000000000000000000000654111701464611015410 0ustar 00000000000000''' This module provides some datetime.tzinfo implementations. All those classes are taken from the Python documentation. ''' from datetime import timedelta, tzinfo import time ZERO = timedelta(0) # constant for zero time offset. class Utc(tzinfo): '''UTC Universal time coordinated time zone. ''' def utcoffset(self, dt): ''' Return offset from UTC in minutes east of UTC, which is ZERO for UTC. ''' return ZERO def tzname(self, dt): ''' Return the time zone name corresponding to the datetime object dt, as a string. ''' return "UTC" def dst(self, dt): ''' Return the daylight saving time (DST) adjustment, in minutes east of UTC. ''' return ZERO UTC = Utc() # the default instance for UTC. class FixedOffset(tzinfo): ''' A class building tzinfo objects for fixed-offset time zones. Note that FixedOffset(0, "UTC") is a different way to build a UTC tzinfo object. ''' def __init__(self, offset_hours, offset_minutes, name): ''' Initialise an instance with time offset and name. The time offset should be positive for time zones east of UTC and negate for time zones west of UTC. ''' self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) self.__name = name def utcoffset(self, dt): ''' Return offset from UTC in minutes of UTC. ''' return self.__offset def tzname(self, dt): ''' Return the time zone name corresponding to the datetime object dt, as a string. ''' return self.__name def dst(self, dt): ''' Return the daylight saving time (DST) adjustment, in minutes east of UTC. ''' return ZERO def __repr__(self): ''' Return nicely formatted repr string. ''' return "" % self.__name STDOFFSET = timedelta(seconds = -time.timezone) # locale time zone offset # calculate local daylight saving offset if any. if time.daylight: DSTOFFSET = timedelta(seconds = -time.altzone) else: DSTOFFSET = STDOFFSET DSTDIFF = DSTOFFSET - STDOFFSET # difference between local time zone and local DST time zone class LocalTimezone(tzinfo): ''' A class capturing the platform's idea of local time. ''' def utcoffset(self, dt): ''' Return offset from UTC in minutes of UTC. ''' if self._isdst(dt): return DSTOFFSET else: return STDOFFSET def dst(self, dt): ''' Return daylight saving offset. ''' if self._isdst(dt): return DSTDIFF else: return ZERO def tzname(self, dt): ''' Return the time zone name corresponding to the datetime object dt, as a string. ''' return time.tzname[self._isdst(dt)] def _isdst(self, dt): ''' Returns true if DST is active for given datetime object dt. ''' tt = (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.weekday(), 0, -1) stamp = time.mktime(tt) tt = time.localtime(stamp) return tt.tm_isdst > 0 LOCAL = LocalTimezone() # the default instance for local time zone.