Количество рабочих дней с учетом праздников

Рассмотрим следующую задачку: «Написать SQL-запрос подсчитывающий количество рабочих дней между двумя датами учитывая все праздничные, выходные дни и их переносы”.

Без учета праздников эта задачка не представляет никаких трудностей. Просто подсчитываем количество дней, где день недели не равен СБ или ВС:

select count(*)
from (select to_date('01.01.2017','dd.mm.yyyy')+rownum dat,
to_char(to_date('01.01.2017','dd.mm.yyyy')+rownum,'DY') dy
from sys.all_objects)
where dat<=to_date('31.12.2017','dd.mm.yyyy') and dy not in('СБ','ВС')

Получаем 260 дней.

В стандартном пакете Oracle, да и наверное во всех других базах данных, отсутствуют сведения о праздничных днях в России. Значит их нужно откуда-нибудь взять. Будем вытаскивать выходные из производственного календаря системы Консультант плюс. Актуальный календарь на текущий год располагается по адресу http://www.consultant.ru/law/ref/calendar/proizvodstvennye/

Рассмотрим только текущий 2017 год. Парсить html страницу будем в java с помощью библиотеки jsoup (https://jsoup.org/). Для начала проинспектируем наш календарь.

Видим, что месяц заключен в таблицу, которой проставлен класс “month-block”. Именно по этому признаку и будем выделять необходимые тэги. Далее нужно определить какие дни у нас праздничные, а какие рабочие.

Инспектор html кода показал, что дни бывают трех классов: inactively, holiday weekend, work. Мы будем брать только те значения тэгов, у которых выставлен класс которому соответствуют праздничные дни.

Здесь я должен сделать небольшой дисклэймер. Мое творение не является образцом хорошего кодинга на java. В нем совсем не используются возможности объектно-ориентированного программирования, вся программа написана в одном методе main. Это очень плохо. Пока я не особо умею писать по-другому, но все впереди, я только учусь.

Также пока не подобрал хороший плагин для wordpress для отображения кода. Если стандартные тэги Code для SQL еще более-менее смотрятся, то вот java код практически не читабелен. Постараюсь со временем переделать соответствующие блоки.

Итак, можно приступить к программированию. Вот что у меня получилось.

public class ParseConsultant{
public static void main(String args[]) throws IOException, SQLException {
List monthsList = new ArrayList<>(); //сюда поместим все блоки всех месяцев
List daysList = new ArrayList<>(); //сюда поместим блоки с днями
Document doc = Jsoup.connect("http://www.consultant.ru/law/ref/calendar/proizvodstvennye/").get(); //коннекстимся к источнику
Elements monthsElements = doc.getElementsByAttributeValue("class", "month-block"); //добавляем элементы-месяцы
monthsElements.forEach(monthsElement -> {
String aElement = monthsElement.child(0).toString(); //выбираем все содержимаое месяца
monthsList.add(aElement);
});
Locale.setDefault(Locale.ENGLISH); //подключаемся к базе данных
String dbURL = "jdbc:oracle:thin:dksd/1@localhost:1521:XE"; //подключаемся к базе данных
Connection conn = DriverManager.getConnection(dbURL);
if (conn != null) {
System.out.println("Connected");
}
Statement statement = conn.createStatement(); //открываем statement для последующего insert
for (int x = 0; x < 12; x = x + 1) { //открываем цикл для пробега по всем 12-ти месяцам Document mon = Jsoup.parse(monthsList.get(x)); //парсим содержимое массива месяцев для выбора дней. System.out.println(mon.toString()); //смотрим что у нас в месяце Elements mElements = mon.getElementsByClass("holiday weekend"); //выбираем нужные тэги по классу mElements.forEach(mElement -> {
String dElem = mElement.text(); //получаем значение тэга, то есть праздничный день
daysList.add(dElem); //добавляем значение тэга в массив
});
int z = daysList.size(); //считаем сколько добавилось выходных дней в текущем месяце
for (int i = 0; i < z; i = i + 1) { //для каждого дня формируем insert строку
System.out.println(i + "-" + daysList.get(i)); //смотрим какие у нас выходные дни в текущем месяце
int m = x + 1; //для правильного insert месяца, который не может быть равен нулю
String insertSQL = "insert into holidays (HOLIDAY) VALUES (to_date('" + daysList.get(i) + "." + m + ".2017','dd.mm.yyyy'))";
statement.executeUpdate(insertSQL); //добавлем строки в таблицу holidays
}
daysList.clear(); //очищаем массив в конце цикла
}
}
}

Программа с помощью библиотеки jsoup коннектится к запрашиваемой странице, выбирает все блоки где имелся класс «month-block». Потом каждый блок еще раз парсится на предмет текста тэга, содержащего класс «holiday weekend». Сохраняет все в ArrayList и подставляет в строку для инсерта в базу данных.

Выполнив эту программу получаем даты всех выходных дней в предварительно созданной таблице HOLIDAYS, состоящей из одной колонки HOLIDAY. Их должно получиться 118.

Теперь модифицируем SQL запрос.

select count(*)
from (select to_date('01.01.2017','dd.mm.yyyy')+rownum dat
from sys.all_objects)
where dat<=to_date('31.12.2017','dd.mm.yyyy') and dat not in (select holiday from holidays)

Получаем 247 дней. На целых 13 дней меньше))