Блог о программировании

Работа с производственным календарем на PHP

Категория: PHP
 2 марта 2018 г. 23:15

Бывают ситуации, когда необходимо рассчитать дату с учетом именно рабочих, а не календарных дней. На какую-либо алгоритмизацию здесь полагаться достоверно не приходится, поскольку правительство РФ то и дело штампует исключения из правил. То новогодние выходные перенесут на май, то субботу сделают рабочей, а выходной перенесут на будний день. И так каждый год! И все эти ситуации необходимо учитывать.

В интернете на этот счет есть несколько решений, но все они либо некорректные, либо совершенно неудобные. Несколько таких решений: Один, Два и Три.

В связи с таким положением дел и был написан класс WorkCalendar, позволяющий удобно работать с производственным календарем. WorkCalendar расширяет возможности класса Carbon\Carbon, поэтому работать с ним одно удовольствие. Итак, добавленные методы:

  • isWorkday(): bool - true, если день рабочий, иначе false. Один из наиболее удобных методов. Позволяет выяснить, является ли текущий день рабочим. Пример:
    $workday = WorkCalendar::create('2018', '02', '22');
    print_r($workday->isWorkday()); // true
    
    $workday = WorkCalendar::create('2018', '02', '23');
    print_r($date->isWorkday()); // true

  • diffInWorkdays(WorkCalendar $carbon): int - возвращает разницу в рабочих днях между двумя датами. Может возвращать отрицательное значение, если передаваемая дата меньше(раньше) текущей. Пример:
    $first = WorkCalendar::create('2018', '02', '23');
    $second = WorkCalendar::create('2018', '02', '08');
    print_r($first->diffInWorkdays($second)); // 8

  • addWorkday() - добавить рабочий день к текущей дате. Пример:
    $first = WorkCalendar::create('2018', '02', '26');
    $first->subWorkday();
    print_r($first->format('Y-m-d')); // 2018-02-22

  • subWorkday() - отнять рабочий день от текущей даты. Пример:
    $first = WorkCalendar::create('2018', '02', '26');
    $first->subWorkday();
    print_r($first->format('Y-m-d')); // 2018-02-22

  • addWorkdays(int $count) - добавить $count рабочих дней к текущей дате. Крайне полезен в ситуациях "через 10 рабочих дней", "в течение 5 рабочих дней", коими грешат наши государственные конторы. Пример:
    $first = WorkCalendar::create('2018', '03', '01');
    $first->addWorkdays(5);
    print_r($first->format('Y-m-d')); // 2018-03-12

  • subWorkdays(int $count) - отнять $count рабочих дней от текущей даты. Пример:
    $first = WorkCalendar::create('2018', '03', '12');
    $first->subWorkdays(5);
    print_r($first->format('Y-m-d')); // 2018-03-01

Устанавливается данный хелпер через composer:

$ composer require tochka-developers/work-calendar

Текущие ограничения

Для корректной работы класса, ему нужно откуда-то брать информацию о всех праздничных днях. Этим источником является xmlcalendar.ru. Так вот, там есть данные только начиная с 2013 года. Также данный хелпер учитывает только официальные праздники РФ, но не какие-либо региональные праздники.



Собственный производственный календарь

Производственный календарь использует массив из 365 элементов, который может принимать 0 в случае, если день является праздничным, и 1 иначе. Соответственно, чтобы использовать собственный производственный календарь основанный на национальных праздниках, например, Республики Казахстан, для этого нужно сделать несколько вещей:

  • Определить класс, который будет отвечать за формирование маски рабочих дней. Для этого необходимо отнаследоваться от класса Tochka\Calendar\AbstractYearMaskProvider и определить метод generateYearMask(int $year), который и занимается формированием массива с маской рабочих дней.
  • В новом классе переопределить константу RES_DIR, указывающую, в какую директорию необходимо сохранять маски рабочих дней, чтобы постоянно их не генерировать.
  • При использовании рабочего календаря с помощью метода Tochka\Calendar\WorkCalendar::setMaskProvider(AbstractYearMaskProvider $provider) указать вновь созданный класс.

Пример:

...
// Определяем новый класс, который генерирует 
// необходимый нам производственный календарь
class KzYearMaskProvider extends AbstractYearMaskProvider
{
	const RES_DIR = __DIR__ . '/../resources/kz/';

	protected function generateYearMask(int $year)
	{
		$mask = array_fill(0, 365, 0);
		
		// Тут должна быть логика выявления 
		// рабочих дней и их указания в маске $mask 
		
		return $mask;
	}
}

WorkCalendar::setMaskProvider(new KzYearMaskProvider());

// Дальше можем использовать WorkCalendar
$date = WorkCalendar::now();
$date->addWorkdays(5); // Добавляем 5 рабочих дней, используя маску KzYearMaskProvider
...
Ссылки

Ссылка на github.

Ссылка на packagist.

Поделиться статьей

Оставить комментарий