📜 ⬆️ ⬇️

Python production calendar

image

Foreword



When I was my work as an analyst, my colleagues and I had almost a daily need to calculate the delivery times for completions. The task was like this: calculate the date of delivery of improvements starting from tomorrow + 40 working days. While I was working and managing the analytics department, I didn’t get my hands on automating this function, but now I’ve decided to correct it, especially since this is a wonderful and simple project that will help beginners to become familiar with the basic Python designs.
')
In order not to delay familiarization with this module, just type in the command line:
pip install prod-cal 


I guarantee that the project will work on Python 2.7 and Windows 7, since it was developed on this configuration.

I will not describe how to collect packages and upload them in PyPi, there are sufficiently detailed articles on this topic, I’ll just say that a beginner can cope with this task, so if you’ve been thinking of making a new module, don’t put it on the back burner nothing complicated.

The main purpose of this article is to disassemble the device of this module and to outline the prospects for its development by the community.

In order not to produce calendars in my calendar, you can use all the methods of the standard calendar.Calendar module.


The composition of the project



After installation, the project will be available in C: \ Python27 \ Lib \ site-packages \ prodcal, if you installed the package in a virtual environment, then look for it in: <home directory Wirth. environments> \ Lib \ site-packages \ prodcal

The project can not install at all and download it directly from the site PyPi . After that, unpack and use the code directly in your project.

The project consists of the following files (all with the extension * .py):


Examples of using
 from procal import ProdCal my_first_prod_cal = ProdCal() #    1  my_first_prod_cal.is_work_day(2016, 5, 1) #    my_first_prod_cal.is_work_day(2016, 4, 1) #    my_first_prod_cal.is_work_day(2016, 4, 2) #     ( ) my_first_prod_cal.is_work_day(2016, 2, 20) #     my_first_prod_cal.is_work_day(date(2016, 5, 1) #      (today - ) my_first_prod_cal.is_work_day('today') #      (yesterday - ) my_first_prod_cal.is_work_day('yesterday') #      (tomorrow - ) my_first_prod_cal.is_work_day('tomorrow') #        my_first_prod_cal.count_work_days([2016, 4, 1], [2016, 4, 30]) my_first_prod_cal.count_work_days([2016, 5, 1], [2016, 5, 31]) my_first_prod_cal.count_work_days([2016, 6, 1], [2016, 6, 30]) #        my_first_prod_cal.count_work_days(date(2016, 4, 1), date(2016, 4, 30)) my_first_prod_cal.count_work_days(date(2016, 5, 1), date(2016, 5, 31)) my_first_prod_cal.count_work_days(date(2016, 6, 1), date(2016, 6, 30)) #      (today, yesterday, tomorrow) my_first_prod_cal.count_work_days('today', date(2016, 4, 30)) my_first_prod_cal.count_work_days('yesterday', date(2016, 4, 30)) my_first_prod_cal.count_work_days('tomorrow', date(2016, 4, 30)) #           () my_first_prod_cal.count_work_days([2016, 4, 1], 30) my_first_prod_cal.count_work_days('today', 30) #        my_first_prod_cal.count_holidays([2016, 4, 1], [2016, 4, 30]) my_first_prod_cal.count_holidays([2016, 5, 1], [2016, 5, 31]) my_first_prod_cal.count_holidays([2016, 6, 1], [2016, 6, 30]) #        my_first_prod_cal.count_holidays(date(2016, 4, 1), date(2016, 4, 30)) my_first_prod_cal.count_holidays(date(2016, 5, 1), date(2016, 5, 31)) my_first_prod_cal.count_holidays(date(2016, 6, 1), date(2016, 6, 30)) #      (today, yesterday, tomorrow) my_first_prod_cal.count_holidays('today', date(2016, 4, 30)) my_first_prod_cal.count_holidays('yesterday', date(2016, 4, 30)) my_first_prod_cal.count_holidays('tomorrow', date(2016, 4, 30)) #           () my_first_prod_cal.count_holidays([2016, 4, 1], 30) my_first_prod_cal.count_holidays('today', 30) #       my_first_prod_cal.get_date_by_work_days([2016, 4, 1], 21)) my_first_prod_cal.get_date_by_work_days('today', 21) 



Implementation


Production Calendar Structure


