PHP: Generate Carbon array with opening and closing times (per day) - php

The desired result
I would like to have an associative array that contains a range of times (between opening and closing times) with an interval of 15 minutes. For example:
[
'2017-01-16' => [ // Start of the week (Monday)
'08:00', // Opening time
'08:15',
'08:30',
// Etc..
'18:00', // Closing time
],
'2017-01-17' => [ // Tuesday
'10:00', // Opening time
'10:15',
'10:30',
// Etc..
'22:00', // Closing time
],
// For every day in the week.
];
Another thing I would like to be able to do, is: Take a range of times (e.g. 09:00 - 10:00) and remove it from the array (at a specific date key)
The steps I made (so far)
I have an array that looks just like the one above. But.. it starts with 00:00 and ends at 23:45. With the following code (mainly from another question at Stackoverflow):
private function generateDateRange(Carbon $start_date, Carbon $end_date)
{
$dates = [];
while ($start_date->lte($end_date)) {
if(! array_key_exists($start_date->format('Y-m-d'), $dates)) {
$dates[$start_date->format('Y-m-d')] = [];
} else {
array_push($dates[$start_date->format('Y-m-d')], $start_date->format('H:i'));
if(in_array($start_date->format('H:i'), $dates[$start_date->format('Y-m-d')])) {
$start_date->addMinutes(15);
} else {
$start_date->addDay();
}
}
}
return $dates;
}
$start = Carbon::now()->startOfWeek();
$end = Carbon::now()->endOfWeek();
$range = $this->generateDateRange($start, $end);
My question
How can I do this in PHP (Laravel)? I am planning to make this (more) dynamic by using a database. But first I want to have a working basic.
Does someone know what I could do to reach the desired result?

Try this:
private function generateDateRange(Carbon $start_date, Carbon $end_date,$slot_duration = 15)
{
$dates = [];
$slots = $start_date->diffInMinutes($end_date)/$slot_duration;
//first unchanged time
$dates[$start_date->toDateString()][]=$start_date->toTimeString();
for($s = 1;$s <=$slots;$s++){
$dates[$start_date->toDateString()][]=$start_date->addMinute($slot_duration)->toTimeString();
}
return $dates;
}

Related

how to return an arrays of the free days using DateTime

