![]() |
#6 |
Moderator
|
Как обычно, обожаю подобные задачки. Вечерком прикинул алгоритм (под спилберговские "Челюсти" по НТВ), сегодня неспешно воплотил в перерывах основной работы. Видел, что в теме уже что-то появилось от sukhanchik'а и EVGL'а, но заставил себя не смотреть раньше времени, чтобы увиденное не влияло на мой плотницкий вариант.
Вначале я хотел вообще обойтись одним SELECT'ом классического SQL. Но крыша начала немного коситься (а тут еще и акулы по телеку плавали!), и я свернул на дорожку перебора дат, входящих в диапазон, с отбором диапазонов рабочего времени в map и последующим вычислением общей суммы рабочих часов. Диапазоны представлены 2-х элементными контейнерами [дата/время начала, дата/время окончания]. Дата/время - real, в котором целая часть - дни, а дробная - доля суток. Элементы рабочего календаря имитируются встроенной функцией, в которой реализован конкретный график работы. График работы взял из своего личного жЫзненного опыта - 4 дня с 8-30 до 17-30, в пятницу - до 16-30. Обед - 48 минут (а не час), и за счет ежедневных "переработок" в 12 минут к пятнице набегает час, на который и можно уйти пораньше. Наверное, многим знаком подобный прием. Обед в джобе учтён! ![]() X++: #define.secondsOf24hrs( 86400 ) static void KKu_demoCountWorkHoursForPeriod(Args _args) { // ИСХОДНЫЕ ДАННЫЕ ------------------------- // дата/время начала и дата/время окончания date dateBeg = 01\06\2009; timeOfDay timeBeg = str2time('07:00:00'); date dateEnd = 30\06\2009; timeOfDay timeEnd = str2time('24:00:00'); // ----------------------------------------- real dateTimeBeg = date2num(dateBeg) + timeBeg/#secondsOf24hrs; real dateTimeEnd = date2num(dateEnd) + timeEnd/#secondsOf24hrs; real start, finish, workHours; int i, elemCount; Map allDaysRanges = new Map(Types::Integer, Types::Container); Set oneDayRanges = new Set(Types::Container); SetEnumerator setEnumr; MapEnumerator mapEnumr; // встроенная функция имитирует рабочий календарь ------------------------------------ // для одного дня возвращает два периода, с учетом обеденного перерыва // для простоты примера не учитывает праздники и связанные с ними переносы Set workdayRanges(date _date) { Set setRanges = new Set(Types::Container); ; switch (dayOfWk(_date)) { case 1,2,3,4 : // c 8-30 до 17-30 (обед 48 минут) // до обеда setRanges.add([date2num(_date) + str2time('08:30:00')/#secondsOf24hrs, date2num(_date) + str2time('13:00:00')/#secondsOf24hrs]); // после обеда setRanges.add([date2num(_date) + str2time('13:48:00')/#secondsOf24hrs, date2num(_date) + str2time('17:30:00')/#secondsOf24hrs]); break; case 5 : // ПЯТНИЦО! - на час меньше! c 8-30 до 16-30 (обед 48 минут) // до обеда setRanges.add([date2num(_date) + str2time('08:30:00')/#secondsOf24hrs, date2num(_date) + str2time('13:00:00')/#secondsOf24hrs]); // после обеда setRanges.add([date2num(_date) + str2time('13:48:00')/#secondsOf24hrs, date2num(_date) + str2time('16:30:00')/#secondsOf24hrs]); break; case 6,7 : // ничего -> Have a nice weekend! )) break; } return setRanges; } // ----------------------------------------------------------------------------------- ; if (dateTimeEnd < dateTimeBeg) { box::stop('Дата/время окончания меньше даты/времени начала!\nВыполнение прервано.'); return; } // заполнение map циклом между крайними датами периода for (i=date2num(dateBeg); i<=date2num(dateEnd)+1; i++) { oneDayRanges = workdayRanges( num2date(i) ); if (! oneDayRanges.empty()) { setEnumr = oneDayRanges.getEnumerator(); while (setEnumr.moveNext()) { [start, finish] = setEnumr.current(); if ( ((start >= dateTimeBeg) && (start <= dateTimeEnd)) || // start between Beg and End ((finish >= dateTimeBeg) && (finish <= dateTimeEnd)) || // finish between Beg and End ((start <= dateTimeBeg) && (finish >= dateTimeEnd)) ) // на случай внутри одного диапазона { elemCount++; allDaysRanges.insert(elemCount, [start, finish]); } } } } if (! allDaysRanges.empty()) { // коррекция первого диапазона [start, finish] = allDaysRanges.lookup(1); start = max(start, dateTimeBeg); allDaysRanges.insert(1, [start, finish]); // коррекция последнего диапазона [start, finish] = allDaysRanges.lookup(elemCount); finish = min(finish, dateTimeEnd); allDaysRanges.insert(elemCount, [start, finish]); } // подсчет рабочих часов за период mapEnumr = allDaysRanges.getEnumerator(); while (mapEnumr.moveNext()) { [start, finish] = mapEnumr.currentValue(); workHours += (finish - start) * 24; } box::info( strFmt('Кол-во рабочих часов = %1', workHours)); } |
|