Bölüme bir zaman aralığı dilimleme

5 Cevap php

İlk soru. Nazik olun.

Ben görevler üzerinde çalışmaya harcanan teknisyenlerinin zaman izler yazılım üzerinde çalışıyorum. Yazılım haftanın günleri ve günün saatine göre farklı faturalandırılabilir oranı çarpanları tanımak için geliştirilmiş gerekmektedir. (Örneğin, "Saat ve hafta içi 17:00 sonra bir buçuk.")

Yazılımını kullanarak teknoloji, sadece tarih, onun başlangıç ​​zamanı ve (saat ve dakika) yaptığı stop zamanını giriş için gereklidir. Yazılım oranı çarpanları değiştirmek ne zaman sınırları bölüme zaman girişini kırması bekleniyor. Tek bir zaman giriş Birden fazla güne yayılan izin verilmez.

Burada oran tablonun kısmi örnek. Birinci düzey Dizi anahtarları açıkçası, haftanın gün vardır. Yeni çarpan olarak başladı, ve dizideki sonraki ardışık girişine kadar çalıştığında ikinci düzey Dizi anahtarları günün zamanını temsil eder. Dizi değerleri o zaman aralığı için çarpan vardır.

[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
            )
        ...
    )

Düz İngilizce, bu zaman-ve-bir buçuk 08:00 gece yarısından itibaren hızını, 8 ila 05:00 düzenli oranı ve 23:59 kadar 5 tekrar zaman-ve-bir-buçuk temsil eder. Bu sonlarının o zaman ikinci rasgele olabilir ve her gün için onları keyfi bir sayı olabilir. (This format is entirely negotiable, but my goal is to make it as easily human-readable as possible.)

Örnek olarak: entry 21:00:00 (09:00) için 15:00:00 (15:00) den Pazartesi günü açmış bir zaman 1x faturalandırılır 2 saat ve 1.5x olarak fatura 4 saat oluşacak. Tek bir saat girişi çoklu sonları yayılan için de mümkündür. Yukarıdaki örnekte rateTable kullanarak, 06:00-21:00 Bir süre giriş 6-8 AM@1.5x, 08:00-17:00 @ 1x ve 5-9 PM@1.5x 3 alt aralıkları olurdu. Buna karşılık, bir zaman sadece giriş 8:15:00-08:30:00 olabilir ve tamamen tek bir çarpan aralığında kapsıyor olması da mümkündür.

Ben gerçekten haftanın bir günü sürebilir bazı PHP kadar kodlama (ya da en azından bir algoritma oluşturulması), bazı yardım, bir başlangıç ​​zamanı ve bir durma zamanı kullanmak ve gerekli subparts içine ayrıştırmak olabilir. Bu çıkış (başlatma, durdurma, çarpanı) üçlüsü için birden fazla giriş oluşan bir dizi olması için ideal olacaktır. Yukarıdaki örnek için, çıkış olacaktır:

[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
            )
    )

Ben sadece düz (potansiyel olarak) birden subparts tek bir (başlangıç, durdurma) bölme mantığı etrafında başımı sarmak olamaz.

5 Cevap

Eineki algoritması kırık. Benim girişimleri eksik kısmı başlangıç ​​and durma zamanı her çarpanı aralığında mevcut olmamdı. Benim özgün rateTable veri yoğunluğu değeri, bu yüzden config saklanan tablo almak ve benim kod içeri durdurma kez eklemek Eineki en dönüştürmek () rutin cesareti kullanılan zaten otomatik oluşturulmuş (veya dolu) bir minimal oranı tablo, kodun geri kalanı bobini veya uyarı / hata atmak olmaz garanti, bu yüzden bu dahil. Ben de aklımda iki birbirimiz olmadan yararlı bir amaca yoksa beri birlikte map_shift () fatura yoğunlaştırılmış () ve.

<?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'));

?>

Herkese teşekkürler, özellikle Eineki.

Ben farklı bir yaklaşım kullanmak, ve ben düşünceler bir çift tabanlı rateTable gösterimini değiştirmek olacaktır.

  • $ RateTable aralıklarını tanımlamak, neden onları doğru kodlamak değil mi?
  • Ne (benim örnekte Salı ve Pazartesi sınır tanımı iki farklı yaklaşımlar kullanır) sınırları üzerinde olur;
  • Alacağınız sonuçlar karşılaştırılabilir türü vardır ama farklı bir temsilini kullanın.
  • 23:59:59 => Bana bir hack gibi görünüyor. Ben şu anda açıklayamam ama ben bunun için dikkat etmeniz bana kafasının arkasında bir çan zil var.

Son ama en az değil, benim kişisel deneyim bana bir algoritma üzerinde başınızı sarın edemez eğer (size başarılı ve sorunları çözmek bile) ortak çalışanlar aynı zorluklar olasıdır ve kodu, diyelim hata birincil kaynağı. Eğer basit ve etkili bir çözüm bulursanız o zaman, para ve baş ağrısı bir kazanç olacaktır. Belki de çözüm çok verimli olmasa bile bir kazanç olacaktır.

$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'));

En azından size yeni birine özgün biçimini dönüştürmek için bir işlev gerekir.

$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));

Ve evet, sizinle anında dönüşüm yapabileceği

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

ama performansları biraz bakım eğer ...

Gibi bir şey öneririm

get total time to allocate (workstop - workstart) 

find the start slot (the last element where time < workstart)
and how much of start slot is billable, reduce time left to allocate

move to next slot

while you have time left to allocate

   if the end time is in the same slot
       get the portion of the time slot that is billable
   else
       the whole slot is billable
       reduce the time to allocate by the slot time 


   (build your output array) and move to the next slot

loop while

Bu içten işlemek için gün / saat / dakika hesaplamaları kolaylaştırmak saniye için tüm kat dönüştürmek için daha kolay olabilir.

Bu temelde @ Loopo algoritmasının bir uyarlamasıdır.

İlk olarak, > ve < kullanarak kez karşılaştırma yapabilmek için güzel olurdu, bu yüzden önce biz UNIX zamanda tüm zamanlar (haftanın günü + ikinci saat / dakika /) dönüştürmek uzaklıklar:

// 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;
}

İdeal olarak, bu zaten (örn. yerine orijinal xx:yy:zz D dizeleri veritabanında bu dönüştürülen uzaklıklar saklamak) bitmiş olurdu.

Şimdi (kilitler eksikliği nedeniyle bir yardımcı ile) splitter:

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;
    }
}

Ve sınıfını kullanarak:

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

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

var_dump($splitClockedTimes);

Umarım bu yardımcı olur.

İşte benim yöntem

Ben bunu çok kolay hale getirmek için saniye her şeyi dönüştürülür.

İşte saniye endeksli oran tablosu bulunuyor. Pazartesi için gidecekseniz sadece 3 saat yuvası

// 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
    )
);

Saniye ss: dd: ss Bu dönüştürmek için bu işlevi kullanır

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

Bu oran tablo döndüren işlevdir

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;
}

İşte bunu kullanmak nasıl

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

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

iadeler

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
        )

)

Temelde kod oranı masanın üzerinde döngüler ve her oranına ait kaç saniye verilen zaman yuvadan bulur.