def __add__(self, period): if isinstance(period, Period): return _advance(self, period.length, period.units) elif isinstance(period, int): return Date(serialNumber=self.__serialNumber__ + period) else: period = Period(period) return _advance(self, period.length, period.units)
def __sub__(self, period): if isinstance(period, Period): return _advance(self, -period.length, period.units) elif isinstance(period, int): return Date(serial_number=self.__serialNumber__ - period) elif isinstance(period, Date): return self.__serialNumber__ - period.__serialNumber__ else: period = Period(period) return _advance(self, -period.length, period.units)
def testPeriodPickle(self): p1 = Period('36m') f = tempfile.NamedTemporaryFile('w+b', delete=False) pickle.dump(p1, f) f.close() with open(f.name, 'rb') as f2: pickled_period = pickle.load(f2) self.assertEqual(p1, pickled_period) os.unlink(f.name)
def advanceDate(self, d, period, c=BizDayConventions.Following, endOfMonth=False): if isinstance(period, str): period = Period(period) n = period.length units = period.units if n == 0: return self.adjustDate(d, c) elif units == TimeUnits.BDays: d1 = d if n > 0: while n > 0: d1 += 1 while self.isHoliday(d1): d1 += 1 n -= 1 else: while n < 0: d1 -= 1 while self.isHoliday(d1): d1 -= 1 n += 1 return d1 elif units == TimeUnits.Days or units == TimeUnits.Weeks: d1 = d + period return self.adjustDate(d1, c) else: d1 = d + period if endOfMonth and self.isEndOfMonth(d): return self.endOfMonth(d1) return self.adjustDate(d1, c)
def testPeriodDeepCopy(self): p1 = Period('36m') p2 = copy.deepcopy(p1) self.assertEqual(p1, p2)
def testWeeksDaysAlgebra(self): two_weeks = Period(length=2, units=TimeUnits.Weeks) one_week = Period(length=1, units=TimeUnits.Weeks) three_days = Period(length=3, units=TimeUnits.Days) one_day = Period(length=1, units=TimeUnits.Days) n = 2 flag = two_weeks / n == one_week self.assertTrue(flag, "division error: {0} / {1:d}" " not equal to {2}".format(two_weeks, n, one_week)) n = 7 flag = one_week / 7 == one_day self.assertTrue(flag, "division error: {0} / {1:d}" " not equal to {2}".format(one_week, n, one_day)) sum = three_days sum += one_day flag = sum == Period(length=4, units=TimeUnits.Days) self.assertTrue(flag, "sum error: {0}" " + {1}" " != {2}".format(three_days, one_day, Period(length=4, units=TimeUnits.Days))) sum += one_week flag = sum == Period(length=11, units=TimeUnits.Days) self.assertTrue(flag, "sum error: {0}" " + {1}" " + {2}" " != {3}".format(three_days, one_day, one_week, Period(length=11, units=TimeUnits.Days))) sevenDays = Period(length=7, units=TimeUnits.Days) flag = sevenDays.length == 7 self.assertTrue(flag, "normalization error: sevenDays.length" " is {0:d}" " instead of 7".format(sevenDays.length)) flag = sevenDays.units == TimeUnits.Days self.assertTrue(flag, "normalization error: sevenDays.units" " is {0:d}" " instead of {1:d}".format(sevenDays.units, TimeUnits.Days)) normalized_seven_days = sevenDays.normalize() flag = normalized_seven_days.length == 1 self.assertTrue(flag, "normalization error: normalized_seven_days.length" " is {0:d}" " instead of 1".format(normalized_seven_days.length)) flag = normalized_seven_days.units == TimeUnits.Weeks self.assertTrue(flag, "normalization error: TwelveMonths.units" " is {0:d}" " instead of {1:d}".format(normalized_seven_days.units, TimeUnits.Weeks))
def testYearsMonthsAlgebra(self): one_year = Period(length=1, units=TimeUnits.Years) six_months = Period(length=6, units=TimeUnits.Months) three_months = Period(length=3, units=TimeUnits.Months) n = 4 flag = one_year / n == three_months self.assertTrue(flag, "division error: {0} / {1:d}" " not equal to {2}".format(one_year, n, three_months)) n = 2 flag = one_year / n == six_months self.assertTrue(flag, "division error: {0} / {1:d}" " not equal to {2}".format(one_year, n, six_months)) test_sum = three_months test_sum += six_months flag = test_sum == Period(length=9, units=TimeUnits.Months) self.assertTrue(flag, "test_sum error: {0}" " + {1}" " != {2}".format(three_months, six_months, Period(length=9, units=TimeUnits.Months))) test_sum += one_year flag = test_sum == Period(length=21, units=TimeUnits.Months) self.assertTrue(flag, "test_sum error: {0}" " + {1}" " + {2}" " != {3}".format(three_months, six_months, one_year, Period(length=21, units=TimeUnits.Months))) twelve_months = Period(length=12, units=TimeUnits.Months) flag = twelve_months.length == 12 self.assertTrue(flag, "normalization error: TwelveMonths.length" " is {0:d}" " instead of 12".format(twelve_months.length)) flag = twelve_months.units == TimeUnits.Months self.assertTrue(flag, "normalization error: TwelveMonths.units" " is {0:d}" " instead of {1:d}".format(twelve_months.units, TimeUnits.Months)) normalized_twelve_months = Period(length=12, units=TimeUnits.Months) normalized_twelve_months = normalized_twelve_months.normalize() flag = normalized_twelve_months.length == 1 self.assertTrue(flag, "normalization error: TwelveMonths.length" " is {0:d}" " instead of 1".format(twelve_months.length)) flag = normalized_twelve_months.units == TimeUnits.Years self.assertTrue(flag, "normalization error: TwelveMonths.units" " is {0:d}" " instead of {1:d}".format(twelve_months.units, TimeUnits.Years)) thirty_days = Period(length=30, units=TimeUnits.Days) normalized_thirty_days = thirty_days.normalize() flag = normalized_thirty_days.units == TimeUnits.Days self.assertTrue(flag, "normalization error: ThirtyDays.units" " is {0:d}" " instead of {1:d}".format(normalized_thirty_days.units, TimeUnits.Days)) thirty_b_days = Period(length=30, units=TimeUnits.BDays) normalized_thirty_b_days = thirty_b_days.normalize() flag = normalized_thirty_b_days.units == TimeUnits.BDays self.assertTrue(flag, "normalization error: ThirtyBDays.units" " is {0:d}" " instead of {1:d}".format(normalized_thirty_b_days.units, TimeUnits.BDays))
def testComparingOperators(self): p1 = Period(length=0, units=TimeUnits.Days) p2 = Period(length=1, units=TimeUnits.Days) self.assertTrue(p1 < p2) p1 = Period(length=13, units=TimeUnits.Months) p2 = Period(length=1, units=TimeUnits.Years) self.assertTrue(not p1 < p2) p1 = Period(length=1, units=TimeUnits.Years) p2 = Period(length=13, units=TimeUnits.Months) self.assertTrue(p1 < p2) p1 = Period(length=13, units=TimeUnits.Days) p2 = Period(length=2, units=TimeUnits.Weeks) self.assertTrue(p1 < p2) p1 = Period(length=2, units=TimeUnits.Weeks) p2 = Period(length=13, units=TimeUnits.Days) self.assertTrue(not p1 < p2) p1 = Period(length=1, units=TimeUnits.Years) p2 = Period(length=56, units=TimeUnits.Weeks) self.assertTrue(p1 < p2) p1 = Period(length=56, units=TimeUnits.Weeks) p2 = Period(length=1, units=TimeUnits.Years) self.assertTrue(not p1 < p2) p1 = Period(length=21, units=TimeUnits.Weeks) p2 = Period(length=5, units=TimeUnits.Months) with self.assertRaises(ValueError): _ = p1 < p2 p1 = Period(length=21, units=TimeUnits.BDays) with self.assertRaises(ValueError): _ = p1 < p2 # test not equal operator p1 = Period(length=1, units=TimeUnits.Days) p2 = Period(length=1, units=TimeUnits.Days) self.assertTrue(not p1 != p2) p2 = Period(length=1, units=TimeUnits.Years) self.assertTrue(p1 != p2) # test greater than operator p1 = Period(length=1, units=TimeUnits.Days) p2 = Period(length=2, units=TimeUnits.Days) self.assertEqual(p1 < p2, not p1 > p2)
def testBasicArithmetic(self): # test bad normalize test_period = Period(length=1, units=TimeUnits.Years) test_period._units = 10 with self.assertRaises(TypeError): test_period.normalize() # test plus method p1 = Period(length=0, units=TimeUnits.Days) p2 = Period(length=10, units=TimeUnits.Months) calculated = p1 + p2 self.assertEqual(p2, calculated, "added value {0} should be equal to {1}".format(calculated, p2)) p1 = Period(length=2, units=TimeUnits.Years) p2 = Period(length=13, units=TimeUnits.Months) calculated = p1 + p2 expected = Period(length=37, units=TimeUnits.Months) self.assertEqual(expected, calculated, "added value {0} should be equal to {1}".format(calculated, expected)) p2 = Period(length=2, units=TimeUnits.Weeks) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.BDays) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.Days) with self.assertRaises(ValueError): _ = p1 + p2 p2._units = 10 with self.assertRaises(ValueError): _ = p1 + p2 p1 = Period(length=13, units=TimeUnits.Months) p2 = Period(length=2, units=TimeUnits.Years) calculated = p1 + p2 expected = Period(length=37, units=TimeUnits.Months) self.assertEqual(expected, calculated, "added value {0} should be equal to {1}".format(calculated, expected)) p2 = Period(length=2, units=TimeUnits.Weeks) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.BDays) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.Days) with self.assertRaises(ValueError): _ = p1 + p2 p2._units = 10 with self.assertRaises(ValueError): _ = p1 + p2 p1 = Period(length=2, units=TimeUnits.Weeks) p2 = Period(length=7, units=TimeUnits.Days) calculated = p1 + p2 expected = Period(length=21, units=TimeUnits.Days) self.assertEqual(expected, calculated, "added value {0} should be equal to {1}".format(calculated, expected)) p2 = Period(length=2, units=TimeUnits.Months) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.BDays) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.Years) with self.assertRaises(ValueError): _ = p1 + p2 p2._units = 10 with self.assertRaises(ValueError): _ = p1 + p2 p1 = Period(length=7, units=TimeUnits.Days) p2 = Period(length=2, units=TimeUnits.Weeks) calculated = p1 + p2 expected = Period(length=21, units=TimeUnits.Days) self.assertEqual(expected, calculated, "added value {0} should be equal to {1}".format(calculated, expected)) p2 = Period(length=2, units=TimeUnits.Months) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.BDays) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.Years) with self.assertRaises(ValueError): _ = p1 + p2 p2._units = 10 with self.assertRaises(ValueError): _ = p1 + p2 p1 = Period(length=7, units=TimeUnits.BDays) p2 = Period(length=2, units=TimeUnits.Months) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.Days) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.Weeks) with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.Years) with self.assertRaises(ValueError): _ = p1 + p2 p2._units = 10 with self.assertRaises(ValueError): _ = p1 + p2 p2 = Period(length=2, units=TimeUnits.BDays) self.assertEqual(p1 + p2, Period('9B')) # test negative operator p1 = Period(length=-13, units=TimeUnits.Weeks) p2 = -p1 self.assertEqual(p2, Period(length=13, units=TimeUnits.Weeks)) # test less operator p1 = Period(length=0, units=TimeUnits.Days) p2 = Period(length=-3, units=TimeUnits.BDays) self.assertTrue(p2 < p1) # test sub operator p1 = Period(length=0, units=TimeUnits.Days) p2 = Period(length=-3, units=TimeUnits.BDays) self.assertEqual(p1 - p2, Period('3b')) # test string representation p1 = Period(length=12, units=TimeUnits.Months) self.assertEqual("1Y", p1.__str__())
def __init__(self, effectiveDate, terminationDate, tenor, calendar, convention=BizDayConventions.Following, terminationConvention=BizDayConventions.Following, dateGenerationRule=DateGeneration.Forward, endOfMonth=False, firstDate=None, nextToLastDate=None, evaluationDate=None): # Initialize private data self._effectiveDate = effectiveDate self._terminationDate = terminationDate self._tenor = tenor self._cal = calendar self._convention = convention self._terminationConvention = terminationConvention self._rule = dateGenerationRule self._dates = [] self._isRegular = [] if tenor < Period("1M"): self._endOfMonth = False else: self._endOfMonth = endOfMonth if firstDate is None or firstDate == effectiveDate: self._firstDate = None else: self._firstDate = firstDate if nextToLastDate is None or nextToLastDate == terminationDate: self._nextToLastDate = None else: self._nextToLastDate = nextToLastDate # in many cases (e.g. non-expired bonds) the effective date is not # really necessary. In these cases a decent placeholder is enough if effectiveDate is None and firstDate is None and dateGenerationRule == DateGeneration.Backward: evalDate = evaluationDate py_assert(evalDate < terminationDate, ValueError, "null effective date") if nextToLastDate is not None: y = int((nextToLastDate - evalDate) / 366) + 1 effectiveDate = nextToLastDate - Period(length=y, units=TimeUnits.Years) else: y = int((terminationDate - evalDate) / 366) + 1 effectiveDate = terminationDate - Period(length=y, units=TimeUnits.Years) else: py_assert(effectiveDate is not None, ValueError, "null effective date") py_assert( effectiveDate < terminationDate, ValueError, "effective date ({0}) " "later than or equal to termination date ({1}".format( effectiveDate, terminationDate)) if tenor.length == 0: self._rule = DateGeneration.Zero else: py_assert( tenor.length > 0, ValueError, "non positive tenor ({0:d}) not allowed".format(tenor.length)) if self._firstDate is not None: if self._rule == DateGeneration.Backward or self._rule == DateGeneration.Forward: py_assert( effectiveDate < self._firstDate < terminationDate, ValueError, "first date ({0}) out of effective-termination date range [{1}, {2})" .format(self._firstDate, effectiveDate, terminationDate)) # we should ensure that the above condition is still # verified after adjustment elif self._rule == DateGeneration.Zero: raise ValueError( "first date incompatible with {0:d} date generation rule". format(self._rule)) else: raise ValueError("unknown rule ({0:d})".format(self._rule)) if self._nextToLastDate is not None: if self._rule == DateGeneration.Backward or self._rule == DateGeneration.Forward: py_assert( effectiveDate < self._nextToLastDate < terminationDate, ValueError, "next to last date ({0}) out of effective-termination date range [{1}, {2})" .format(self._nextToLastDate, effectiveDate, terminationDate)) # we should ensure that the above condition is still # verified after adjustment elif self._rule == DateGeneration.Zero: raise ValueError( "next to last date incompatible with {0:d} date generation rule" .format(self._rule)) else: raise ValueError("unknown rule ({0:d})".format(self._rule)) # calendar needed for endOfMonth adjustment nullCalendar = Calendar("Null") periods = 1 if self._rule == DateGeneration.Zero: self._tenor = Period(length=0, units=TimeUnits.Years) self._dates.extend([effectiveDate, terminationDate]) self._isRegular.append(True) elif self._rule == DateGeneration.Backward: self._dates.append(terminationDate) seed = terminationDate if self._nextToLastDate is not None: self._dates.insert(0, self._nextToLastDate) temp = nullCalendar.advanceDate( seed, Period(length=-periods * self._tenor.length, units=self._tenor.units), convention, self._endOfMonth) if temp != self._nextToLastDate: self._isRegular.insert(0, False) else: self._isRegular.insert(0, True) seed = self._nextToLastDate exitDate = effectiveDate if self._firstDate is not None: exitDate = self._firstDate while True: temp = nullCalendar.advanceDate( seed, Period(length=-periods * self._tenor.length, units=self._tenor.units), convention, self._endOfMonth) if temp < exitDate: if self._firstDate is not None and self._cal.adjustDate( self._dates[0], convention) != self._cal.adjustDate( self._firstDate, convention): self._dates.insert(0, self._firstDate) self._isRegular.insert(0, False) break else: # skip dates that would result in duplicates # after adjustment if self._cal.adjustDate( self._dates[0], convention) != self._cal.adjustDate( temp, convention): self._dates.insert(0, temp) self._isRegular.insert(0, True) periods += 1 if self._cal.adjustDate(self._dates[0], convention) != self._cal.adjustDate( effectiveDate, convention): self._dates.insert(0, effectiveDate) self._isRegular.insert(0, False) elif self._rule == DateGeneration.Forward: self._dates.append(effectiveDate) seed = self._dates[-1] if self._firstDate is not None: self._dates.append(self._firstDate) temp = nullCalendar.advanceDate( seed, Period(length=periods * self._tenor.length, units=self._tenor.units), convention, self._endOfMonth) if temp != self._firstDate: self._isRegular.append(False) else: self._isRegular.append(True) seed = self._firstDate exitDate = terminationDate if self._nextToLastDate is not None: exitDate = self._nextToLastDate while True: temp = nullCalendar.advanceDate( seed, Period(length=periods * self._tenor.length, units=self._tenor.units), convention, self._endOfMonth) if temp > exitDate: if self._nextToLastDate is not None and self._cal.adjustDate( self._dates[-1], convention) != self._cal.adjustDate( self._nextToLastDate, convention): self._dates.append(self._nextToLastDate) self._isRegular.append(False) break else: # skip dates that would result in duplicates # after adjustment if self._cal.adjustDate( self._dates[-1], convention) != self._cal.adjustDate( temp, convention): self._dates.append(temp) self._isRegular.append(True) periods += 1 if self._cal.adjustDate( self._dates[-1], terminationConvention) != self._cal.adjustDate( terminationDate, terminationConvention): self._dates.append(terminationDate) self._isRegular.append(False) else: raise ValueError("unknown rule ({0:d})".format(self._rule)) # adjustments if self._endOfMonth and self._cal.isEndOfMonth(seed): # adjust to end of month if convention == BizDayConventions.Unadjusted: for i in range(len(self._dates) - 1): self._dates[i] = Date.endOfMonth(self._dates[i]) else: for i in range(len(self._dates) - 1): self._dates[i] = self._cal.endOfMonth(self._dates[i]) if terminationConvention != BizDayConventions.Unadjusted: self._dates[0] = self._cal.endOfMonth(self._dates[0]) self._dates[-1] = self._cal.endOfMonth(self._dates[-1]) else: if self._rule == DateGeneration.Backward: self._dates[-1] = Date.endOfMonth(self._dates[-1]) else: self._dates[0] = Date.endOfMonth(self._dates[0]) else: for i in range(len(self._dates) - 1): self._dates[i] = self._cal.adjustDate(self._dates[i], convention) if terminationConvention != BizDayConventions.Unadjusted: self._dates[-1] = self._cal.adjustDate(self._dates[-1], terminationConvention) # Final safety checks to remove extra next-to-last date, if # necessary. It can happen to be equal or later than the end # date due to EOM adjustments (see the Schedule test suite # for an example). dateLen = len(self._dates) if dateLen >= 2 and self._dates[dateLen - 2] >= self._dates[-1]: self._isRegular[dateLen - 2] = (self._dates[dateLen - 2] == self._dates[-1]) self._dates[dateLen - 2] = self._dates[-1] self._dates.pop() self._isRegular.pop() if len(self._dates) >= 2 and self._dates[1] <= self._dates[0]: self._isRegular[1] = (self._dates[1] == self._dates[0]) self._dates[1] = self._dates[0] self._dates = self._dates[1:] self._isRegular = self._isRegular[1:] py_assert( len(self._dates) >= 1, ValueError, "degenerate single date ({0}) schedule\n" "seed date: {1}\n" "exit date: {2}\n" "effective date: {3}\n" "first date: {4}\n" "next to last date: {5}\n" "termination date: {6}\n" "generation rule: {7}\n" "end of month: {8}\n".format(self._dates[0], seed, exitDate, effectiveDate, firstDate, nextToLastDate, terminationDate, self._rule, self._endOfMonth))