Разрезание времени располагается в части

Первый вопрос. Будьте нежны.

Я работаю над программным обеспечением, которое отслеживает потраченную работу времени технических персоналов над задачами. Программное обеспечение должно быть улучшено для распознавания различных оплачиваемых множителей уровня на основе дня недели и времени суток. (Например, "Оплата в полуторном размере после 17:00 в рабочие дни".)

Технология с помощью программного обеспечения только требуется, чтобы регистрировать дату, его время начала и его время остановки (в течение многих часов и минут). Программное обеспечение, как ожидают, разломает запись времени на части на границах того, когда множители уровня изменятся. Единственной записи времени не разрешают охватить в несколько дней.

Вот частичный образец списка тарифов. Ключи массива первого уровня являются днями недели, очевидно. Ключи массива второго уровня представляют время суток, когда новый множитель умирает и работает до следующей последовательной записи в массиве. Значения массива являются множителем для того диапазона времени.

[rateTable] => Array
    (
        [Monday] => Array
            (
                [00:00:00] => 1.5
                [08:00:00] => 1
                [17:00:00] => 1.5
                [23:59:59] => 1
            )

        [Tuesday] => Array
            (
                [00:00:00] => 1.5
                [08:00:00] => 1
                [17:00:00] => 1.5
                [23:59:59] => 1
            )
        ...
    )

Без обиняков это представляет в полуторном размере уровень с полуночи до 8:00, регулярный уровень с 20:00 до 17:00, и оплата в полуторном размере снова от 5 до 23:59. Время, когда эти повреждения происходят, может быть произвольным к второму и может быть произвольное число их в течение каждого дня. (Этот формат является совершенно договорным, но моя цель состоит в том, чтобы сделать его максимально легко человекочитаемым.)

Как пример: запись времени, зарегистрированная в понедельник с 15:00:00 (15:00) к 21:00:00 (21:00), состояла бы из 2 часов, тарифицированных в 1x и 4 часов, тарифицированных в 1.5x. Для единственной записи времени также возможно охватить несколько повреждений. Используя пример rateTable выше, запись времени с 6:00 до 21:00 имела бы 3 поддиапазона от 6-8 AM 1.5x, 8:00 - 17:00 1x, и PM 5-9 1.5x. В отличие от этого, также возможно, что запись времени может только быть с 8:15:00 до 8:30:00 и быть полностью охвачена в диапазоне единственного множителя.

Я мог действительно использовать некоторую справку, кодирующую некоторый PHP (или по крайней мере разрабатывающий алгоритм), который может занять день недели, времени начала и времени остановки и проанализировать в в необходимые подразделения. Это было бы идеально, чтобы иметь вывод быть массивом, который состоит из многократных въездов для (запустите, остановитесь, множитель), триплет. Для вышеупомянутого примера вывод был бы:

[output] => Array
    (
        [0] => Array
            (
                [start] => 15:00:00
                [stop] => 17:00:00
                [multiplier] => 1
            )

        [1] => Array
            (
                [start] => 17:00:00
                [stop] => 21:00:00
                [multiplier] => 1.5
            )
    )

Я просто не могу перенестись, моя голова вокруг логики разделения сингла (запустите, остановитесь) в (потенциально) несколько подразделений.

20
задан beporter 7 May 2010 в 22:24
поделиться

5 ответов

Eineki разгадал алгоритм. В моих попытках не хватало только начального и конечного времени в каждом диапазоне множителей. Мне важна плотность данных в моей оригинальной rateTable, поэтому я использовал внутренности Eineki's convert(), чтобы взять таблицу, хранящуюся в config, и добавить в нее время остановки. Мой код уже автоматически создал (или заполнил) минимальную таблицу тарифов, гарантируя, что остальная часть кода не захлебнется и не будет выдавать предупреждения/ошибки, поэтому я включил ее. Я также объединил bill() и map_shift() вместе, поскольку, на мой взгляд, они не имеют никакой полезной цели друг без друга.

<?php

//-----------------------------------------------------------------------
function CompactSliceData($start, $stop, $multiplier)
// Used by the VerifyRateTable() to change the format of the multiplier table.
{
    return compact('start', 'stop','multiplier');
}

