summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/isodate/duration.py143
-rw-r--r--src/isodate/isoduration.py4
-rw-r--r--src/isodate/isotime.py6
-rw-r--r--src/isodate/tests/__init__.py1
-rw-r--r--src/isodate/tests/test_date.py1
-rw-r--r--src/isodate/tests/test_datetime.py1
-rw-r--r--src/isodate/tests/test_duration.py5
-rw-r--r--src/isodate/tests/test_pickle.py13
-rw-r--r--src/isodate/tests/test_strf.py1
-rw-r--r--src/isodate/tests/test_time.py1
-rw-r--r--src/isodate/tzinfo.py17
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()