从开始和结束日期开始的PHP空闲时间块

时间:2016-11-23 10:43:45

标签: php mysql arrays datetime multidimensional-array

我试图完全从MySQL查询中获取这些空闲时间块,但是我无法构建一个可以处理数据的查询。我选择从查询中获取startend日期,并在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,不包括周末。因此,例如,上面的会议时间数组应该产生以下空闲时间数组:

$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月7日和8日没有空闲时间所以它们根本没有出现。此外,11月12日和13日没有出现在空闲时间阵列中,这是因为它们在周末出现。但是,如果第12和第13个不是周末,它们应该在空闲时间数组中显示为空闲时间的整天(09:00到17:00)。

我能想到实现这一目标的唯一方法是创建每天的数组,并且在其中有一个从05:00到17:00的5分钟增量数组,其值为NULL。然后遍历此数组,检查每个日期和5分钟增量是否在第一个数组的开始和结束时间内,如果是,则将其标记为忙。然后我可以再次循环并获取每天空值的开始和结束时间?这似乎不是一个很好的解决方案,如果会议时间少于几分钟也会失败。

我认为此时我不能看到树木用于树木,而且可能已经过度思考了。

还有其他解决方案吗?

修改 SQL Fiddle适用于任何想要通过SQL尝试的人

4 个答案:

答案 0 :(得分: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 :(得分:3)

遵循@ guided1的逻辑,我开始运行一些测试,我越来越开始编写整个算法,就在这里。请转到@ guided1

LinearLayout

答案 2 :(得分: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.

在整个代码中添加日期范围可以在很短的时间间隔内显着加快速度,但会降低其可读性。

答案 3 :(得分:0)

不是使用您拥有的日期和日期,而是将值存储为## create dummy raster n <- 50 r <- raster(ncol=n, nrow=n, xmn=4, xmx=10, ymn=52, ymx=54) projection(r) <- "+proj=longlat +datum=WGS84 +ellps=WGS84 +towgs84=0,0,0" values(r) <- 1:n^2+rnorm(n^2) n.side <- 2 # number of tiles per side dx <- (extent(r)[2]- extent(r)[1])/ n.side # extent of one tile in x direction dy <- (extent(r)[4]- extent(r)[3])/ n.side # extent of one tile in y direction xs <- seq(extent(r)[1], by= dx, length= n.side) #lower left x-coordinates ys <- seq(extent(r)[3], by= dy, length= n.side) #lower left y-coordinates cS <- expand.grid(x= xs, y= ys) ## loop over extents and crop for(i in 1:nrow(cS)) { ex1 <- c(cS[i,1], cS[i,1]+dx, cS[i,2], cS[i,2]+dy) # create extents for cropping raster cl1 <- crop(r, ex1) # crop raster by extent writeRaster(x = cl1, filename=paste("test",i,".tif", sep=""), format="GTiff", overwrite=T) # write to file } ## check functionality... test <- raster(paste("test1.tif", sep="")) plot(test) 个对象,然后使用这些将日期转换为UNIX时间戳。这些只是自UNIX Epoch(1970-01-01 00:00:00)开始以来的秒的整数值。

现在,您可以进行简单的数学计算,找出日期范围内的差距(将开始日期/时间和结束日期/时间转换为结束标记的时间戳)。

一旦您将一个开始结束块标识为时间戳,您就可以将其反馈到DateTime对象并将其转换回标准日期表示。

识别空闲块的长度也很简单。由于值均为秒数,因此从结束时间减去开始时间将产生秒数差异。为了确保它大于15分钟,你可以将它与整数值900(60 * 15)进行比较,如果它大于那么差距大于15分钟。