//-----------------------------------------------------------------------
function VerifyAndConvertRateTable($configRateTable)
// The rate table must contain keyed elements for all 7 days of the week. 
// Each subarray must contain at LEAST a single entry for '00:00:00' => 
// 1 and '23:59:59' => 1. If the first entry does not start at midnight, 
// a new element will be added to the array to represent this. If given 
// an empty array, this function will auto-vivicate a "default" rate 
// table where all time is billed at 1.0x.
{
    $weekDays = array('Monday', 'Tuesday', 'Wednesday', 
            'Thursday', 'Friday', 'Saturday', 
            'Sunday',);  // Not very i18n friendly?     

    $newTable = array();
    foreach($weekDays as $day)
    {
        if( !array_key_exists($day, $configRateTable) 
            || !is_array($configRateTable[$day]) 
            || !array_key_exists('00:00:00', $configRateTable[$day]) )
        {
            $configRateTable[$day]['00:00:00'] = 1;
        }

        if( !array_key_exists($day, $configRateTable) 
            || !is_array($configRateTable[$day]) 
            || !array_key_exists('23:59:59', $configRateTable[$day]) )
        {
            $configRateTable[$day]['23:59:59'] = 1;
        }

        // Convert the provided table format to something we can work with internally.
        // Ref: http://stackoverflow.com/questions/2792048/slicing-a-time-range-into-parts
        $newTable[$day] = array_slice(
                array_map(
                   'CompactSliceData',
                   array_keys($configRateTable[$day]),
                   array_keys(array_slice($configRateTable[$day],1)),
                   $configRateTable[$day]),
                0,-1);
    }
    return $newTable;
}

//-----------------------------------------------------------------------
function SliceTimeEntry($dayTable, $start, $stop)
// Iterate through a day's table of rate slices and split the $start/$stop
// into parts along the boundaries.
// Ref: http://stackoverflow.com/questions/2792048/slicing-a-time-range-into-parts
{
    $report = array();
    foreach($dayTable as $slice) 
    {
        if ($start < $slice['stop'] && $stop > $slice['start'])
        {
           $report[] = array(
                    'start'=> max($start, $slice['start']),
                    'stop' => min($stop, $slice['stop']),
                    'multiplier' => $slice['multiplier']
                );
        }
    }
    return $report;
}


/* examples */
$rateTable = array(
    'Monday' => array('00:00:00' => 1.5, '08:00:00' => 1, '17:00:00' => 1.5),
    'Tuesday' => array('00:00:00' => 1.5, '08:00:00' => 1, '17:00:00' => 1.5),
    'Wednesday' => array('00:00:00' => 1.5, '08:00:00' => 1, '17:00:00' => 1.5),
    'Thursday' => array('00:00:00' => 1.5, '08:00:00' => 1, '17:00:00' => 1.5),
    'Friday' => array('00:00:00' => 1.5, '08:00:00' => 1, '17:00:00' => 1.5),
    'Saturday' => array('00:00:00' => 1.5, '15:00:00' => 2),
    'Sunday' => array('00:00:00' => 1.5, '15:00:00' => 2),
);

$rateTable = VerifyAndConvertRateTable($rateTable);

print_r(SliceTimeEntry($rateTable['Monday'],'08:05:00','18:05:00'));
print_r(SliceTimeEntry($rateTable['Monday'],'08:05:00','12:00:00'));
print_r(SliceTimeEntry($rateTable['Tuesday'],'07:15:00','19:30:00'));
print_r(SliceTimeEntry($rateTable['Tuesday'],'07:15:00','17:00:00'));

?>

Спасибо всем, особенно Eineki.

0
ответ дан 30 November 2019 в 01:36
поделиться

Я бы использовал другой подход и изменю представление rateTable, исходя из нескольких соображений.

  • Таблица $ rateTable описывает интервалы, почему вы не кодируете их правильно?
  • Что происходит на границах (вторник и понедельник в моем примере используют два разных подхода к определению границ);
  • Результаты, которые вы get имеют сопоставимый тип, но используют другое представление.
  • 23: 59: 59 => мне кажется хакерским. Я не могу сейчас объяснить, но у меня в затылке звонит колокольчик, говорящий, что нужно остерегаться этого.

И последнее, но не менее важное: мой личный опыт позволяет мне сказать, что если вы не можете понять алгоритм, скорее всего, ваши коллеги столкнутся с такими же трудностями (даже если вы добьетесь успеха и решите проблемы) и код будет основным источником ошибки. Если вы найдете более простое и эффективное решение, это будет выигрыш времени, денег и головной боли. Может быть, это будет выигрыш, даже если решение будет не столь эффективным.

