diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/isodate/duration.py | 143 | ||||
-rw-r--r-- | src/isodate/isoduration.py | 4 | ||||
-rw-r--r-- | src/isodate/isotime.py | 6 | ||||
-rw-r--r-- | src/isodate/tests/__init__.py | 1 | ||||
-rw-r--r-- | src/isodate/tests/test_date.py | 1 | ||||
-rw-r--r-- | src/isodate/tests/test_datetime.py | 1 | ||||
-rw-r--r-- | src/isodate/tests/test_duration.py | 5 | ||||
-rw-r--r-- | src/isodate/tests/test_pickle.py | 13 | ||||
-rw-r--r-- | src/isodate/tests/test_strf.py | 1 | ||||
-rw-r--r-- | src/isodate/tests/test_time.py | 1 | ||||
-rw-r--r-- | src/isodate/tzinfo.py | 17 |
11 files changed, 113 insertions, 80 deletions
diff --git a/src/isodate/duration.py b/src/isodate/duration.py index 5408315..c47b964 100644 --- a/src/isodate/duration.py +++ b/src/isodate/duration.py @@ -30,7 +30,7 @@ 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 +from datetime import timedelta from decimal import Decimal, ROUND_FLOOR @@ -121,7 +121,10 @@ class Duration(object): if self.years: params.append('%d years' % self.years) if self.months: - params.append('%d months' % self.months) + fmt = "%d months" + if self.months <= 1: + fmt = "%d month" + params.append(fmt % self.months) params.append(str(self.tdelta)) return ', '.join(params) @@ -156,16 +159,15 @@ class Duration(object): 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)): + try: + # try anything that looks like a date or datetime + # other has attributes year, month, day + # and relies on 'timedelta + other' being implemented if (not(float(self.years).is_integer() and float(self.months).is_integer())): raise ValueError('fractional years or months not supported' @@ -179,35 +181,24 @@ class Duration(object): else: newday = other.day newdt = other.replace(year=newyear, month=newmonth, day=newday) + # does a timedelta + date/datetime 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): + except AttributeError: + # other probably was not a date/datetime compatible object + pass + try: + # try if other is a timedelta + # relies on timedelta + timedelta supported newduration = Duration(years=self.years, months=self.months) newduration.tdelta = self.tdelta + other return newduration - if isinstance(other, (date, datetime)): - if (not(float(self.years).is_integer() and - float(self.months).is_integer())): - raise ValueError('fractional years or months not supported' - ' for date calculations') - 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__)) + except AttributeError: + # ignore ... other probably was not a timedelt compatible object + pass + # we have tried everything .... return a NotImplemented + return NotImplemented + + __radd__ = __add__ def __mul__(self, other): if isinstance(other, int): @@ -216,19 +207,9 @@ class Duration(object): months=self.months * other) newduration.tdelta = self.tdelta * other return newduration - raise TypeError('unsupported operand type(s) for *: %s and %s' % - (self.__class__, other.__class__)) + return NotImplemented - def __rmul__(self, other): - - if isinstance(other, int): - newduration = Duration( - years=self.years * other, - months=self.months * other) - newduration.tdelta = self.tdelta * other - return newduration - raise TypeError('unsupported operand type(s) for *: %s and %s' % - (other.__class__, self.__class__)) + __rmul__ = __mul__ def __sub__(self, other): ''' @@ -240,20 +221,37 @@ class Duration(object): months=self.months - other.months) newduration.tdelta = self.tdelta - other.tdelta return newduration - if isinstance(other, timedelta): + try: + # do maths with our timedelta object .... 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__)) + except TypeError: + # looks like timedelta - other is not implemented + pass + return NotImplemented def __rsub__(self, other): ''' It is possible to subtract Duration objecs from date, datetime and timedelta objects. + + TODO: there is some weird behaviour in date - timedelta ... + if timedelta has seconds or microseconds set, then + date - timedelta != date + (-timedelta) + for now we follow this behaviour to avoid surprises when mixing + timedeltas with Durations, but in case this ever changes in + the stdlib we can just do: + return -self + other + instead of all the current code ''' - # print '__rsub__:', self, other - if isinstance(other, (date, datetime)): + if isinstance(other, timedelta): + tmpdur = Duration() + tmpdur.tdelta = other + return tmpdur - self + try: + # check if other behaves like a date/datetime object + # does it have year, month, day and replace? if (not(float(self.years).is_integer() and float(self.months).is_integer())): raise ValueError('fractional years or months not supported' @@ -268,27 +266,26 @@ class Duration(object): 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__)) + except AttributeError: + # other probably was not compatible with data/datetime + pass + return NotImplemented 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)): + if isinstance(other, Duration): + if (((self.years * 12 + self.months) == + (other.years * 12 + other.months) and + self.tdelta == other.tdelta)): + return True + return False + # check if other con be compared against timedelta object + # will raise an AssertionError when optimisation is off + if 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): @@ -296,17 +293,17 @@ class Duration(object): 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)): + if isinstance(other, Duration): + if (((self.years * 12 + self.months) != + (other.years * 12 + other.months) or + self.tdelta != other.tdelta)): + return True + return False + # check if other can be compared against timedelta object + # will raise an AssertionError when optimisation is off + if 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 + return True def totimedelta(self, start=None, end=None): ''' diff --git a/src/isodate/isoduration.py b/src/isodate/isoduration.py index 6da69f5..88829f7 100644 --- a/src/isodate/isoduration.py +++ b/src/isodate/isoduration.py @@ -34,6 +34,8 @@ from datetime import timedelta from decimal import Decimal import re +from six import string_types + from isodate.duration import Duration from isodate.isoerror import ISO8601Error from isodate.isodatetime import parse_datetime @@ -80,7 +82,7 @@ def parse_duration(datestring): The alternative format does not support durations with years, months or days set to 0. """ - if not isinstance(datestring, basestring): + if not isinstance(datestring, string_types): raise TypeError("Expecting a string %r" % datestring) match = ISO8601_PERIOD_REGEX.match(datestring) if not match: diff --git a/src/isodate/isotime.py b/src/isodate/isotime.py index 9650cda..113d34b 100644 --- a/src/isodate/isotime.py +++ b/src/isodate/isotime.py @@ -125,7 +125,7 @@ def parse_time(timestring): if 'second' in groups: # round to microseconds if fractional seconds are more precise second = Decimal(groups['second']).quantize(Decimal('.000001')) - microsecond = (second - int(second)) * long(1e6) + microsecond = (second - int(second)) * int(1e6) # int(...) ... no rounding # to_integral() ... rounding return time(int(groups['hour']), int(groups['minute']), @@ -134,7 +134,7 @@ def parse_time(timestring): if 'minute' in groups: minute = Decimal(groups['minute']) second = (minute - int(minute)) * 60 - microsecond = (second - int(second)) * long(1e6) + microsecond = (second - int(second)) * int(1e6) return time(int(groups['hour']), int(minute), int(second), int(microsecond.to_integral()), tzinfo) else: @@ -142,7 +142,7 @@ def parse_time(timestring): hour = Decimal(groups['hour']) minute = (hour - int(hour)) * 60 second = (minute - int(minute)) * 60 - microsecond = (second - int(second)) * long(1e6) + microsecond = (second - int(second)) * int(1e6) return time(int(hour), int(minute), int(second), int(microsecond.to_integral()), tzinfo) raise ISO8601Error('Unrecognised ISO 8601 time format: %r' % timestring) diff --git a/src/isodate/tests/__init__.py b/src/isodate/tests/__init__.py index 09dba2e..b1d46bd 100644 --- a/src/isodate/tests/__init__.py +++ b/src/isodate/tests/__init__.py @@ -46,5 +46,6 @@ def test_suite(): test_pickle.test_suite(), ]) + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/src/isodate/tests/test_date.py b/src/isodate/tests/test_date.py index 2519e68..6ee89cb 100644 --- a/src/isodate/tests/test_date.py +++ b/src/isodate/tests/test_date.py @@ -127,5 +127,6 @@ def test_suite(): def load_tests(loader, tests, pattern): return test_suite() + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/src/isodate/tests/test_datetime.py b/src/isodate/tests/test_datetime.py index ddad5da..3cdfe42 100644 --- a/src/isodate/tests/test_datetime.py +++ b/src/isodate/tests/test_datetime.py @@ -142,5 +142,6 @@ def test_suite(): def load_tests(loader, tests, pattern): return test_suite() + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/src/isodate/tests/test_duration.py b/src/isodate/tests/test_duration.py index 0b80a54..b0e0332 100644 --- a/src/isodate/tests/test_duration.py +++ b/src/isodate/tests/test_duration.py @@ -313,6 +313,10 @@ class DurationTest(unittest.TestCase): 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)) + dur = Duration(months=0) + self.assertEqual('0:00:00', str(dur)) + dur = Duration(months=1) + self.assertEqual('1 month, 0:00:00', str(dur)) def test_hash(self): ''' @@ -597,5 +601,6 @@ def test_suite(): def load_tests(loader, tests, pattern): return test_suite() + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/src/isodate/tests/test_pickle.py b/src/isodate/tests/test_pickle.py index b52f8cb..7fc6213 100644 --- a/src/isodate/tests/test_pickle.py +++ b/src/isodate/tests/test_pickle.py @@ -1,5 +1,7 @@ import unittest -import cPickle as pickle + +from six.moves import cPickle as pickle + import isodate @@ -31,11 +33,17 @@ class TestPickle(unittest.TestCase): pikl = pickle.dumps(dur, proto) if dur != pickle.loads(pikl): raise Exception("not equal") - except Exception, e: + except Exception as e: failed.append("pickle proto %d failed (%s)" % (proto, repr(e))) self.assertEqual(len(failed), 0, "pickle protos failed: %s" % str(failed)) + def test_pickle_utc(self): + ''' + isodate.UTC objects remain the same after pickling. + ''' + self.assertTrue(isodate.UTC is pickle.loads(pickle.dumps(isodate.UTC))) + def test_suite(): ''' @@ -50,5 +58,6 @@ def test_suite(): def load_tests(loader, tests, pattern): return test_suite() + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/src/isodate/tests/test_strf.py b/src/isodate/tests/test_strf.py index 37a135b..1aa76c7 100644 --- a/src/isodate/tests/test_strf.py +++ b/src/isodate/tests/test_strf.py @@ -131,5 +131,6 @@ def test_suite(): def load_tests(loader, tests, pattern): return test_suite() + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/src/isodate/tests/test_time.py b/src/isodate/tests/test_time.py index cc5ec08..7dfd0c0 100644 --- a/src/isodate/tests/test_time.py +++ b/src/isodate/tests/test_time.py @@ -139,5 +139,6 @@ def test_suite(): def load_tests(loader, tests, pattern): return test_suite() + if __name__ == '__main__': unittest.main(defaultTest='test_suite') diff --git a/src/isodate/tzinfo.py b/src/isodate/tzinfo.py index b41f058..5d3a42d 100644 --- a/src/isodate/tzinfo.py +++ b/src/isodate/tzinfo.py @@ -36,10 +36,24 @@ class Utc(tzinfo): ''' return ZERO + def __reduce__(self): + ''' + When unpickling a Utc object, return the default instance below, UTC. + ''' + return _Utc, () + + UTC = Utc() # the default instance for UTC. +def _Utc(): + ''' + Helper function for unpickling a Utc object. + ''' + return UTC + + class FixedOffset(tzinfo): ''' A class building tzinfo objects for fixed-offset time zones. @@ -138,5 +152,6 @@ class LocalTimezone(tzinfo): tt = time.localtime(stamp) return tt.tm_isdst > 0 -LOCAL = LocalTimezone() + # the default instance for local time zone. +LOCAL = LocalTimezone() |