def __init__(self, year=None, month=None, day=None, serialNumber=None): # do the input validation if serialNumber: self.__serialNumber__ = serialNumber y = (int(serialNumber / 365)) + 1900 if serialNumber <= _YearOffset[y - 1900]: y -= 1 d = serialNumber - _YearOffset[y - 1900] m = int(d / 30) + 1 leap = Date.isLeap(y) while d <= _month_offset(m, leap): m -= 1 py_assert( 1900 < y < 2200, ValueError, "year {0:d} is out of bound. It must be in [1901, 2199]". format(y)) self._year = y self._month = m self._day = d - _month_offset(m, leap) return elif serialNumber and (year or month or day): raise ValueError( "When serial number is offered, no year or month or day number should be entered" ) elif not (year and month and day): raise ValueError( "year: {0}, month: {1}, day: {2} can't be null value included". format(year, month, day)) self._calculate_date(year, month, day)
def nthWeekday(nth, dayOfWeek, m, y): py_assert(nth > 0, ValueError, "zeroth day of week in a given (month, year) is undefined") py_assert(nth < 6, ValueError, "no more than 5 weekday in a given (month, year)") first = Date(y, m, 1).weekday() skip = nth - (1 if dayOfWeek >= first else 0) return Date(y, m, (1 + dayOfWeek + skip * 7) - first)
def bizDatesList(self, fromDate, toDate): py_assert( fromDate <= toDate, ValueError, "from date ({0} must be earlier than to date {1}".format( fromDate, toDate)) result = [] d = fromDate while d <= toDate: if self.isBizDay(d): result.append(d) d += 1 return result
def holDatesList(self, fromDate, toDate, includeWeekEnds=True): py_assert( fromDate <= toDate, ValueError, "from date ({0} must be earlier than to date {1}".format( fromDate, toDate)) result = [] d = fromDate while d <= toDate: if self.isHoliday(d) and (includeWeekEnds or not self.isWeekEnd(d.weekday())): result.append(d) d += 1 return result
def __div__(self, n): resunits = self.units reslength = self.length if reslength % n == 0: reslength /= n else: if resunits == TimeUnits.Years: reslength *= 12 resunits = TimeUnits.Months elif resunits == TimeUnits.Weeks: reslength *= 7 resunits = TimeUnits.Days py_assert(reslength % n == 0, ValueError, "{0} cannot be divided by {1:d}".format(self, n)) reslength //= n return Period(length=reslength, units=resunits)
def _advance(date, n, units): if units == TimeUnits.Days or units == TimeUnits.BDays: return Date(serialNumber=date.__serialNumber__ + n) elif units == TimeUnits.Weeks: return Date(serialNumber=date.__serialNumber__ + 7 * n) elif units == TimeUnits.Months: d = date._day m = date._month + n y = date._year added_year = int(floor(m / 12.)) month_left = m % 12 if month_left == 0: month_left = 12 added_year -= 1 y += added_year leap_flag = Date.isLeap(y) py_assert( 1900 < y < 2200, ValueError, 'year {0:d} is out of bound. It must be in [1901, 2199]'.format(y)) length = _MonthLeapLength[month_left - 1] if leap_flag else _MonthLength[month_left - 1] if d > length: d = length return Date(y, month_left, d) elif units == TimeUnits.Years: d = date._day m = date._month y = date._year + n leap_flag = Date.isLeap(y) if y <= 1900 or y >= 2200: return Date(1900, 1, 1) if d == 29 and m == 2 and not leap_flag: d = 28 return Date(y, m, d)
def __add__(self, p2): reslength = self.length resunits = self.units p2length = p2.length p2units = p2.units if reslength == 0: return Period(length=p2length, units=p2units) elif resunits == p2units: reslength += p2length return Period(length=reslength, units=resunits) else: if resunits == TimeUnits.Years: if p2units == TimeUnits.Months: resunits = TimeUnits.Months reslength = reslength * 12 + p2length elif p2units == TimeUnits.Weeks or p2units == TimeUnits.Days or p2units == TimeUnits.BDays: py_assert( p2length == 0, ValueError, "impossible addition between {0} and {1}".format( self, p2)) else: raise ValueError( "unknown time unit ({0:d})".format(p2units)) return Period(length=reslength, units=resunits) elif resunits == TimeUnits.Months: if p2units == TimeUnits.Years: reslength += 12 * p2length elif p2units == TimeUnits.Weeks or p2units == TimeUnits.Days or p2units == TimeUnits.BDays: py_assert( p2length == 0, ValueError, "impossible addition between {0} and {1}".format( self, p2)) else: raise ValueError( "unknown time unit ({0:d})".format(p2units)) return Period(length=reslength, units=resunits) elif resunits == TimeUnits.Weeks: if p2units == TimeUnits.Days: resunits = TimeUnits.Days reslength = reslength * 7 + p2length elif p2units == TimeUnits.Years or p2units == TimeUnits.Months or p2units == TimeUnits.BDays: py_assert( p2length == 0, ValueError, "impossible addition between {0} and {1}".format( self, p2)) else: raise ValueError( "unknown time unit ({0:d})".format(p2units)) return Period(length=reslength, units=resunits) elif resunits == TimeUnits.Days: if p2units == TimeUnits.Weeks: reslength += 7 * p2length elif p2units == TimeUnits.Years or p2units == TimeUnits.Months or p2units == TimeUnits.BDays: py_assert( p2length == 0, ValueError, "impossible addition between {0} and {1}".format( self, p2)) else: raise ValueError( "unknown time unit ({0:d})".format(p2units)) return Period(length=reslength, units=resunits) elif resunits == TimeUnits.BDays: if p2units == TimeUnits.Years or p2units == TimeUnits.Months or p2units == TimeUnits.Weeks or p2units == TimeUnits.Days: py_assert( p2length == 0, ValueError, "impossible addition between {0} and {1}".format( self, p2)) else: raise ValueError( "unknown time unit ({0:d})".format(p2units)) return Period(length=reslength, units=resunits)
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))