I'm using Laravel 9 to create lessons scheduler,
I tried that way but Im little lost,
$free_time_frame = [[]]; // nested array here
$start = new DateTime("2023-01-01");
$end = new DateTime("2023-12-31");
$interval = new DateInterval("P1D"); // 1 month interval
$period = new DatePeriod($start, $interval, $end);
$seasons = ["2023-02-01", "2023-02-13"];
foreach ($period as $date) {
if (in_array($date->format("Y-m-d"), $seasons)) {
// Skip the rest of the loop if the current month is in the $seasons array
$free_time_frame[] = []; // append nested array
continue;
}
// Set the start date to the first day of the month
$start_date = new DateTime($date->format("Y-m-01"));
// Set the end date to the last day of the month
$end_date = new DateTime($date->format("Y-m-t"));
// Calculate the number of days between the start and end dates
$diff = $start_date->diff($end_date);
$days = $diff->days + 1; // Add 1 to include the end date
// use the latest nested array
}
I'm booking $seasons I would like to have an array of the free days before "2023-02-01" and days after "2023-02-13" like that :
Expected Result
[
[
"2023-01-01",
"2023-01-02",
"2023-01-03",
"..."
"2023-01-31",
]
[
"2023-02-14",
"2023-02-15",
"2023-02-16",
"2023-02-14",
"and so on util the end of the year"
]
]
Thanks for the help in advance
To calculate free time from $seasons that doesn't include in-between time period, it is just the everything before your first element of your seasons array and everything after last element of your seasons array.
So, achieving this is just running 2 loops of DatePeriod in those ranges.
<?php
$seasons = ["2023-02-01", "2023-02-13", "2023-03-16"];
$free_time_frame = [];
$free_time_frame[] = getDaysInPeriod('2023-01-01', 'P1D', current($seasons));
$free_time_frame[] = getDaysInPeriod(date("Y-m-d", strtotime(end($seasons). " +1 days")), 'P1D', '2024-01-01');
function getDaysInPeriod($startDate, $dateInterval, $endDate){
$span = [];
foreach(new DatePeriod(new DateTime($startDate), new DateInterval($dateInterval),new DateTime($endDate)) as $p){
$span[] = $p->format('Y-m-d');
}
return $span;
}
$free_time_frame = array_filter($free_time_frame);
print_r($free_time_frame);
Online Fiddle
I think you can do the following:
$startDate = '2023-01-01';
$endDate = '2023-03-31';
$seasons = ['2023-02-01', '2023-02-13'];
$prepareSeasons = [
[ $startDate, Carbon::parse($seasons[0])->subDay()->format('Y-m-d') ],
[ Carbon::parse($seasons[0])->addDay()->format('Y-m-d'), $endDate ]
];
// this is want we want to achieve
// $prepareSeasons = [
// [$startDate, '2023-01-31'],
// ['2023-02-14', $endDate],
// ];
$freeTimeFrame = [];
foreach ($prepareSeasons as $seasonDates)
{
$freeSeason = [];
$seasonDates = CarbonPeriod::create($seasonDates[0], $seasonDates[1])->toArray();
foreach ($seasonDates as $date)
{
array_push($freeSeason, $date->format('Y-m-d'));
}
array_push($freeTimeFrame, $freeSeason);
}
return $freeTimeFrame;
The code above will achieve your expected result
[
[
"2023-01-01",
"2023-01-02",
"2023-01-03",
"...",
"...",
"2023-01-31",
],
[
"2023-02-14",
"2023-02-15",
"2023-02-16",
"2023-02-14",
"...",
"...",
"2023-12-31",
]
]
I hope this can help you.
Okay, so I think I understand, that you want to get all dates between a range of dates, then exclude a specific range of date.
You can do it like this:
$originalDates = \Carbon\CarbonPeriod::between('2023-01-01', '2023-01-10')->toArray();
$excludedDates = \Carbon\CarbonPeriod::between('2023-01-03', '2023-01-06')->toArray();
$finalDates = [];
foreach ($originalDates as $date) {
if(!in_array($date, $excludedDates)){
array_push($finalDates, $date->format('d-m-Y'));
}
}
return $finalDates;
This will return an array of the following results:
01-01-2023
02-01-2023
07-01-2023
08-01-2023
09-01-2023
10-01-2023
Notice here that it skipped over from 2023-01-03 to 2023-01-06
You can format this as you like by changing the line of $date->format('d-m-Y') to represent the results the way you want it, you can even remove the format to return a carbon array, which you can parse as you like somewhere else.
And you can go one step further, and have multiple excludedDates, and check for them in_array
Hopefully this helps, add a comment if you want further clarifications or modifications.

Generate random dates with random times between two dates for selected period and frequency

