Package: business-python
Business (Python)Date calculations based on business calendars. (Python 3.8+)Python implementation ofhttps://github.com/gocardless/businessDocumentationTo get business, simply:$pipinstallbusiness-pythonVersion 2.1.0 breaking changesIn version 2.1.0 we have dropped support for End-of-Life Python version 3.6 and 3.7. Last release supporting these versions isv2.0.3.Version 2.0.0 breaking changesIn version 2.0.0 we have removed the bundled calendars. If you still need these they are available onv1.0.1.MigrationDownload/create calendars to a directory within your project eg:lib/calendarsChange your code to include theload_pathfor your calendarsContinue using.load("my_calendar")as usual# lib/calendars contains yml filesCalendar.load_paths=['lib/calendars']calendar=Calendar.load("my_calendar")Getting startedGet started with business by creating an instance of the calendar class, passing in a hash that specifies which days of the week are considered working days, and which days are holidays.frombusiness.calendarimportCalendarcalendar=Calendar(working_days=["monday","tuesday","wednesday","thursday","friday"],# array items are either parseable date strings, or real datetime.date objectsholidays=["January 1st, 2020","April 10th, 2020"],extra_working_dates=[],)extra_working_dateskey makes the calendar to consider a weekend day as a working day.Ifworking_daysis missing, then common default is used (mon-fri).
Ifholidaysis missing, "no holidays" assumed.
Ifextra_working_datesis missing, then no changes inworking_dayswill happen.Elements ofholidaysandextra_working_datesmay be either strings thatCalendar.parse_date()can understand, or YYYY-MM-DD (which is considered as a Date by Python YAML itself).Calendar YAML file example# lib/calendars/my_calendar.ymlworking_days:-Monday-Sundayholidays:-2017-01-08# Same as January 8th, 2017extra_working_dates:-2020-12-26# Will consider 26 Dec 2020 (A Saturday), a working dayTheload_cachemethod allows a thread safe way to avoid reloading the same calendar multiple times, and provides a performant way to dynamically load calendars for different requests.Using business-pythonDefine your calendars in a folder eg:lib/calendarsand set this directory onCalendar.load_paths=Calendar.load_paths=['lib/calendars']calendar=Calendar.load_cache("my_calendar")Input data typesTheparse_datemethod is used to process the input date(s) in each method and return adatetime.dateobject.Calendar.parse_date("2019-01-01")# => datetime.date(2019, 1, 1)Supported data types are:datetime.datedatetime.datetimepandas.Timestamp(treated asdatetime.datetime)date string parseable bydateutil.parser.parsenumpy.datetime64is not supported, but can be converted todatetime.date:numpy.datetime64('2014-06-01T23:00:05.453000000').astype('M8[D]').astype('O')# =>  datetime.date(2014, 6, 1)Checking for business daysTo check whether a given date is a business day (falls on one of the specified working days or extra working dates, and is not a holiday), use theis_business_daymethod onCalendar.calendar.is_business_day("Monday, 8 June 2020")# => truecalendar.is_business_day("Sunday, 7 June 2020")# => falseBusiness day arithmeticFor our purposes, date-based calculations are sufficient. Supporting time-based calculations as well makes the code significantly more complex. We chose to avoid this extra complexity by sticking solely to date-based mathematics.Theadd_business_daysmethod is used to perform business day arithmetic on dates.input_date=Calendar.parse_date("Thursday, 12 June 2014")calendar.add_business_days(input_date,4).strftime("%A,%d%B %Y")# => "Wednesday, 18 June 2014"calendar.add_business_days(input_date,-4).strftime("%A,%d%B %Y")# => "Friday, 06 June 2014"Theroll_forwardandroll_backwardmethods snap a date to a nearby business day. If provided with a business day, they will return that date. Otherwise, they will advance (forward forroll_forwardand backward forroll_backward) until a business day is found.input_date=Calendar.parse_date("Saturday, 14 June 2014")calendar.roll_forward(input_date).strftime("%A,%d%B %Y")# => "Monday, 16 June 2014"calendar.roll_backward(input_date).strftime("%A,%d%B %Y")# => "Friday, 13 June 2014"In contrast, thenext_business_dayandprevious_business_daymethods will always move to a next or previous date until a business day is found, regardless if the input provided is a business day.input_date=Calendar.parse_date("Monday, 9 June 2014")calendar.roll_forward(input_date).strftime("%A,%d%B %Y")# => "Monday, 09 June 2014"calendar.next_business_day(input_date).strftime("%A,%d%B %Y")# => "Tuesday, 10 June 2014"calendar.previous_business_day(input_date).strftime("%A,%d%B %Y")# => "Friday, 06 June 2014"To count the number of business days between two dates, pass the dates tobusiness_days_between. This method counts from start of the first date to start of the second date. So, assuming no holidays, there would be two business days between a Monday and a Wednesday.fromdatetimeimporttimedeltainput_date=Calendar.parse_date("Saturday, 14 June 2014")calendar.business_days_between(input_date,input_date+timedelta(days=7))# => 5Theget_business_day_of_monthmethod return the running total of business days for a given date in that month. This method counts the number of business days from the start of the first day of the month to the given input date.input_date=Calendar.parse_date("Thursday, 12 June 2014")calendar.get_business_day_of_month(input_date)# => 9License & ContributingThis is available as open source under the terms of theMIT License.Bug reports and pull requests are welcome on GitHub athttps://github.com/gocardless/business-python.GoCardless ♥ open source. If you do too, comejoin us.