$rateTable = array(
    'Monday' => array (
        array('start'=>'00:00:00','stop'=>'07:59:59','multiplier'=>1.5),
        array('start'=>'08:00:00','stop'=>'16:59:59','multiplier'=>1),
        array('start'=>'17:00:00','stop'=>'23:59:59','multiplier'=>1.5)
    ),
    'Tuesday'=> array (
        array('start'=>'00:00:00','stop'=>'08:00:00','multiplier'=>1.5),
        array('start'=>'08:00:00','stop'=>'17:00:00','multiplier'=>1),
        array('start'=>'17:00:00','stop'=>'23:59:59','multiplier'=>1.5)
    )
);

function map_shift($shift, $startTime, $stopTime)
{
    if ($startTime >= $shift['stop'] or $stopTime <= $shift['start']) {
        return;
    }
    return array(
        'start'=> max($startTime, $shift['start']),
        'stop' => min($stopTime, $shift['stop']),
        'multiplier' => $shift['multiplier']
    );
}

function bill($day, $start, $stop)
{
    $report = array();
    foreach($day as $slice) {
        $result = map_shift($slice, $start, $stop);
        if ($result) {
           array_push($report,$result);
        }
    }
    return $report;
}



/* examples */
var_dump(bill($rateTable['Monday'],'08:05:00','18:05:00'));
var_dump(bill($rateTable['Monday'],'08:05:00','12:00:00'));
var_dump(bill($rateTable['Tuesday'],'07:15:00','19:30:00'));
var_dump(bill($rateTable['Tuesday'],'07:15:00','17:00:00'));

По крайней мере, вам нужна функция для преобразования исходного формата в новый.

$oldMonday = array (
   '00:00:00'=>1.5,
   '08:00:00'=>1,
   '17:00:00'=>1.5,
   '23:59:59'=>1
);

function convert($array) 
{
    return array_slice(
        array_map(
           function($start,$stop, $multiplier) 
           {
               return compact('start', 'stop','multiplier');
           },
           array_keys($array),
           array_keys(array_slice($array,1)),
           $array),
        0,
        -1);
}

var_dump(convert($oldMonday));

И да, вы можете выполнить преобразование на лету с помощью

bill(convert($oldRateTable['Tuesday']),'07:15:00','17:00:00');

, но если вас немного волнует производительность ...

3
ответ дан 30 November 2019 в 01:36
поделиться

Я бы предложил что-то вроде

 получить общее время для выделения (рабочая остановка - рабочий запуск) 
 