I have to create a scheduling component that will plan e-mails that need to be sent out. Users can select a start time, end time, and frequency. Code should produce a random moment for every frequency, between start and end time. Outside of office hours.
Paramaters:
User can select a period between 01/01/2020 (the start) and 01/01/2021 (the end). In this case user selects a timespan of one exactly year.
User can select a frequency. In this case user selects '2 months'.
Function:
Code produces a list of datetimes. The total time (one year) is divided by frequency (2 months). We expect a list of 6 datetimes.
Every datetime is a random moment in said frequency (2 months). Within office hours.
Result:
An example result for these paramaters might as follows, with the calculated frequency bounds for clarity:
[jan/feb] 21-02-2020 11.36
[mrt/apr] 04-03-2020 16.11
[mei/jun] 13-05-2020 09.49
[jul-aug] 14-07-2020 15.25
[sep-okt] 02-09-2020 14.09
[nov-dec] 25-12-2020 13.55
--
I've been thinking about how to implement this best, but I can't figure out an elegant solution.
How could one do this using PHP?
Any insights, references, or code spikes would be greatly appreciated. I'm really stuck on this one.
I think you're just asking for suggestions on how to generate a list of repeating (2 weekly) dates with a random time between say 9am and 5pm? Is that right?
If so - something like this (untested, pseudo code) might be a starting point:
$start = new Datetime('1st January 2021');
$end = new Datetime('1st July 2021');
$day_start = 9;
$day_end = 17;
$date = $start;
$dates = [$date]; // Start date into array
while($date < $end) {
$new_date = clone($date->modify("+ 2 weeks"));
$new_date->setTime(mt_rand($day_start, $day_end), mt_rand(0, 59));
$dates[] = $new_date;
}
var_dump($dates);
Steve's anwser seems good, but you should consider 2 additional things
holiday check, in the while after first $new_date line, like:
$holiday = array('2021-01-01', '2021-01-06', '2021-12-25');
if (!in_array($new_date,$holiday))
also a check if date is a office day or a weekend in a similar way as above with working days as an array.
It's kind of crappy code but I think it will work as you wish.
function getDiffInSeconds(\DateTime $start, \DateTime $end) : int
{
$startTimestamp = $start->getTimestamp();
$endTimestamp = $end->getTimestamp();
return $endTimestamp - $startTimestamp;
}
function getShiftData(\DateTime $start, \DateTime $end) : array
{
$shiftStartHour = \DateTime::createFromFormat('H:i:s', $start->format('H:i:s'));
$shiftEndHour = \DateTime::createFromFormat('H:i:s', $end->format('H:i:s'));
$shiftInSeconds = intval($shiftEndHour->getTimestamp() - $shiftStartHour->getTimestamp());
return [
$shiftStartHour,
$shiftEndHour,
$shiftInSeconds,
];
}
function dayIsWeekendOrHoliday(\DateTime $date, array $holidays = []) : bool
{
$weekendDayIndexes = [
0 => 'Sunday',
6 => 'Saturday',
];
$dayOfWeek = $date->format('w');
if (empty($holidays)) {
$dayIsWeekendOrHoliday = isset($weekendDayIndexes[$dayOfWeek]);
} else {
$dayMonthDate = $date->format('d/m');
$dayMonthYearDate = $date->format('d/m/Y');
$dayIsWeekendOrHoliday = (isset($weekendDayIndexes[$dayOfWeek]) || isset($holidays[$dayMonthDate]) || isset($holidays[$dayMonthYearDate]));
}
return $dayIsWeekendOrHoliday;
}
function getScheduleDates(\DateTime $start, \DateTime $end, int $frequencyInSeconds) : array
{
if ($frequencyInSeconds < (24 * 60 * 60)) {
throw new \InvalidArgumentException('Frequency must be bigger than one day');
}
$diffInSeconds = getDiffInSeconds($start, $end);
// If difference between $start and $end is bigger than two days
if ($diffInSeconds > (2 * 24 * 60 * 60)) {
// If difference is bigger than 2 days we add 1 day to start and subtract 1 day from end
$start->modify('+1 day');
$end->modify('-1 day');
// Getting new $diffInSeconds after $start and $end changes
$diffInSeconds = getDiffInSeconds($start, $end);
}
if ($frequencyInSeconds > $diffInSeconds) {
throw new \InvalidArgumentException('Frequency is bigger than difference between dates');
}
$holidays = [
'01/01' => 'New Year',
'18/04/2020' => 'Easter 1st official holiday because 19/04/2020',
'20/04/2020' => 'Easter',
'21/04/2020' => 'Easter 2nd day',
'27/04' => 'Konings',
'04/05' => '4mei',
'05/05' => '4mei',
'24/12' => 'Christmas 1st day',
'25/12' => 'Christmas 2nd day',
'26/12' => 'Christmas 3nd day',
'27/12' => 'Christmas 3rd day',
'31/12' => 'Old Year'
];
[$shiftStartHour, $shiftEndHour, $shiftInSeconds] = getShiftData($start, $end);
$amountOfNotifications = floor($diffInSeconds / $frequencyInSeconds);
$periodInSeconds = intval($diffInSeconds / $amountOfNotifications);
$maxDaysBetweenNotifications = intval($periodInSeconds / (24 * 60 * 60));
// If $maxDaysBetweenNotifications is equals to 1 then we have to change $periodInSeconds to amount of seconds for one day
if ($maxDaysBetweenNotifications === 1) {
$periodInSeconds = (24 * 60 * 60);
}
$dates = [];
for ($i = 0; $i < $amountOfNotifications; $i++) {
$periodStart = clone $start;
$periodStart->setTimestamp($start->getTimestamp() + ($i * $periodInSeconds));
$seconds = mt_rand(0, $shiftInSeconds);
// If $maxDaysBetweenNotifications is equals to 1 then we have to check only one day without loop through the dates
if ($maxDaysBetweenNotifications === 1) {
$interval = new \DateInterval('P' . $maxDaysBetweenNotifications . 'DT' . $seconds . 'S');
$date = clone $periodStart;
$date->add($interval);
$dayIsWeekendOrHoliday = dayIsWeekendOrHoliday($date, $holidays);
} else {
// When $maxDaysBetweenNotifications we have to loop through the dates to pick them
$loopsCount = 0;
$maxLoops = 3; // Max loops before breaking and skipping the period
do {
$day = mt_rand(0, $maxDaysBetweenNotifications);
$periodStart->modify($shiftStartHour);
$interval = new \DateInterval('P' . $day . 'DT' . $seconds . 'S');
$date = clone $periodStart;
$date->add($interval);
$dayIsWeekendOrHoliday = dayIsWeekendOrHoliday($date, $holidays);
// If the day is weekend or holiday then we have to increment $loopsCount by 1 for each loop
if ($dayIsWeekendOrHoliday === true) {
$loopsCount++;
// If $loopsCount is equals to $maxLoops then we have to break the loop
if ($loopsCount === $maxLoops) {
break;
}
}
} while ($dayIsWeekendOrHoliday);
}
// Adds the date to $dates only if the day is not a weekend day and holiday
if ($dayIsWeekendOrHoliday === false) {
$dates[] = $date;
}
}
return $dates;
}
$start = new \DateTime('2020-12-30 08:00:00', new \DateTimeZone('Europe/Sofia'));
$end = new \DateTime('2021-01-18 17:00:00', new \DateTimeZone('Europe/Sofia'));
$frequencyInSeconds = 86400; // 1 day
$dates = getScheduleDates($start, $end, $frequencyInSeconds);
var_dump($dates);
You have to pass $start, $end and $frequencyInSeconds as I showed in example and then you will get your random dates. Notice that I $start and $end must have hours in them because they are used as start and end hours for shifts. Because the rule is to return a date within a shift time only in working days. Also you have to provide frequency in seconds - you can calculate them outside the function or you can change it to calculate them inside. I did it this way because I don't know what are your predefined periods.
This function returns an array of \DateTime() instances so you can do whatever you want with them.
UPDATE 08/01/2020:
Holidays now are part of calculation and they will be excluded from returned dates if they are passed when you are calling the function. You can pass them in d/m and d/m/Y formats because of holidays like Easter and in case when the holiday is on weekend but people will get additional dayoff during the working week.
UPDATE 13/01/2020:
I've made updated code version to fix the issue with infinite loops when $frequencyInSeconds is shorter like 1 day. The new code used few functions getDiffInSeconds, getShiftData and dayIsWeekendOrHoliday as helper methods to reduce code duplication and cleaner and more readable code

