def add_weekdays(self, numDays: int): """ Returns a new date that is numDays working days after Date. Note that only weekends are taken into account. Other Holidays are not. If you want to include regional holidays then use add_business_days from the FinCalendar class. """ # TODO: REMOVE DATETIME DEPENDENCE HERE if isinstance(numDays, int) is False: raise FinError("Num days must be an integer") positiveNumDays = (numDays > 0) numDays = abs(numDays) # 5 week days make up a week numWeeks = int(numDays / 5) remainingDays = numDays % 5 if (positiveNumDays): if (self._weekday + remainingDays > self.FRI): # add weekend remainingDays += 2 return self.add_days(numWeeks * 7 + remainingDays) else: if (self._weekday - remainingDays < self.MON): # add weekend remainingDays += 2 return self.add_days(-(numWeeks * 7 + remainingDays))
def add_months(self, mm: (list, int)): """ Returns a new date that is mm months after the Date. If mm is an integer or float you get back a single date. If mm is a vector you get back a vector of dates.""" num_months = 1 scalarFlag = False if isinstance(mm, int) or isinstance(mm, float): mmVector = [mm] scalarFlag = True else: mmVector = mm num_months = len(mmVector) dateList = [] for i in range(0, num_months): mmi = mmVector[i] # If I get a float I check it has no decimal places if int(mmi) != mmi: raise FinError("Must only pass integers or float integers.") mmi = int(mmi) d = self._d m = self._m + mmi y = self._y while m > 12: m = m - 12 y += 1 while m < 1: m = m + 12 y -= 1 leap_year = is_leap_year(y) if leap_year: if d > monthDaysLeapYear[m - 1]: d = monthDaysLeapYear[m-1] else: if d > monthDaysNotLeapYear[m - 1]: d = monthDaysNotLeapYear[m-1] newDt = Date(d, m, y) dateList.append(newDt) if scalarFlag is True: return dateList[0] else: return dateList
def days_in_month(m, y): """ Get the number of days in the month (1-12) of a given year y. """ if m < 1 or m > 12: raise FinError("Month must be 1-12") if is_leap_year(y) is False: return monthDaysNotLeapYear[m - 1] else: return monthDaysLeapYear[m - 1]
def add_hours(self, hours): """ Returns a new date that is h hours after the Date. """ if hours < 0: raise FinError("Number of hours must be positive") startHour = self._hh finalHour = startHour + hours days = int(finalHour / 24) hour = finalHour % 24 # Move forward a specific number of days dt1 = self.add_days(days) # On that date we then move to the correct hour dt2 = Date(dt1._d, dt1._m, dt1._y, hour, dt1._mm, dt1._ss) return dt2
def third_wednesday_of_month(self, m: int, # Month number y: int): # Year number """ For a specific month and year this returns the day number of the 3rd Wednesday by scanning through dates in the third week. """ # Suppose 1st is Weds then 8th is Wed and 15th is 3rd Wed # Suppose 1st is Thur then 7th is Wed and 14th is 2nd Wed so 21 is 3rd # so earliest and latest dates are 15th and 21st d_start = 15 d_end = 21 for d in range(d_start, d_end+1): immDate = Date(d, m, y) if immDate._weekday == self.WED: return d # Should never reach this line but just to be defensive raise FinError("Third Wednesday not found")
def print(self, *args): """ Print comma separated output to GOLDEN or COMPARE directory. """ if self._mode == FinTestCaseMode.DEBUG_TEST_CASES: print(args) return if not self._foldersExist: print("Cannot print as GOLDEN and COMPARE folders don't exist") return if self._headerFields is None: print("ERROR: Need to set header fields before printing results") elif len(self._headerFields) != len(args): n1 = len(self._headerFields) n2 = len(args) raise FinError("ERROR: Number of data columns is " + str(n1) + " but must equal " + str(n2) + " to align with headers.") if self._mode == FinTestCaseMode.SAVE_TEST_CASES: filename = self._goldenFilename else: filename = self._compareFilename f = open(filename, 'a') f.write("RESULTS,") for arg in args: if isinstance(arg, float): f.write("%10.8f" % (arg)) else: f.write(str(arg)) f.write(",") f.write("\n") f.close()
def __repr__(self): """ returns a formatted string of the date """ global gDateFormatType dayNameStr = short_day_names[self._weekday] if self._d < 10: dayStr = "0" + str(self._d) else: dayStr = "" + str(self._d) if self._m < 10: shortMonthStr = "0" + str(self._m) else: shortMonthStr = str(self._m) longMonthStr = shortMonthNames[self._m - 1] shortYearStr = str(self._y)[2:] longYearStr = str(self._y) if gDateFormatType == DateFormatTypes.UK_LONGEST: sep = " " date_str = dayNameStr + " " + dayStr + sep + longMonthStr + sep + longYearStr return date_str elif gDateFormatType == DateFormatTypes.UK_LONG: sep = "-" date_str = dayStr + sep + longMonthStr + sep + longYearStr return date_str elif gDateFormatType == DateFormatTypes.UK_MEDIUM: sep = "/" date_str = dayStr + sep + shortMonthStr + sep + longYearStr return date_str elif gDateFormatType == DateFormatTypes.UK_SHORT: sep = "/" date_str = dayStr + sep + shortMonthStr + sep + shortYearStr return date_str elif gDateFormatType == DateFormatTypes.US_LONGEST: sep = " " date_str = dayNameStr + " " + longMonthStr + sep + dayStr + sep + longYearStr return date_str elif gDateFormatType == DateFormatTypes.US_LONG: sep = "-" date_str = longMonthStr + sep + dayStr + sep + longYearStr return date_str elif gDateFormatType == DateFormatTypes.US_MEDIUM: sep = "-" date_str = shortMonthStr + sep + dayStr + sep + longYearStr return date_str elif gDateFormatType == DateFormatTypes.US_SHORT: sep = "-" date_str = shortMonthStr + sep + dayStr + sep + shortYearStr return date_str elif gDateFormatType == DateFormatTypes.BLOOMBERG: sep = "/" date_str = shortMonthStr + sep + dayStr + sep + shortYearStr return date_str elif gDateFormatType == DateFormatTypes.DATETIME: sep = "/" if self._hh < 10: hourStr = "0" + str(self._hh) else: hourStr = str(self._hh) if self._mm < 10: minuteStr = "0" + str(self._mm) else: minuteStr = str(self._mm) if self._ss < 10: secondStr = "0" + str(self._ss) else: secondStr = str(self._ss) timeStr = hourStr + ":" + minuteStr + ":" + secondStr date_str = dayStr + sep + shortMonthStr + sep + longYearStr date_str = date_str + " " + timeStr return date_str else: raise FinError("Unknown date format")
def add_tenor(self, tenor: (list, str)): """ Return the date following the Date by a period given by the tenor which is a string consisting of a number and a letter, the letter being d, w, m , y for day, week, month or year. This is case independent. For example 10Y means 10 years while 120m also means 10 years. The date is NOT weekend or holiday calendar adjusted. This must be done AFTERWARDS. """ listFlag = False if isinstance(tenor, list) is True: listFlag = True for ten in tenor: if isinstance(ten, str) is False: raise FinError("Tenor must be a string e.g. '5Y'") else: if isinstance(tenor, str) is True: tenor = [tenor] else: raise FinError("Tenor must be a string e.g. '5Y'") newDates = [] for tenStr in tenor: tenStr = tenStr.upper() DAYS = 1 WEEKS = 2 MONTHS = 3 YEARS = 4 periodType = 0 num_periods = 0 if tenStr == "ON": # overnight - should be used only if spot days = 0 periodType = DAYS num_periods = 1 elif tenStr == "TN": # overnight - should be used when spot days > 0 periodType = DAYS num_periods = 1 elif tenStr[-1] == "D": periodType = DAYS num_periods = int(tenStr[0:-1]) elif tenStr[-1] == "W": periodType = WEEKS num_periods = int(tenStr[0:-1]) elif tenStr[-1] == "M": periodType = MONTHS num_periods = int(tenStr[0:-1]) elif tenStr[-1] == "Y": periodType = YEARS num_periods = int(tenStr[0:-1]) else: raise FinError("Unknown tenor type in " + tenor) newDate = Date(self._d, self._m, self._y) if periodType == DAYS: for _ in range(0, num_periods): newDate = newDate.add_days(1) elif periodType == WEEKS: for _ in range(0, num_periods): newDate = newDate.add_days(7) elif periodType == MONTHS: for _ in range(0, num_periods): newDate = newDate.add_months(1) elif periodType == YEARS: for _ in range(0, num_periods): newDate = newDate.add_months(12) newDates.append(newDate) if listFlag is True: return newDates else: return newDates[0]
def __init__(self, d, m, y, hh=0, mm=0, ss=0): """ Create a date given a day of month, month and year. The arguments must be in the order of day (of month), month number and then the year. The year must be a 4-digit number greater than or equal to 1900. The user can also supply an hour, minute and second for intraday work. Example Input: start_date = Date(1, 1, 2018) """ global gStartYear global gEndYear # If the date has been entered as y, m, d we flip it to d, m, y # This message should be removed after a few releases if d >= gStartYear and d < gEndYear and y > 0 and y <= 31: raise FinError( "Date arguments must now be in the order Date(dd, mm, yyyy)") if gDateCounterList is None: calculate_list() if y < 1900: raise FinError("Year cannot be before 1900") # Resize date list dynamically if required if y < gStartYear: gStartYear = y calculate_list() if y > gEndYear: gEndYear = y calculate_list() if y < gStartYear or y > gEndYear: raise FinError("Date: year " + str(y) + " should be " + str(gStartYear) + " to " + str(gEndYear)) if d < 1: raise FinError("Date: Leap year. Day not valid.") leap_year = is_leap_year(y) if leap_year: if d > monthDaysLeapYear[m - 1]: print(d, m, y) raise FinError("Date: Leap year. Day not valid.") else: if d > monthDaysNotLeapYear[m - 1]: print(d, m, y) raise FinError("Date: Not Leap year. Day not valid.") if hh < 0 or hh > 23: raise FinError("Hours must be in range 0-23") if mm < 0 or mm > 59: raise FinError("Minutes must be in range 0-59") if ss < 0 or ss > 59: raise FinError("Seconds must be in range 0-59") self._y = y self._m = m self._d = d self._hh = hh self._mm = mm self._ss = ss self._excel_date = 0 # This is a float as it includes intraday time # update the excel date used for doing lots of financial calculations self._refresh() dayFraction = self._hh / 24.0 dayFraction += self._mm / 24.0 / 60.0 dayFraction += self._ss / 24.0 / 60.0 / 60.0 self._excel_date += dayFraction # This is a float as it includes intraday time
def __init__(self, moduleName, mode): """ Create the TestCase given the module name and whether we are in GOLDEN or COMPARE mode. """ rootFolder, moduleFilename = split(moduleName) self._carefulMode = False self._verbose = False if mode in FinTestCaseMode: self._mode = mode else: raise FinError("Unknown TestCase Mode") if mode == FinTestCaseMode.DEBUG_TEST_CASES: # Don't do anything self._verbose = True return self._moduleName = moduleFilename[0:-3] self._foldersExist = True self._rootFolder = rootFolder self._headerFields = None self._globalNumWarnings = 0 self._globalNumErrors = 0 # print("Root folder:",self._rootFolder) # print("Modulename:",self._moduleName) self._goldenFolder = join(rootFolder, "golden") self._differencesFolder = join(rootFolder, "differences") if exists(self._goldenFolder) is False: print("Looking for:", self._goldenFolder) print("GOLDEN Folder DOES NOT EXIST. You must create it. Exiting") self._foldersExist = False return None self._compareFolder = join(rootFolder, "compare") if exists(self._compareFolder) is False: print("Looking for:", self._compareFolder) print("COMPARE Folder DOES NOT EXIST. You must create it. Exiting") self._foldersExist = False return None self._goldenFilename = join(self._goldenFolder, self._moduleName + "_GOLDEN.testLog") self._compareFilename = join(self._compareFolder, self._moduleName + "_COMPARE.testLog") self._differencesFilename = join(self._differencesFolder, self._moduleName + "_DIFFS.testLog") if self._mode == FinTestCaseMode.SAVE_TEST_CASES: print("GOLDEN Test Case Creation for module:", moduleFilename) if exists(self._goldenFilename) and self._carefulMode: overwrite = input("File " + self._goldenFilename + " exists. Overwrite (Y/N) ?") if overwrite == "N": print("Not overwriting. Saving test cases failed.") return elif overwrite == "Y": print("Overwriting existing file...") creationTime = time.strftime("%Y%m%d_%H%M%S") f = open(self._goldenFilename, 'w') f.write("File Created on:" + creationTime) f.write("\n") f.close() else: # print("GENERATING NEW OUTPUT FOR MODULE", moduleFilename, # "FOR COMPARISON.") if exists(self._compareFilename) and self._carefulMode: overwrite = input("File " + self._compareFilename + " exists. Overwrite (Y/N) ?") if overwrite == "N": print("Not overwriting. Saving test cases failed.") return elif overwrite == "Y": print("Overwriting existing file...") # print("Creating empty file",self._compareFilename) creationTime = time.strftime("%Y%m%d_%H%M%S") f = open(self._compareFilename, 'w') f.write("File Created on:" + creationTime) f.write("\n") f.close()
def add_weekdays(self, numDays: int): """ Returns a new date that is numDays working days after Date. Note that only weekends are taken into account. Other Holidays are not. If you want to include regional holidays then use add_business_days from the FinCalendar class. """ # TODO: REMOVE DATETIME DEPENDENCE HERE end_date = self; if isinstance(numDays, int) is False: raise FinError("Num days must be an integer") positiveNumDays = (numDays > 0) numDays = abs(numDays) # 5 week days make up a week oldLogic = False if oldLogic is True: numWeeks = int(numDays / 5) remainingDays = numDays % 5 if self._weekday == Date.SAT: weekendAdjust = 1 elif self._weekday == Date.SUN: weekendAdjust = 0 else: weekendAdjust = 2 if (positiveNumDays): if (self._weekday + remainingDays > self.FRI): # add weekend remainingDays += weekendAdjust return self.add_days(numWeeks * 7 + remainingDays) else: if (self._weekday - remainingDays < self.MON): # add weekend remainingDays += weekendAdjust return self.add_days(-(numWeeks * 7 + remainingDays)) else: # new logic numDaysLeft = numDays end_date = self while numDaysLeft > 0: if positiveNumDays is True: end_date = end_date.add_days(1) else: end_date = end_date.add_days(-1) if end_date._weekday == Date.SAT or end_date._weekday == Date.SUN: pass else: numDaysLeft -= 1 return end_date