Показать сообщение отдельно
Старый 23.07.2009, 14:09   #6  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
Как обычно, обожаю подобные задачки. Вечерком прикинул алгоритм (под спилберговские "Челюсти" по НТВ), сегодня неспешно воплотил в перерывах основной работы. Видел, что в теме уже что-то появилось от 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));
}