array assignment using php end does not work as expected

If I have an array:
[
start => 2018-01-01 12:00
end => 2018-01-01 15:00
],
[
start => 2018-01-01 12:00
end => 2018-01-01 17:00
],
This array can have any number of start and end dates, and I will need to filter each day by their start and end time. Ie, 2018-01-01 could have 5 entries with different start and end times.
I need to end up with a single array with the earlier start time, and the LATEST end time:
[
start => 2018-01-01 12:00
end => 2018-01-01 17:00
]
In my loop, I'm comparing the end times and if the second is larger than the first, I want to replace the value of end to this new one:
$newArr[] = $dates[0];
foreach($dates as $date) {
if ($date['end'] > end($newArr)['end'] {
end($newArr)['end'] = $date['end']
}
}
For some reason, this assignment is not actually working - it remains as the initial setting end => 15:00
Hope that's clear :)
The Problem is, that end() returns a value, not a reference.
By pointing manually to the last index it would work as expected:
$newArr[] = $dates[0];
foreach($dates as $date) {
if ($date['end'] > $newArr[max(array_keys($newArr))]['end']) {
$newArr[max(array_keys($newArr))]['end'] = $date['end'];
}
}
echo "<pre>";
var_dump($newArr);
echo "</pre>";
Working snippet: https://3v4l.org/RCqA0
No reason to use end() in either place since you want only one element in the result:
$newArr[] = $dates[0];
foreach($dates as $date) {
if ($date['end'] > $newArr[0]['end']) {
$newArr[0]['end'] = $date['end'];
}
if ($date['start'] < $newArr[0]['start']) {
$newArr[0]['start'] = $date['start'];
}
}
Also, your current code only checks and adds end not start.
You might want to try to convert datetime to timestamp before doing the comparison.
$timestamp['end'] = strtotime($date['end']);

count how many days within a date range are within another date range

From October 1st to March 31 the fee is $1 (season 1). From April 1st to September 30 the fee is $2 (season 2).
How can I calculate the total fee of a given date range (user input) depending on how many days of this date range fall into season 1 and season 2?
The following gives me the number of days of the userĀ“s date range, but I have no idea how to test against season 1 or season 2:
$user_input_start_date = getdate( $a );
$user_input_end_date = getdate( $b );
$start_date_new = mktime( 12, 0, 0, $user_input_start_date['mon'], $user_input_start_date['mday'], $user_input_start_date['year'] );
$end_date_new = mktime( 12, 0, 0, $user_input_end_date['mon'], $user_input_end_date['mday'], $user_input_end_date['year'] );
return round( abs( $start_date_new - $end_date_new ) / 86400 );
Given that a date range starts and ends in 2012 or starts in 2012 and ends in 2013 alone gives me 10 different possibilities of in which season a date range can start and where it can end.
There must be a better solution than iterating if/else and comparing dates over and over again for the following conditions:
Date range is completely within season 1
Date range starts in season 1 and ends in season 2
Date range starts in season 1, spans across season 2 and ends in the second part of season 1
... and so forth with "Starts in season 2", etc
This not a duplicate of How many days until X-Y-Z date? as that only deals with counting the number of days. It does not address the issue of comparing one date range with another.
The key to this problem is to simplify it as much as possible. I think using an array as a lookup table for the cost of each day of the year is the way to go. The first thing to do then, is to generate the array. The array just represents each day of the year and doesn't represent any particular year. I chose to use 2012 to generate the lookup array as it is a leap year and so has every possible day in it.
function getSeasonArray()
{
/**
* I have chosen 2012 as it was a leap year. All we want to do is
* generate an array which has avery day of the year in it.
*/
$startDate = new DateTime('1st January 2012');
//DatePeriod always drops the last day.
$endDate = new DateTime('1st January 2013');
$season2Start = new DateTime('1st April 2012');
$season2End = new DateTime('1st October 2012');
$allDays = new DatePeriod($startDate, new DateInterval('P1D'), $endDate);
$season2Days = new DatePeriod($season2Start, new DateInterval('P1D'), $season2End);
$seasonArray = array();
foreach($allDays as $day){
$seasonArray[] = $day->format('d-M');
$seasonArray[$day->format('d-M')]['season'] = 1;
}
foreach($season2Days as $day){
$seasonArray[$day->format('d-M')]['season'] = 2;
}
return $seasonArray;
}
Once that is done you just need the period over which to calculate:-
$bookingStartDate = new DateTime();//Or wherever you get this from
$bookingEndDate = new DateTime();
$bookingEndDate->setTimestamp(strtotime('+ 7 month'));//Or wherever you get this from
$bookingPeriod = new DatePeriod($bookingStartDate, new DateInterval('P1D'), $bookingEndDate);
Then we can do the calculation:-
$seasons = getSeasonArray();
$totalCost = 0;
foreach($bookingPeriod as $day){
$totalCost += $seasons[$day->format('d-M')]['season'];
var_dump($day->format('d-M') . ' = $' . $seasons[$day->format('d-M')]['season']);
}
var_dump($totalCost);
I have chosen a long booking period, so that you can scan through the var_dump() output and verify the correct price for each day of the year.
This is a quick stab done between distractions at work and I'm sure that with a bit of thought you can mould it into a more elegant solution. I'd like to get rid of the double iteration for example, unfortunately, work pressures prevent me from spending further time on this.
See the PHP DateTime man page for further information on these useful classes.
At first I suggested using the DateTime class that PHP provides, naively assuming that it has some kind of thought-out API that one could use. It turns out that it does not. While it features very basic DateTime functionality, it is mostly unusable because, for most operations, it relies on the DateInterval class. In combination, those classes represent another masterpiece of bad API design.
An interval should be defined like so:
An interval in Joda-Time represents an interval of time from one millisecond instant to another instant. Both instants are fully specified instants in the datetime continuum, complete with time zone.
In PHP, however, an Interval is just a duration:
A date interval stores either a fixed amount of time (in years, months, days, hours etc) or a relative time string [such as "2 days"].
Unfortunately, PHP's DateInterval definition does not allow for intersection/overlap calculation (which the OP needs) because PHP's Intervals have no specific position in the datetime continuum. Therefore, I've implemented a (very rudimentary) class that adheres to JodaTime's definition of an interval. It is not extensively tested, but it should get the work done:
class ProperDateInterval {
private $start = null;
private $end = null;
public function __construct(DateTime $start, DateTime $end) {
$this->start = $start;
$this->end = $end;
}
/**
* Does this time interval overlap the specified time interval.
*/
public function overlaps(ProperDateInterval $other) {
$start = $this->getStart()->getTimestamp();
$end = $this->getEnd()->getTimestamp();
$oStart = $other->getStart()->getTimestamp();
$oEnd = $other->getEnd()->getTimestamp();
return $start < $oEnd && $oStart < $end;
}
/**
* Gets the overlap between this interval and another interval.
*/
public function overlap(ProperDateInterval $other) {
if(!$this->overlaps($other)) {
// I haven't decided what should happen here yet.
// Returning "null" doesn't seem like a good solution.
// Maybe ProperDateInterval::EMPTY?
throw new Exception("No intersection.");
}
$start = $this->getStart()->getTimestamp();
$end = $this->getEnd()->getTimestamp();
$oStart = $other->getStart()->getTimestamp();
$oEnd = $other->getEnd()->getTimestamp();
$overlapStart = NULL;
$overlapEnd = NULL;
if($start === $oStart || $start > $oStart) {
$overlapStart = $this->getStart();
} else {
$overlapStart = $other->getStart();
}
if($end === $oEnd || $end < $oEnd) {
$overlapEnd = $this->getEnd();
} else {
$overlapEnd = $other->getEnd();
}
return new ProperDateInterval($overlapStart, $overlapEnd);
}
/**
* #return long The duration of this interval in seconds.
*/
public function getDuration() {
return $this->getEnd()->getTimestamp() - $this->getStart()->getTimestamp();
}
public function getStart() {
return $this->start;
}
public function getEnd() {
return $this->end;
}
}
It may be used like so:
$seasonStart = DateTime::createFromFormat('j-M-Y', '01-Apr-2012');
$seasonEnd = DateTime::createFromFormat('j-M-Y', '30-Sep-2012');
$userStart = DateTime::createFromFormat('j-M-Y', '01-Jan-2012');
$userEnd = DateTime::createFromFormat('j-M-Y', '02-Apr-2012');
$i1 = new ProperDateInterval($seasonStart, $seasonEnd);
$i2 = new ProperDateInterval($userStart, $userEnd);
$overlap = $i1->overlap($i2);
var_dump($overlap->getDuration());

Split time by day of the week with an interval defined by two Zend_Date

I have two Zend_Date that represent an interval :
$start = new Zend_Date($punch->getStart());
$end = new Zend_Date($punch->getEnd());
$nbHours = $start->sub($end , Zend_Date::HOUR);
$nbMinutes = $start->sub($end , Zend_Date::MINUTE);
$hoursTotal = $nbHours->get(Zend_Date::HOUR);
$minutesTotal = $nbMinutes->get(Zend_Date::MINUTE);
Is there an simple way to split the interval by day of the week with Zend_Date when the interval > 24 hours?
For example, if I have an interval from Monday 8am to Tuesday 4:30pm, I would like to have an array containing monday = 16h and tuesday = 16:30.
You don't need to use Zend_Date for this, in fact it is probably better not to. You should use the date/time classes in PHP instead.
If I understand your question correctly you want an array of days and the hours worked for those days.
I first created a mock class to reflect your code example, I have assumed it is returning timestamps:-
class Punch
{
public function getStart()
{
return time();
}
public function getEnd()
{
return strtotime('+36 hours 45 minutes');
}
}
Then we set up the DateTime objects-
$Punch = new Punch();
$start = new DateTime();
$start->setTimestamp($Punch->getStart());
$end = new DateTime();
$end->setTimestamp($Punch->getEnd());
Then we use a DateInterval object to generate our iterable DatePeriod:-
$interval = new DateInterval('PT1M');
$minutes = new DatePeriod($start, $interval, $end);
Then we simply iterate over it counting the minutes worked in each day:-
$format = 'l';
foreach($minutes as $minute){
if(!isset($result[$minute->format($format)])) $result[$minute->format($format)] = 0;
$result[$minute->format($format)]++;
}
See the manual page for acceptable formats.
We now have the number of minutes worked in each day, converting them to hours is trivial:-
foreach($result as $key => $r){
$result[$key] = $r/60;
}
var_dump($result);
Output (Obviously, you will get a different result running it at a different time) :-
array
'Monday' => float 17.483333333333
'Tuesday' => float 19.266666666667
So on Monday 17.48 hours were worked and 19.27 on Tuesday.
Alternatively:-
foreach($result as $key => $r){
$result[$key] = floor($r/60) . ':' . $r % 60;
}
Would give the following output if that is closer to what you want:-
array
'Monday' => string "17:29"
'Tuesday' => string "19:16"
That's the simplest way I can think of doing it.

Categories