Стараюсь писать про Аксапту, хотя частенько тянет в Офис
Функция определения рабочего статуса дня
В системах, с которыми мне довелось работать довольно близко, включая Аксапту, БОСС-Кадровик, некоторые бухгалтерские и биллинговые самописки и т.п., как правило, имеется специальная таблица-календарь, из которой черпается информация о рабочих и выходных днях. Одна запись в такой таблице соответствует одному календарному дню.
Обычно в календаре имеются записи от даты начала работы предприятия или от некоторой другой оговоренной даты, например, с момента старта новой внедренной системы - это то, что касается прошлого. Распространение календаря в будущее не ограничено, но фактически содержит список дней на несколько ближайших лет (5-10, сколько надо исходя из потребностей предприятия). По мере продвижения в будущее в список добавляются дни последующих лет, например, системным администратором, сгенерившем последовательность дней очередного требуемого года в Excel и затем вставившему эти данные в таблицу-календарь, попутно проставив признаки выходных и праздничных дней, а также учтя нестандатные переносы, указанные в очередном ежегодном Постановлении Правительства РФ.
Каждый раз сталкиваясь близко с таким календарем, я невольно задумывался: почему таблица? почему не функция? Ведь существует же, например, в том же Excel функция WORKDAY (РАБДЕНЬ), рассчитывающая дату, отстоящую от исходной на заданное количество рабочих дней. Эта функция даже учтёт праздники, если ей их передать параметром в виде массива дат, правда, не совсем точно для российской реальности - на переносе праздников, попадающих на субботу и воскресенье, на ближайший следующий рабочий день, который предусмотрен статьей 112 нашего Трудового кодекса, функция WORKDAY сдается ("Ааа, блин!" - сказали суровые сибирские мужики ) Однако, сама попытка создания подобной универсальной функции, вызывает заслуженное уважение в адрес команды разработчиков Excel.
При обучении в институте меня, признаться, несколько утомляли аналитические способы решения интегралов. Поэтому когда я приобщился к вычислительной технике и постиг азы численных методов, я с удовольствием перестал напрягать себя аналитически и вычислял интегралы на машине при помощи метода Симпсона или даже трапеций. Примерно также, мне кажется, не стали напрягать себя и разработчики систем, использующие таблицу-календарь вместо функции. В общем-то, понятно - с таблицей как-то нагляднее и надежнее. К тому же структуру записи можно расширить какими-нибудь дополнительными полями и, таким образом, как бы "превзойти" возможности функции. Опять же количество записей в календаре аж на целый век по современным меркам совсем невелико - чуть меньше 37 тысяч.
Казалось бы, смирись, используй таблицу-календарь и не мучайся! Тем не менее, смутное желание создать функцию, учитывающую российские нюансы переносов праздников, периодически теребило меня несколько лет, но каждый раз начавшийся было процесс раздумий прерывался чем-то более насущным и не оставлял после себя никаких набросков, которые можно было бы развить дальше при следующем приступе "заболевания".
Очередное напоминание явилось в виде темы Как учесть только рабочие часы?, и я почувствовал, что в этот раз должно что-то оформиться... И оно-таки оформилось! Прекрасно понимая, что родившаяся функция представляет скорее теоретический интерес - кто ж откажется от своих таблиц-календарей, которые за годы обросли связями с самыми различными фрагментами системы?! - я все же надеюсь, что кому-то она может пригодиться и для практического дела.
Лично мне она понравилась своей компактностью - мне почему-то представлялось, что в подобной функции всяких IF-ов должно быть на порядок больше, чем оно оказалось на самом деле. Может быть, конечно, это из-за того, что в Аксапте есть такие достойнейшие средства, как Map и Set, и без оных как раз куча IF-ов бы и получилась?
Во всяком случае я пока не думал о портации функции в другие языки. Может, через несколько лет...
В рамках демонстрационного примера функция (dayWorkingStatus) вложена в нижеследующий джоб, результатом работы которого будет вывод в окно infolog всех дат годов 2007,2008, 2009 с указанием дня недели и рабочего статуса дня:
Возвращаемые int-значения ("статусы рабочего дня"):
(to be continued...)
Обычно в календаре имеются записи от даты начала работы предприятия или от некоторой другой оговоренной даты, например, с момента старта новой внедренной системы - это то, что касается прошлого. Распространение календаря в будущее не ограничено, но фактически содержит список дней на несколько ближайших лет (5-10, сколько надо исходя из потребностей предприятия). По мере продвижения в будущее в список добавляются дни последующих лет, например, системным администратором, сгенерившем последовательность дней очередного требуемого года в Excel и затем вставившему эти данные в таблицу-календарь, попутно проставив признаки выходных и праздничных дней, а также учтя нестандатные переносы, указанные в очередном ежегодном Постановлении Правительства РФ.
Каждый раз сталкиваясь близко с таким календарем, я невольно задумывался: почему таблица? почему не функция? Ведь существует же, например, в том же Excel функция WORKDAY (РАБДЕНЬ), рассчитывающая дату, отстоящую от исходной на заданное количество рабочих дней. Эта функция даже учтёт праздники, если ей их передать параметром в виде массива дат, правда, не совсем точно для российской реальности - на переносе праздников, попадающих на субботу и воскресенье, на ближайший следующий рабочий день, который предусмотрен статьей 112 нашего Трудового кодекса, функция WORKDAY сдается ("Ааа, блин!" - сказали суровые сибирские мужики ) Однако, сама попытка создания подобной универсальной функции, вызывает заслуженное уважение в адрес команды разработчиков Excel.
При обучении в институте меня, признаться, несколько утомляли аналитические способы решения интегралов. Поэтому когда я приобщился к вычислительной технике и постиг азы численных методов, я с удовольствием перестал напрягать себя аналитически и вычислял интегралы на машине при помощи метода Симпсона или даже трапеций. Примерно также, мне кажется, не стали напрягать себя и разработчики систем, использующие таблицу-календарь вместо функции. В общем-то, понятно - с таблицей как-то нагляднее и надежнее. К тому же структуру записи можно расширить какими-нибудь дополнительными полями и, таким образом, как бы "превзойти" возможности функции. Опять же количество записей в календаре аж на целый век по современным меркам совсем невелико - чуть меньше 37 тысяч.
Казалось бы, смирись, используй таблицу-календарь и не мучайся! Тем не менее, смутное желание создать функцию, учитывающую российские нюансы переносов праздников, периодически теребило меня несколько лет, но каждый раз начавшийся было процесс раздумий прерывался чем-то более насущным и не оставлял после себя никаких набросков, которые можно было бы развить дальше при следующем приступе "заболевания".
Очередное напоминание явилось в виде темы Как учесть только рабочие часы?, и я почувствовал, что в этот раз должно что-то оформиться... И оно-таки оформилось! Прекрасно понимая, что родившаяся функция представляет скорее теоретический интерес - кто ж откажется от своих таблиц-календарей, которые за годы обросли связями с самыми различными фрагментами системы?! - я все же надеюсь, что кому-то она может пригодиться и для практического дела.
Лично мне она понравилась своей компактностью - мне почему-то представлялось, что в подобной функции всяких IF-ов должно быть на порядок больше, чем оно оказалось на самом деле. Может быть, конечно, это из-за того, что в Аксапте есть такие достойнейшие средства, как Map и Set, и без оных как раз куча IF-ов бы и получилась?
Во всяком случае я пока не думал о портации функции в другие языки. Может, через несколько лет...
В рамках демонстрационного примера функция (dayWorkingStatus) вложена в нижеследующий джоб, результатом работы которого будет вывод в окно infolog всех дат годов 2007,2008, 2009 с указанием дня недели и рабочего статуса дня:
X++:
#macrolib.HolidayExceptions // глобальный макрос (см. ниже) static void Job_RussianHolidays(Args _args) { date currDate; Map holidayExceptions = new Map(Types::Date, Types::Integer); //------------------------------------------------------------------------ int dayWorkingStatus(date _currDate, Map _exceptions = null, Set _holidays = null) { Set holidaysBefore = new Set(Types::Date); Set holidaysTransfer = new Set(Types::Date); SetEnumerator enumr; int holiYear, holiMonth, holiDay; date holidayToMove; ; // самыми первыми проверяем исключения // если искомый день среди них, то заканчиваем и возвращаем статус if (_exceptions) { if (_exceptions.exists(_currDate)) return _exceptions.lookup(_currDate); } if (!_holidays) { _holidays = new Set(Types::Container); // Russian holidays _holidays.add([ 1, 1]); // Новогодние каникулы _holidays.add([ 1, 2]); // Новогодние каникулы _holidays.add([ 1, 3]); // Новогодние каникулы _holidays.add([ 1, 4]); // Новогодние каникулы _holidays.add([ 1, 5]); // Новогодние каникулы _holidays.add([ 1, 7]); // Рождество Христово _holidays.add([ 2,23]); // День защитника Отечества _holidays.add([ 3, 8]); // Международный женский день _holidays.add([ 5, 1]); // Праздник Весны и Труда _holidays.add([ 5, 9]); // День Победы _holidays.add([ 6,12]); // День России _holidays.add([11, 4]); // День народного единства } if ( dayOfWk(_currDate)==6 || dayOfWk(_currDate)==7 || _holidays.in([mthOfYr(_currDate), dayOfMth(_currDate)])) return 0; // текущий - выходной (сб,вс или празд) // если уж дошли сюда, то проверям нет ли праздничного переноса // для этого пробегаем по всему множеству "безгодовых" праздников (_holidays) // и формируем последовательность всех праздничных дат до текущей даты (holidaysBefore), // т.е. генерим реальные праздничные даты за годовой период, предшествующий текущей дате enumr = _holidays.getEnumerator(); while (enumr.moveNext()) { [holiMonth, holiDay] = enumr.current(); holiYear = ([mthOfYr(_currDate), dayOfMth(_currDate)] > [holiMonth, holiDay]) ? (year(_currDate)) : (year(_currDate)-1); holidaysBefore.add(mkDate(holiDay, holiMonth, holiYear)); } // обрабатываем сдвиги выходных дней, вызванных попаданием праздника на субботу или воскрсенье (на 6 или 7) enumr = holidaysBefore.getEnumerator(); while (enumr.moveNext()) { holidayToMove = enumr.current(); // смещение происходит только в том случае, если выходной попадает на 6 или 7 while (dayOfWk(holidayToMove)==6 || dayOfWk(holidayToMove)==7) { if (dayOfWk(holidayToMove)==6) holidayToMove += 2; else if (dayOfWk(holidayToMove)==7) holidayToMove += 1; // если перемещаясь попали на другой праздничный день из НЕСМЕЩЕННЫХ (before) // или из УЖЕ СМЕЩЕННЫХ (transfer), то двигаемся дальше while ( holidaysBefore .in(holidayToMove) || holidaysTransfer.in(holidayToMove) ) holidayToMove += 1; } // только если было смещение, добавляем день в множество переносов if (! holidaysBefore.in(holidayToMove)) holidaysTransfer.add(holidayToMove); } // в результате имеем множество дат с (годами), которые стали выходными из-за предшествующих праздников // окончательно определяем рабочийСтатусДня if (holidaysTransfer.in(_currDate)) return 0; // выходной = день, на который перенесли праздник, попавший на 6 или 7 else { if (_holidays.in([mthOfYr(_currDate+1), dayOfMth(_currDate+1)])) return 2; // рабочий ПРЕДпраздничный else return 1; // обычный рабочий; } } //------------------------------------------------------------------------ ; #holidayExceptionsInsert(holidayExceptions) // передаем map макросу for (currDate=01\01\2007; currDate<=31\12\2009; currDate++) { info(strFmt('%1 -- %2 -- %3', currDate, dayOfWk(currDate), dayWorkingStatus(currDate, holidayExceptions))); } }
- 0 - выходной день,
- 1 - рабочий день,
- 2 - предпраздничный рабочий день.
- _currDate - дата, для которой определяется статус,
- _exceptions - список переносов-исключений, которые не поддаются алгоритму статьи 112 российского Трудового кодекса; если опущен, то работаем без исключений.
- _holidays - список общегосударственных праздников (только день и месяц); если опущен, то подразумеваются праздники России (прописаны в коде).
(to be continued...)
Всего комментариев 5
Комментарии
-
Собственно я прервал предыдущий пост, потому что напоролся на ограничение в 10 тысяч символов!! Поэтому продолжаю уже в этом комментарии
Способ с макросом я использовал для демонстрационного примера. Каждое исключение характеризуется двумя датами: день, который переносят, и день, НА который переносят. Глобальный макрос HolidayExceptions наполнен информацией о реальных переносах-исключениях в 2007-2009 годах:
X++:/* %1 holiday exceptions map */ #LOCALMACRO.holidayExceptionsInsert // ФОРМИРОВАНИЕ СПИСКА ИСКЛЮЧЕНИЙ - на основании Постановлений Правительства РФ // В соответствии c Постановлением Правительства РФ от 11 ноября 2006 года N 661 // переносятся следующие выходные дни 2007 года // с субботы 28 апреля на понедельник 30 апреля %1.insert( 28\04\2007, 2); // 2 - т.к. перенос с предпраздничного дня %1.insert( 30\04\2007, 0); // с субботы 9 июня на понедельник 11 июня %1.insert( 09\06\2007, 2); // 2 - т.к. перенос с предпраздничного дня %1.insert( 11\06\2007, 0); // с субботы 29 декабря на понедельник 31 декабря %1.insert( 29\12\2007, 2); // 2 - т.к. перенос с предпраздничного дня %1.insert( 31\12\2007, 0); // В соответствии с Постановлением Правительства РФ от 11.08.2007 № 512 // переносятся следующие выходные дни 2008 года: // с воскресенья 4 мая на пятницу 2 мая %1.insert( 04\05\2008, 1); // 4 мая 2008 стал рабочим днем %1.insert( 02\05\2008, 0); // 2 мая 2008 стал выходным днем // с субботы 7 июня на пятницу 13 июня %1.insert( 07\06\2008, 1); // 7 июня 2008 стал рабочим днем %1.insert( 13\06\2008, 0); // 13 июня 2008 стал выходным днем // с субботы 1 ноября на понедельник 3 ноября %1.insert( 01\11\2008, 2); // // 2 - т.к. перенос с предпраздничного дня %1.insert( 03\11\2008, 0); // // В соответствии c Постановлением Правительства от 26.11.2008 № 877 // переносятся следующие выходные дни 2009 года: // с воскресенья 11 января на пятницу 9 января. %1.insert( 11\01\2009, 1); %1.insert( 09\01\2009, 0); #ENDMACRO
Запись от Gustav размещена 28.08.2009 в 15:19
Обновил(-а) Gustav 28.08.2009 в 16:11 -
спасибо.
вот http://axforum.info/forums/blog.php?b=20Запись от mazzy размещена 08.09.2009 в 14:35 -
Упражнение в программировании, не более того. Определенный интерес представляла бы функция, которая генерировала бы календарь по этой схеме, а "исключения" заносились бы руками в готовый календарь.
Запись от EVGL размещена 29.09.2009 в 20:59 -
Евгений, ну так... запустить ее в цикле за год без блока "исключений" и сгенерить календарь, нет?
Руками потом, да - информация о новых исключениях все равно обычно появляется тогда, когда календари на след. год в системах уже в основном заготовлены.
Кстати, в Австрии тоже есть такие сдвиги выходных из-за праздников, попавших на уик-энд? Или это исключительно российская особенность?Запись от Gustav размещена 01.10.2009 в 18:53 -
Да, это - исключительно советское извращение. (Я называю это извращением потому, что заранее со 100%-ой вероятностью не знаешь, работаешь ли в выбранный день в будущем, или нет.) Если в Австрии праздник попадает на выходной, то считай, не повезло. А алгоритм пришлось бы расширять на вычисление Пасхи и Троицы.
Запись от EVGL размещена 02.10.2009 в 16:51
Обновил(-а) EVGL 02.10.2009 в 16:54