All production calendars are in the subdirectory of prodcals as separate files. The format of the file name is acc. ISO country letter code in lower case. For example, Russia. The production calendar is in the ru.py file.

The file contains two dictionaries: NON_WORK_DAY_DICT and WORK_DAY_DICT, they have the same structure, the first dictionary describes non-working days (holidays), and the second describes the transfer of working days to weekends. Dictionaries do not contain references to “standard” non-working days Saturday and Sunday.
The calendar describes two nested dictionaries: months are invested in a year, the value of the month is a list of days.
For the convenience of working with the calendar, a separate class ProdDict was made (inherited from the standard dictionary) in which the is_value method is implemented, which returns True or False depending on the presence of the transferred value in the dictionary. At the entrance this class only accepts dates. The implementation of the ProdDict class is described in the prod_dict file (located in the prodcals subdirectory).

ProdCal class implementation


This class can be created without specifying any arguments, in this case the default calendar (Russian) will be used. If you need to specify which calendar to use, then you must pass a named argument locale = <value>, where value is the ISO country code in any register. An example for creating a production calendar of Ukraine:
 from prodcal import ProdCal my_prod_cal = ProdCal(locale='UA') 

The calendars of the following countries are currently supported: Belarus, Georgia, Kazakhstan, Russia, Ukraine.

ProdCal class methods

is_work_day


Input: date, list (with int), argument tuple, string (supports only: 'today', tomorrow ',' yesterday ')
Output: bool

Description: Checks the specified date for whether it is a business day.

Note: for convenience, this and all other methods have the ability to transfer dates as arguments in a convenient format, as implemented described in the section describing service functions.

count_work_days, count_holidays


Admission: start date, end date (period), date format described above.
Output: int

Description: calculates the number of working days in a given period (in the case of count_work_days), and in the case of count_holidays the number of days off.

get_date_by_work_days


Login: start date, int
Output: date

Description: Calculates the end date for a given number of working days.

Description of service functions


Let me remind you that the service functions are in the service.py file.
The simplest function get_date_today converts the transferred value to a necessary date, the implementation is the simplest (I suggest rewriting inquiring minds with a more efficient construction, for example, choosing from a dictionary).

 def get_date_today(day): today = datetime.today().date() if 'today' == day: return today elif 'yesterday' == day: return today - timedelta(days=1) elif 'tomorrow' == day: return today + timedelta(days=1) raise ValueError('Unknown string format', day) 


The magic of the possibility of using dates in various formats (if expressed correctly) is implemented in the cast function.
Cast function implementation
 def cast(start_date, end_date): if isinstance(start_date, (tuple, list)) and isinstance(end_date, (tuple, list)): start_date, end_date = date(*start_date), date(*end_date) if isinstance(start_date, str): start_date = get_date_today(start_date) elif isinstance(start_date, (tuple, list)): start_date = date(*start_date) if isinstance(end_date, (tuple, list)): end_date = date(*end_date) elif isinstance(end_date, int): end_date = calc_days_by_int(start_date, end_date) if isinstance(start_date, date) and isinstance(end_date, date): pass else: raise ValueError("Unknown format for parse") 


The whole idea is very simple, check the type of the arguments passed and bring everything to the date and return it. If you do not understand throw an exception.

Another interesting place is the function get_prodcals, which loads the desired calendar from the subdirectory prodcals by the transferred value. This is ensured by using the import_module () function from the standard importlib library, which interprets the passed string as the path to the module. For example: import_module ('prodcal.prodcals.ru') is equivalent to from prodcals import ru. The main point of using this function is not to explicitly indicate which calendars to load, which somewhat facilitates further support.

New calendar support


New calendars are supported by adding new calendars to the config.py file, writing tests and loading the calendar to the prodcals subdirectory. In addition, do nothing more.

Development plans


If we have already taken up some business, then we need to solve it globally: to provide support for all production calendars in the world.

It is also planned to add a number of new functions, for example: calculating the date and time for the transmitted hours, write compatibility tests with Python3 and fix some errors.

For Russian-speaking users, this article can act as a module documentation, but for the rest, you will have to make separate documentation.

For all who would like to participate in the development of this project and this repository is available.

Thanks


In addition to me, Arkady Aristov from Chelyabinsk is participating in this project, for which he thanks a lot!

Source: https://habr.com/ru/post/281040/


All Articles