найдите начальный слот (последний элемент, где time 

Возможно, будет проще преобразовать все время в секунды внутренне, чтобы производить вычисления дней / часов / минут легче обрабатывать.

0
ответ дан 30 November 2019 в 01:36
поделиться

Вот мой метод

Я преобразовал все в секунды, чтобы упростить задачу.

Вот таблица тарифов, индексированная по секундам. В понедельник есть только 3 временных интервала

// 0-28800 (12am-8am) = 1.5
// 28800-61200 (8am-5pm) = 1
// 61200-86399 (5pm-11:50pm) = 1.5

$rate_table = array(
    'monday' => array (
        '28800' => 1.5,
        '61200' => 1,
        '86399' => 1.5
    )
);

Эта функция используется для преобразования чч: мм: сс в секунды

function time2seconds( $time ){
    list($h,$m,$s) = explode(':', $time);
    return ((int)$h*3600)+((int)$m*60)+(int)$s;
}

Это функция, которая возвращает таблицу тарифов

function get_rates( $start, $end, $rate_table ) {

    $day = strtolower( date( 'l', strtotime( $start ) ) );

    // these should probably be pulled out and the function
    // should accept integers and not time strings
    $start_time = time2seconds( end( explode( 'T', $start ) ) );
    $end_time = time2seconds( end( explode( 'T', $end ) ) );

    $current_time = $start_time;

    foreach( $rate_table[$day] as $seconds => $multiplier ) {

        // loop until we get to the first slot
        if ( $start_time < $seconds ) {
            //$rate[ $seconds ] = ( $seconds < $end_time ? $seconds : $end_time ) - $current_time;

            $rate[] = array (

                'start' => $current_time,
                'stop' => $seconds < $end_time ? $seconds : $end_time,
                'duration' => ( $seconds < $end_time ? $seconds : $end_time ) - $current_time,
                'multiplier' => $multiplier

            );

            $current_time=$seconds;
            // quit the loop if the next time block is after clock out time
            if ( $current_time > $end_time ) break;
        }

    }

    return $rate;
}

Вот как вы ее используете

$start = '2010-05-03T07:00:00';
$end = '2010-05-03T21:00:00';

print_r( get_rates( $start, $end, $rate_table ) );

возвращает

Array
(
    [0] => Array
        (
            [start] => 25200
            [stop] => 28800
            [duration] => 3600
            [multiplier] => 1.5
        )

    [1] => Array
        (
            [start] => 28800
            [stop] => 61200
            [duration] => 32400
            [multiplier] => 1
        )

    [2] => Array
        (
            [start] => 61200
            [stop] => 75600
            [duration] => 14400
            [multiplier] => 1.5
        )

)

В основном код просматривает таблицу тарифов и находит, сколько секунд из данного временного интервала принадлежит каждой скорости.

1
ответ дан 30 November 2019 в 01:36
поделиться

По сути, это адаптация алгоритма @Loopo.

Во-первых, было бы неплохо иметь возможность сравнивать время, используя > и <, поэтому сначала мы конвертируем все времена (день недели + час/минута/секунда) в смещения времени UNIX:

// Code is messy and probably depends on how you structure things internally.

function timeOffset($dayOfWeek, $time) {
    // TODO Use standard libraries for this.
    $daysOfWeek = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');

    $splitTime = explode(':', $time);
    $offset = (((int)array_search($dayOfWeek, $daysOfWeek) * 24 + (int)$time[0]) * 60 + (int)$time[1]) * 60 + (int)$time[2];

    return $offset;
}

$rateTable = array(
    'Monday' => array(
        '00:00:00' => 1.5,
        '08:00:00' => 1,
        '17:00:00' => 1.5,
    ),

    'Tuesday' => array(
        '00:00:00' => 1.5,
        '08:00:00' => 1,
        '17:00:00' => 1.5,
    )
);

$clockedTimes = array(
    array('Monday', '15:00:00', '21:00:00')
);

$rateTableConverted = array();

foreach($rateTable as $dayOfWeek => $times) {
    foreach($times as $time => $multiplier) {
        $offset = timeOffset($dayOfWeek, $time);
        $rateTableConverted[$offset] = $multiplier;
    }
}

ksort($rateTableConverted);

$clockedTimesConverted = array();

foreach($clockedTimes as $clock) {
    $convertedClock = array(
        'start' => timeOffset($clock[0], $clock[1]),
        'end'   => timeOffset($clock[0], $clock[2]),
    );

    $clockedTimesConverted[] = $convertedClock;
}

В идеале, это уже было сделано (например, вы храните эти конвертированные смещения в базе данных вместо оригинальных xx:yy:zz D строк).

Теперь разделитель (с помощником из-за отсутствия замыканий):

class BetweenValues {
    public $start, $end;

    public function __construct($start, $end) {
        $this->start = $start;
        $this->end = $end;
    }

    public function isValueBetween($value) {
        return $this->start <= $value && $value <= $this->end;
    }
}

class TimeRangeSplitter {
    private $rateTable;

    public function __construct($rateTable) {
        $this->rateTable = $rateTable;
    }

    private function getIntersectingTimes($times, $start, $end) {
        ksort($times);

        $betweenCalculator = new BetweenValues($start, $end);

        $intersecting = array_filter($times, array($betweenCalculator, 'isValueBetween'));

        /* If possible, get the time before this one so we can use its multiplier later. */
        if(key($intersecting) > 0 && current($intersecting) != $start) {
            array_unshift($intersecting, $times[key($intersecting) - 1]);
        }

        return array_values($intersecting);
    }

    public function getSplitTimes($start, $end) {
        $splits = array();

        $intersecting = $this->getIntersectingTimes(array_keys($this->rateTable), $start, $end);

        $curTime = $start;
        $curMultiplier = 0;

        foreach($intersecting as $sectionStartTime) {
            $splits[] = $this->getSplit($curTime, $sectionStartTime, $curMultiplier, $curTime);

            $curMultiplier = $this->rateTable[$sectionStartTime];
        }

        $splits[] = $this->getSplit($curTime, $end, $curMultiplier, $curTime);

        return array_filter($splits);
    }

    private function getSplit($time, $split, $multiplier, &$newTime) {
        $ret = NULL;

        if($time < $split) {
            $ret = array(
                'start' => $time,
                'end' => $split,
                'multiplier' => $multiplier,
            );

            $newTime = $split;
        }

        return $ret;
    }
}

И использование класса:

$splitClockedTimes = array();
$splitter = new TimeRangeSplitter($rateTableConverted);

foreach($clockedTimesConverted as $clocked) {
    $splitClockedTimes[] = $splitter->getSplitTimes($clocked['start'], $clocked['end']);
}

var_dump($splitClockedTimes);

Надеюсь, это поможет.

0
ответ дан 30 November 2019 в 01:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: