2016-11-23 19 views
2

純粋にMySQLクエリから空き時間ブロックを取得しようとしましたが、持っているデータを扱うクエリを構築できませんでした。私は単純にクエリからstartendの日付を取得し、PHPの空き時間ブロックを解消することを選択しました。しかしこれもまた困難であることが証明されている。開始日と終了日のPHP空き時間ブロック

私は、次の配列(ただの例では、曜日と時間が異なります)している:私はで終わるしたいことは、それぞれの自由な時間の開始時間と終了時間と日数の配列である

$meeting = array(array('start' => '2016-11-14 16:00:00', 
         'end' => '2016-11-14 16:30:00' 
        ), 
       array('start' => '2016-11-14 16:45:00', 
         'end' => '2016-11-14 20:00:00' 
        ), 
       array('start' => '2016-11-14 14:00:00', 
         'end' => '2016-11-14 15:00:00' 
        ), 
       array('start' => '2016-11-14 13:00:00', 
         'end' => '2016-11-14 14:00:00' 
        ), 
       array('start' => '2016-11-11 15:20:00', 
         'end' => '2016-11-11 16:00:00' 
        ), 
       array('start' => '2016-11-11 14:00:00', 
         'end' => '2016-11-11 15:00:00' 
        ), 
       array('start' => '2016-11-07 07:00:00', 
         'end' => '2016-11-09 15:00:00' 
        ) 
       ); 

週末を除く毎日09:00〜17:00の間にのみブロックされます。あなたが任意の自由な時間を持つ配列に表示されるだけの日を見ることができるように、11月の7日と8日は何の自由な時間を持っていない

$daily_free = array('2016-11-09' => array(array('free_start' => '15:00:00', 
               'free_end' => '17:00:00' 
               ) 
             ), 
        '2016-11-10' => array(array('free_start' => '09:00:00', 
               'free_end' => '17:00:00' 
               ) 
             ), 
        '2016-11-11' => array(array('free_start' => '09:00:00', 
               'free_end' => '14:00:00' 
               ), 
              array('free_start' => '15:00:00', 
               'free_end' => '15:20:00' 
               ), 
              array('free_start' => '16:00:00', 
               'free_end' => '17:00:00' 
               ) 
             ), 
        '2016-11-14' => array(array('free_start' => '09:00:00', 
               'free_end' => '13:00:00' 
               ), 
              array('free_start' => '15:00:00', 
               'free_end' => '16:00:00' 
               ), 
              array('free_start' => '16:30:00', 
               'free_end' => '16:45:00' 
               ) 
             ) 
        ); 

:だからたとえば、会議上記の時間配列は、自由時間の次の配列を生成する必要があります彼らは全く現れません。また、11月12日と13日はフリータイムの配列には表示されません。これは週末になるためです。しかし、12日と13日が週末でなかった場合、自由時間のフル日(09:00〜17:00)としてフリータイム配列に表示されます。

これを達成するために考えられる唯一の方法の1つは、毎日の配列を作成し、その中にNULL値の09:00から17:00までの5分増分の配列を持つことです。次に、この配列をループして、各日付と5分の増分が最初の配列の開始時刻と終了時刻の範囲内にあるかどうかをチェックし、そうであればビジーとしてマークします。私は再びループを繰り返し、毎日のヌル値の開始時刻と終了時刻を取得できますか?これは素晴らしい解決策のようには見えず、会議が数分未満の場合にも失敗します。

私はこの時点で木の木を見ることはできないと思っています。

他に解決策はありますか?

EDIT: SQL Fiddle UNIX時間に日付を変換するためにこれらを使用し、その後、SQL

+0

日のリストを持つテーブルを作成してSQLでジョブを実行するのは簡単ではないでしょうか? [SQLFiddle](http://sqlfiddle.com/)でサンプルデータセットを作成すると、その方法を示すことができます。 –

+0

@AlexBlex SQL Fiddleが質問に追加されました、ありがとう! – superphonic

+1

btw予定がない日はどうなりますか? 12と13のように?彼らは自由時間でなければなりませんが、あなたはあなたの希望する出力にそれらを含めません。 – phobia82

答えて

3

私はそれがあなたが材料から何かを彫刻しているような問題を考えてみることです。私は自由な時間[09 - 17]の完全な一日から始まり、予定の上をループする。すべての予定について、既存の空き時間を分割するか(開始日順に並べ替える)、または範囲を変更するかのいずれかです。時間が重ならないともっと楽になります。予定が空き時間範囲外の場合は、無視することができます。

{----} {--------} Free Time Range 
      [---] Appointment in the middle - you need to split 

{----} {--------} Free Time Range 
     [---]  Appointment at the beginning - change the start date 

{----} {--------} Free Time Range 
      [---] Appointment at the end - change the end date 

{----} {--------} Free Time Range 
     [--------] Appointment fills the whole spot - delete 
+1

私はこのロジックが大好きです!また、StackOverflowへようこそ!あなたがしばらく行っているように見えます:) – superphonic

+1

ありがとう:Dとありがとう:) – guided1

+1

あなたの範囲外に開始または終了する予定と、サンプルで持っている7番目から9番目のように、1日以上かかるものを考えてみよう。 – phobia82

0

を経由してみてくださいではなく、あなたが持っている日と日付での作業、DateTimeオブジェクトとして値を格納したい人のためスタンプ。これらは、単純にUNIXエポック(1970-01-01 00:00:00)の開始からの秒数の整数値です。

シンプルな計算を実行して、1日の範囲内でギャップを見つけることができます(終了日時と終了日時をエンドマーカーのタイムスタンプに変換)。

タイムスタンプとして識別された開始終了ブロックがあると、これをDateTimeオブジェクトに戻し、これを標準の日付表現に変換することができます。

空きブロックの長さを特定することも非常に簡単です。値はすべて秒であるため、終了時刻から開始時刻を減算すると秒数の差が得られます。 15分を超えるようにするには、整数値900(60 * 15)と比較することができます。ギャップが大きい場合は15分を超えます。

+0

日付をタイムスタンプに変換するにはどうすればよいですか?私はまだ毎日どのように1つの会議の終わりと次の会議の開始の間の範囲を得るためにいくつかに必要ですか?整数や実際の日付オブジェクトでそれを行うのは重要ですか? – superphonic

+0

日付を簡略化するために整数に変換すると、1つの会議の終了が数字(例えば1200)になり、次の会議が4500から開始されます。1の終わりと次の開始の間に3300秒の差があることは簡単に分かります。これは15分(900秒)を超えていますので、1200から始まり4500で終了する空きブロックがあることがわかりました。明らかに、これらの整数は人にやさしくないので、結果を表示するときは、定期的な日付。 (私が使用した値は明らかに現在の日付で見られるものではありません:)) – Graeme

3

私はいくつかのテストを実行し始めました。それからさらに進んで、アルゴリズム全体を書き終わったと思います。クレジットを@guided1にご記入ください

$meeting = array(array('start' => '2016-11-14 16:00:00', 
         'end' => '2016-11-14 16:30:00' 
        ), 
       array('start' => '2016-11-14 16:45:00', 
         'end' => '2016-11-14 20:00:00' 
        ), 
       array('start' => '2016-11-14 14:00:00', 
         'end' => '2016-11-14 15:00:00' 
        ), 
       array('start' => '2016-11-14 13:00:00', 
         'end' => '2016-11-14 14:00:00' 
        ), 
       array('start' => '2016-11-11 15:20:00', 
         'end' => '2016-11-11 16:00:00' 
        ), 
       array('start' => '2016-11-11 14:00:00', 
         'end' => '2016-11-11 15:00:00' 
        ), 
       array('start' => '2016-11-07 07:00:00', 
         'end' => '2016-11-09 15:00:00' 
        ) 
       ); 
$min = "09:00:00"; 
$max = "17:00:00"; 
$start = "2030-01-01 00:00:00"; 
$end = "2000-01-01 00:00:00"; 
$free = [['start' => "2000-01-01 00:00:00", 'end' => "2030-01-01 00:00:00"]]; 
foreach($meeting as $m){ 
    foreach($free as $k=>$f){ 
     if($m['start']>$f['start'] && $m['start']<=$f['end']){ 
      $free[$k]['end'] = $m['start']; 
      if($m['end']<$f['end']) 
       $free[] = ['start'=>$m['end'], 'end'=>$f['end']]; 
     } 
     elseif($m['end']<$f['end'] && $m['end']>$f['start']){ 
      $free[$k]['start'] = $m['end']; 
     } 
    } 
    $start = min($start,$m['start']); 
    $end = max($end,$m['end']); 
} 
$begin = new DateTime($start); 
$end = new DateTime($end); 

$interval = DateInterval::createFromDateString('1 day'); 
$period = new DatePeriod($begin, $interval, $end); 

$daily_free = []; 
foreach ($period as $dt){ 
    $m = [ 
     'start'=>date('Y-m-d ',strtotime($dt->format('Y-m-d').' -1 day')).$max, 
     'end'=>$dt->format('Y-m-d ').$min 
    ]; 
    foreach($free as $k=>$f){ 
     if($m['start']>$f['start'] && $m['start']<=$f['end']){ 
      $free[$k]['end'] = $m['start']; 
      if($m['end']<$f['end']) 
       $free[] = ['start'=>$m['end'], 'end'=>$f['end']]; 
     } 
     elseif($m['end']<$f['end'] && $m['end']>$f['start']){ 
      $free[$k]['start'] = $m['end']; 
     } 
    } 
} 
foreach($free as $k=>$f){ 
    $s = explode(" ",$f['start']); 
    $e = explode(" ",$f['end']); 
    if($s[0] == $e[0]){ 
     $daily_free[$s[0]][] = ['start_free'=>$s[1], 'end_free'=>$e[1]]; 
    } 
} 
ksort($daily_free); 
print_r($daily_free); 
+0

うわー、テストされ、完璧に動作するようだ!私は@ guided1への正解を要求通りに授与しますが、2日後に賞金を得て、これを授与します!余分なマイルをお越しいただきありがとうございます。 – superphonic

+0

うわー。本当に余分なマイル! – guided1

1

純粋なMySQLソリューションです。真に実用的な、しかし、より多くの脳の運動好きではない:

SELECT r.* 
FROM (
    SELECT DISTINCT dd.`day`, 
     IF(dd.`ba` = 1, f.`free_before_start`, f.`free_after_start`) as `start`, 
     IF(dd.`ba` = 1, f.`free_before_end`, f.`free_after_end`) as `end` 
    FROM (
     SELECT 
      a.`day`, 
      IF(ISNULL(a.`id`) OR a.`before_day` < a.`day`, a.`day_start`, a.`before_end`) as `free_before_start`, 
      IF(ISNULL(a.`id`), a.`day_end`, a.`start`) as `free_before_end`, 
      IF(ISNULL(a.`id`), a.`day_start`, a.`end`) as `free_after_start`, 
      IF(ISNULL(a.`id`) OR a.`next_day` > a.`day`, a.`day_end`, a.`next_start`) as `free_after_end` 
     FROM (
      SELECT n.*, 
      b.`day` as `before_day`, b.`end` as `before_end`, 
      t.`day` as `next_day`, t.`start` as `next_start` 
      FROM (
       SELECT (@c1 := @c1 + 1) as `rowid`, s.* 
       FROM (
        SELECT d.`day`, d.`start` AS `day_start`, d.`end` AS `day_end`, 
          m.`id`, GREATEST(d.`start`, m.`start`) AS `start`, LEAST(d.`end`, m.`end`) AS `end`  
        FROM days d 
        LEFT JOIN `meetings` m ON 
          m.start <= d.end 
         AND m.end >= d.start 
        ORDER BY 2 ASC, 6 ASC 
       ) s 
       CROSS JOIN (SELECT @c1 := 0) AS dummy 
      ) n 
      LEFT JOIN 
      (
       SELECT (@c2 := @c2 + 1) as `rowid`, s.* 
       FROM (
        SELECT d.`day`, d.`start` AS `day_start`, d.`end` AS `day_end`, 
          m.`id`, GREATEST(d.`start`, m.`start`) AS `start`, LEAST(d.`end`, m.`end`) AS `end`  
        FROM days d 
        LEFT JOIN `meetings` m ON 
          m.start <= d.end 
         AND m.end >= d.start 
        ORDER BY 2 ASC, 6 ASC 
       ) s 
       CROSS JOIN (SELECT @c2 := 0) AS dummy 
      ) t on t.`rowid` = n.`rowid` + 1 
      LEFT JOIN 
      (
       SELECT (@c3 := @c3 + 1) as `rowid`, s.* 
       FROM (
        SELECT d.`day`, d.`start` AS `day_start`, d.`end` AS `day_end`, 
          m.`id`, GREATEST(d.`start`, m.`start`) AS `start`, LEAST(d.`end`, m.`end`) AS `end`  
        FROM days d 
        LEFT JOIN `meetings` m ON 
          m.start <= d.end 
         AND m.end >= d.start 
        ORDER BY 2 ASC, 6 ASC 
       ) s 
       CROSS JOIN (SELECT @c3 := 0) AS dummy 
      ) b on b.`rowid` = n.`rowid` - 1 
     ) a 
    ) f 
    INNER JOIN (
     SELECT `day`, 1 as `ba` FROM `days` 
     UNION ALL 
     SELECT `day`, 2 as `ba` FROM `days` 
     ORDER BY 1, 2 
    ) dd on f.`day` = dd.`day` 
    WHERE f.`free_before_start` <> f.`free_before_end` OR f.`free_after_start` <> f.`free_after_end` 
) r 
WHERE r.`start` <> r.`end` 

は、次のようworking hours表が必要です:

CREATE TABLE `days` (
    `day` date NOT NULL, 
    `start` timestamp NOT NULL, 
    `end` timestamp NOT NULL, 
    PRIMARY KEY (`day`) 
) ENGINE=InnoDB CHARSET=utf8; 

INSERT INTO `days` 
    (`day`, `start`, `end`) 
VALUES 
    ('2016-11-06', '2016-11-06 07:00:00', '2016-11-06 17:00:00'), 
    ('2016-11-07', '2016-11-07 07:00:00', '2016-11-07 17:00:00'), 
    ('2016-11-08', '2016-11-08 07:00:00', '2016-11-08 17:00:00'), 
    ('2016-11-09', '2016-11-09 07:00:00', '2016-11-09 17:00:00'), 
    ('2016-11-10', '2016-11-10 07:00:00', '2016-11-10 17:00:00'), 
    etc, around 250 working days per year. 

は、すべてのコード上の日付範囲を追加する大幅に短い間隔のために、それをスピードアップすることができますが、さらに読みにくくする。

関連する問題