Home > Software design >  Mysql datetime count the hours worked between a specific time
Mysql datetime count the hours worked between a specific time

Time:04-10

I'm looking for a way to count the hours worked between a given time range.

For example to count from the MySQL data below the hours worked between 22:00 and 06:00.

Using date_start 2022-04-01 21:00:00 and date_end 2022-04-02 08:00:00 the user worked 11 hours total and 8 night hours.

Of course the data could also be something like 2022-04-01 05:00:00 and 2022-04-01 16:00:00 which will then need to output 2 night hours or 2022-04-01 18:00:00and 2022-04-02 03:00:00 which outputs 5 night hours.

MySQL table:

CREATE TABLE `tasks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `date_start` datetime DEFAULT NULL,
  `date_end` datetime DEFAULT NULL,
  UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `tasks` (`date_start`,`date_end`) VALUES 
('2022-04-01 04:00:00', '2022-04-01 16:00:00'), # 2:00 nighthours
('2022-04-02 05:00:00', '2022-04-02 23:30:00'), # 2:30 nighthours
('2022-04-03 06:00:00', '2022-04-03 18:00:00'), # 0:00 nighthours
('2022-04-04 12:00:00', '2022-04-05 00:00:00'), # 2:00 nighthours
('2022-04-05 19:00:00', '2022-04-06 07:00:00'); # 8:00 nighthours

Current MySQL:

# 21600 = 06:00 hours
# 79200 = 22:00 hours

SELECT t.date_start, t.date_end, DATE_FORMAT(TIMEDIFF(
(CASE WHEN DATE(t.date_start) != DATE(t.date_end) AND TIME_TO_SEC(t.date_end) > 21600 THEN DATE_FORMAT(t.date_end, '%Y-%m-%d 06:%i:%s')
WHEN TIME_TO_SEC(t.date_start) < 21600 THEN DATE_FORMAT(t.date_start, '%Y-%m-%d 06:%i:%s')
ELSE t.date_end END), 
(CASE WHEN DATE(t.date_start) != DATE(t.date_end) AND TIME_TO_SEC(t.date_start) < 79200 THEN DATE_FORMAT(t.date_start, '%Y-%m-%d 22:%i:%s')
WHEN TIME_TO_SEC(t.date_end) > 79200 THEN DATE_FORMAT(t.date_start, '%Y-%m-%d 22:%i:%s')
WHEN DATE(t.date_start) = DATE(t.date_end) AND TIME_TO_SEC(t.date_end) <= 79200 AND TIME_TO_SEC(t.date_start) >= 21600 THEN t.date_end
ELSE t.date_start END)
), '%H:%i') AS night_time FROM tasks t;

Output:

date_start              date_end                night_time
2022-04-01 04:00:00     2022-04-01 16:00:00     02:00
2022-04-02 05:00:00     2022-04-02 23:30:00     -16:00
2022-04-03 06:00:00     2022-04-03 18:00:00     00:00
2022-04-04 12:00:00     2022-04-05 00:00:00     02:00
2022-04-05 19:00:00     2022-04-06 07:00:00     08:00

Currently I still have a problem in my current MySQL when the start_date and end_date both start on the same day and both have night hours. for example 2022-04-02 05:00:00 and 2022-04-02 23:30:00 which has 01:00 night hour in start_date and 1:30 hour in end_date (total night: 02:30 hours)

I am not sure if my current MySQL is the best/fastest way to achieve my goal.

CodePudding user response:

Calculating Time Overlaps

You can calculate the amount of time two date ranges overlap using:

  • MIN( EndDate1, EndDate2 ) - MAX( StartDate1, StartDate2 )

For example if the date ranges are:

Date_Start Date_End Night_Shift_Start Night_Shift_End
2022-04-01 21:00:00 2022-04-02 08:00:00 2022-04-01 22:00:00 ** 2022-04-02 06:00:00 **

The result would be 8 hours:

Min( EndDate ) - Max( StartDate ) .... As Unix Timestamps Time Overlap
2022-04-02 06:00:00 (minus) 2022-04-01 22:00:00 1648875600 - 1648846800 = 28800 seconds 08:00:00 hours

Checking for Multiple Overlaps

Since technically a single shift could have both started and ended during "night hours" (22:00 to 06:00) you need check for overlaps on both sides.

Date_Start Date_End Night Hours ...
2022-04-05 05:00:00 2022-04-05 23:30:00 2.5 hours (1 hour) : 2022-04-05 05:00 to 2022-04-05 06:00
(1.5 hours) : 2022-04-05 22:00 to 2022-04-05 23:30

One approach is using the base start/end times to calculate the previous and upcoming "night hour" periods:

SELECT  *
        , TIMESTAMP(DATE(date_start) - INTERVAL 1 DAY, '22:00:00') AS current_start
        , TIMESTAMP(DATE(date_start), '06:00:00') AS current_end
        , TIMESTAMP(DATE(date_start), '22:00:00') AS next_start
        , TIMESTAMP(DATE(date_start)   INTERVAL 1 DAY, '06:00:00') AS next_end
FROM   tasks

Results:

id date_start date_end current_start current_end next_start next_end
1 2022-04-01 04:00:00 2022-04-01 16:00:00 2022-03-31 22:00:00 2022-04-01 06:00:00 2022-04-01 22:00:00 2022-04-02 06:00:00
2 2022-04-02 05:00:00 2022-04-02 23:30:00 2022-04-01 22:00:00 2022-04-02 06:00:00 2022-04-02 22:00:00 2022-04-03 06:00:00
3 2022-04-03 06:00:00 2022-04-03 18:00:00 2022-04-02 22:00:00 2022-04-03 06:00:00 2022-04-03 22:00:00 2022-04-04 06:00:00
4 2022-04-04 12:00:00 2022-04-05 00:00:00 2022-04-03 22:00:00 2022-04-04 06:00:00 2022-04-04 22:00:00 2022-04-05 06:00:00
5 2022-04-05 19:00:00 2022-04-06 07:00:00 2022-04-04 22:00:00 2022-04-05 06:00:00 2022-04-05 22:00:00 2022-04-06 06:00:00
6 2022-04-01 04:00:00 2022-04-01 16:00:00 2022-03-31 22:00:00 2022-04-01 06:00:00 2022-04-01 22:00:00 2022-04-02 06:00:00
7 2022-04-05 19:00:00 2022-04-06 07:00:00 2022-04-04 22:00:00 2022-04-05 06:00:00 2022-04-05 22:00:00 2022-04-06 06:00:00
8 2022-04-05 05:00:00 2022-04-05 23:30:00 2022-04-04 22:00:00 2022-04-05 06:00:00 2022-04-05 22:00:00 2022-04-06 06:00:00



Total Overlap Time

Once you have the "night hour" ranges, calculate the overlapping time on both sides and add them together to get the total time worked during "night hours"

SELECT id
       , date_start
       , date_end
       , SEC_TO_TIME( 
            GREATEST(0, start_overlap__in_seconds)  -- ignore negative time, which means no overlap
              GREATEST(0, end_overlap_in_seconds)
       ) AS time_overall
FROM  ( 
          SELECT * 
                 , UNIX_TIMESTAMP(LEAST(date_end, current_end))
                   - UNIX_TIMESTAMP(GREATEST(date_start, current_start))
                 AS start_overlap__in_seconds                   
                 , UNIX_TIMESTAMP(LEAST(date_end,next_end))
                   - UNIX_TIMESTAMP(GREATEST(date_start,next_start))
                 AS end_overlap_in_seconds                   
          FROM  (
                  SELECT  *
                          , TIMESTAMP(DATE(date_start) - INTERVAL 1 DAY, '22:00:00') AS current_start
                          , TIMESTAMP(DATE(date_start), '06:00:00') AS current_end
                          , TIMESTAMP(DATE(date_start), '22:00:00') AS next_start
                          , TIMESTAMP(DATE(date_start)   INTERVAL 1 DAY, '06:00:00') AS next_end
                  FROM   tasks
                ) tmp 
     ) t

Final Results:

id date_start date_end time_overall
1 2022-04-01 04:00:00 2022-04-01 16:00:00 02:00:00
2 2022-04-02 05:00:00 2022-04-02 23:30:00 02:30:00
3 2022-04-03 06:00:00 2022-04-03 18:00:00 00:00:00
4 2022-04-04 12:00:00 2022-04-05 00:00:00 02:00:00
5 2022-04-05 19:00:00 2022-04-06 07:00:00 08:00:00
6 2022-04-01 04:00:00 2022-04-01 16:00:00 02:00:00
7 2022-04-05 19:00:00 2022-04-06 07:00:00 08:00:00
8 2022-04-05 05:00:00 2022-04-05 23:30:00 02:30:00

db<>fiddle here

  • Related