Как обычно, обожаю подобные задачки. Вечерком прикинул алгоритм (под спилберговские "Челюсти" по НТВ), сегодня неспешно воплотил в перерывах основной работы. Видел, что в теме уже что-то появилось от 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));
}