PHP: Algorithm for finding available appointment times to choose from - php

A practice needs to schedule an appointment with a patient and assign that appointment to a doctor.
An appointment comprises of various tests, and a start time.
A test comprises of a name and a duration.
As such, we can calculate required duration by summing the duration of all required tests.
The practice can select a date, doctor, and the required tests - using ajax, I want to be able to return a json object of available time slots for the patient to choose from.
Build an array of all available times (20 minute intervals between open and close).
-
$practiceOpen = strtotime($input['date'] . ' 10:00');
$practiceClose = strtotime($input['date'] . ' 18:00');
$interval = 20 * 60; // 20 mins
while ($start < ( $end - $requiredDuration * 60 ) )
{
$times[] = date( "Y-m-d H:i", $start);
$start += $interval;
}
Get a collection of all appointments on specified date for that doctor.
nested foreach with appointments[] (2) and times[] (1) to remove all unavailable times from the master times[].
example
foreach ($appointments as $appointment)
{
foreach($times as $key => $time)
{
$time = strtotime($time);
if (
$time + $requiredDuration * 60 >= strtotime($appointment->appointment)
&&
$time < strtotime($appointment->appointment) + $appointment->duration * 60
) { unset($times[$key]); }
}
}
The if statement basically says
**If**
start time plus the required duration of the tests is larger than the start time of the existing appointment **AND**
start time is less than existing appointment + duration of existing appointment
**Then** Remove that timeslot from array.
The question: The above snippet / algorithm appears to work, however what is wanted was a pointer in the right direction for:
Is this the proper / most efficient way of tackling the problem?
Are there known algorithms which handle this which I could implement which would work better or faster than my implementation?
Is there a way to test this properly to see if I am not excluding appointment times which should be available or including times which are not available?

To those looking for a algorithm to find all the times available for the day, taking into consideration the already unavailable times, here's what I have done:
First decide what is the range of time you are accepting bookings (ex.: from 08:00am to 08:00pm)
Avoid multiple database query. Instead of checking database for each time we are looping, get all the unavailable slots into an array (single query only)
Decide what is the amount of minutes every session/booking takes
Decide what is the gap between times selection (mine is usually 5 minutes)
Full code:
$start = strtotime('08:00');
$end = strtotime('20:00');
$minutesPerSession = 30;
$minutesPerGap = 5;
$verifyDay = date('Y-m-d'); // set the day to check
// query database here and grab all the unavailables into an array
$unavailables =
[
['start_at' => '2021-12-27 09:00', 'end_at' => '2021-12-27 09:30'],
['start_at' => '2021-12-27 10:20', 'end_at' => '2021-12-27 10:50'],
['start_at' => '2021-12-27 11:00', 'end_at' => '2021-12-27 11:30'],
];
$slotsAvailable = [];
for ($i = $start; $i <= $end; $i = $i + $minutesPerGap * 60)
{
$time = date('H:i', $i);
$range = date('H:i', strtotime("{$minutesPerSession} minutes", $i));
$dateStart = $verifyDay . ' ' . $time;
$dateEnd = $verifyDay . ' ' . $range;
$isAvailable = true;
foreach ($unavailables as $key => $unavailable)
{
if
(
($unavailable['start_at'] <= $dateStart && $unavailable['end_at'] >= $dateStart) ||
($unavailable['start_at'] <= $dateEnd && $unavailable['end_at'] >= $dateEnd) ||
($unavailable['start_at'] >= $dateStart && $unavailable['end_at'] <= $dateEnd)
)
{
$isAvailable = false;
break;
}
}
if ($isAvailable)
array_push($slotsAvailable, $time);
}
print_r($slotsAvailable);
In my case, this would give me available slots like:
[0] => 08:00
[1] => 08:05
[2] => 08:10
[3] => 08:15
[4] => 08:20
[5] => 08:25
[6] => 09:35
[7] => 09:40
[8] => 09:45
[9] => 11:35
[....]

Related

Counting working hours (different for weekdays and Saturdays)

I tried #ebelendez's code for Calculating working hours between two dates, however I'm confused on how to set the value of Saturdays by 3 hours (08:00-11:00). For example, the working hours per day during weekdays is 8 hours (excluding 1 hour break), let's say I want to get the total working hours from Thursday to Saturday, the expected result would be 19 hours.
Here is what I've done. Can someone help me with this?
$from = '2022-04-21 07:00:00';
$to = '2022-04-23 16:00:00';
echo abs(get_working_hours($from, $to));
function get_working_hours($from,$to){
//config
$ini_time = [7,0]; //hr, min
$end_time = [16,0]; //hr, min
//date objects
$ini = date_create($from);
$ini_wk = date_time_set(date_create($from),$ini_time[0],$ini_time[1]);
$end = date_create($to);
$end_wk = date_time_set(date_create($to),$end_time[0],$end_time[1]);
//days
$workdays_arr = get_workdays($ini,$end);
$workdays_count = count($workdays_arr);
$workday_seconds = (($end_time[0] * 60 + $end_time[1]) - ($ini_time[0] * 60 + $ini_time[1])) * 60 - 3600;
//get time difference
$ini_seconds = 0;
$end_seconds = 0;
if(in_array($ini->format('Y-m-d'),$workdays_arr)) $ini_seconds = $ini->format('U') - $ini_wk->format('U');
if(in_array($end->format('Y-m-d'),$workdays_arr)) $end_seconds = $end_wk->format('U') - $end->format('U');
$seconds_dif = $ini_seconds > 0 ? $ini_seconds : 0;
if($end_seconds > 0) $seconds_dif += $end_seconds;
//final calculations
$working_seconds = ($workdays_count * $workday_seconds) - $seconds_dif;
return $working_seconds / 3600; //return hrs
}
function get_workdays($ini,$end){
//config
$skipdays = [0]; //sunday:0
$skipdates = [];
//vars
$current = clone $ini;
$current_disp = $current->format('Y-m-d');
$end_disp = $end->format('Y-m-d');
$days_arr = [];
//days range
while($current_disp <= $end_disp){
if(!in_array($current->format('w'),$skipdays) && !in_array($current_disp,$skipdates)){
$days_arr[] = $current_disp;
}
$current->add(new DateInterval('P1D')); //adds one day
$current_disp = $current->format('Y-m-d');
}
return $days_arr;
}
Your code and linked answers seem unnecessarily complicated. All we really need is to:
Configure how many hours should be counted for for each day;
Create an iterable DatePeriod (with DateTime objects for each date in the period);
Iterate dates, look up how many hours should be counted for each day, sum it up.
class CountWorkingHours
{
// Define hours counted for each day:
public array $hours = [
'Mon' => 8,
'Tue' => 8,
'Wed' => 8,
'Thu' => 8,
'Fri' => 8,
'Sat' => 3,
'Sun' => 0
];
// Method for counting the hours:
public function get_hours_for_period(string $from, string $to): int
{
// Create DatePeriod with requested Start/End dates:
$period = new DatePeriod(
new DateTime($from),
new DateInterval('P1D'),
new DateTime($to)
);
$hours = [];
// Loop over DateTime objects in the DatePeriod:
foreach($period as $date) {
// Get name of day and add matching hours:
$day = $date->format('D');
$hours[] = $this->hours[$day];
}
// Return sum of hours:
return array_sum($hours);
}
}
Source # BitBucket
Usage (returns an integer with working hours in a given period):
$cwh = new CountWorkingHours();
$hours = $cwh->get_hours_for_period('2022-04-21 07:00:00', '2022-04-30 16:00:00');
// = 62
If you need to account for public holidays etc. exceptions to the standard weekly hour counts, you can add a check inside the period loop for "skip dates". For example, have a $skip_dates property with an array of non-work-days, then check for !in_array($date->format('Y-m-d'), $this->skip_dates) before incrementing the work hours for a given day.
P.S. This code assumes that you are calculating whole working days. If your start or end hours were defined in the middle of a working day, that wouldn't be accounted for. (If you wanted to factor that in, you'd have to configure daily work times and the code would have to account for that in evaluating start and end dates. Seemed an unnecessary exercise for current purposes.)

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

Return the average time difference between dates excluding "Non-Working hours"

I am making a ticketing system for my company. In my database I record the timestamp of when a ticket is first raised and a timestamp of when the ticket is marked as completed.
I have written a function which returns the average time (hrs) a ticket takes to complete:
public function calculateAvgResolveTime()
{
$timeQuery = $this->database->query('SELECT ticketCreated, ticketCompletedOn FROM employeeTickets');
$cumulativeTicketTime = $cumulativeTimes = 0;
while($time = $timeQuery->fetch_assoc()) {
$timeCreated = strtotime($time['ticketCreated']);
$timeCompleted = strtotime($time['ticketCompletedOn']);
if($timeCompleted > $timeCreated) {
$cumulativeTimes++;
$cumulativeTicketTime = $cumulativeTicketTime + ($timeCompleted - $timeCreated);
}
}
$time = ($cumulativeTicketTime / 60 / 60);
$time = sprintf('%02d:%02d', (int) $time, round(fmod($time, 1) * 60));
return $time;
}
Is there a way I could exclude certain hours? For example our office is open from 09:00-17:00 Monday to Friday.
At the moment if a ticket is raised at 16:30 on a Friday and is completed 09:15 on Monday the average time would be quite high when actually the ticket only took 45 minutes of working time.
Result of var_export():
array(
array ( 'ticketCreated' => '2020-02-03 15:59:30','ticketCompletedOn' => '2020-02-04 09:53:35'),
array ( 'ticketCreated' => '2020-02-04 14:00:00', 'ticketCompletedOn' => '2020-02-04 14:36:00')
)
You will have to loop over the dates between ticketCreated and ticketCompletedOn day by day.
There seems to be no mathy way(or at least not in readable format) to solve this as you have time constraints of excluding Saturdays and Sundays as well as the working period being from 09:00:00 to 17:00:00.
Snippet:
<?php
$data =array(
array ( 'ticketCreated' => '2020-02-03 15:59:30','ticketCompletedOn' => '2020-02-04 09:53:35'),
array ( 'ticketCreated' => '2020-02-04 14:00:00', 'ticketCompletedOn' => '2020-02-04 14:36:00')
);
$sum_time = 0;
foreach($data as $details){
$start_time = new DateTime($details['ticketCreated']);
$end_time = new DateTime($details['ticketCompletedOn']);
$end_of_day = new DateTime($start_time->format('Y-m-d') . ' 17:00:00'); // since a day ends at 17:00
do{
$diff = $end_time->diff($start_time);
$diff2 = $end_of_day->diff($start_time);
if($end_time->format('Y-m-d') === $start_time->format('Y-m-d')){ // meaning finished on the same day
$sum_time += ($diff->h * 60) + ($diff->i) + ($diff->s / 60);
}else if(!in_array($end_of_day->format('N'),[6,7])){ // skipping Saturdays and Sundays
$sum_time += ($diff2->h * 60) + ($diff2->i) + ($diff2->s / 60); // add the day's offset(480 minutes)
}
$end_of_day->add(new DateInterval('P1D'));
$start_time = new DateTime($end_of_day->format('Y-m-d') . ' 09:00:00'); // start time for next day which is 09:00
}while($start_time <= $end_time);
}
$avg = $sum_time / count($data);
echo "$avg minutes",PHP_EOL;
Demo: https://3v4l.org/gpFt4
Explanation:
We first create DateTime instances of the dates.
We now have a do while loop inside.
If the end time and start time fall on the same day, we just take differences in terms of hours, minutes and seconds.
If the end time and start time doesn't fall on the same day, then we subtract the times from start_time from end_of_day which will be 480 minutes for a proper start or remaining offset of that day till 17:00:00.
If we come across a day which is Saturday or Sunday, we just skip it.
In the end, we just print the average by dividing sum by total number of tickets.

Array of working hours from 9:00 to 18:00

I need to save reports of working hours to the database. The working hours are from 9:00 to 18:00
I have the start date: 2018/06/01 13:00:00
And the end date: 2018/06/04 17:00:00
The result should be an array of reports for each working day of working hours:
June 1: Checkin - 13:00; Checkout 18:00;
--two weekend days are skipped--
June 4: Checkin - 09:00; Checkout 18:00;
June 5: Checkin 09:00; Checkout 18:00;
June 6: Checkin 9:00; Checkout 17:00;
Any idea how to do that?
Edit: Don't get me wrong, I don't expect anyone to write code for me :) I just need a hint to understand how to make the loop know when to start and when to end on each day. Should that be a loop for each hour of the working days provided?
Well, first you need to loop through each day using some php function like date(), or Datetime() class. Then ( in the loop ) you need add the day to the array with the respective working hours, but always check if the day is a weekend or not ( you can achieve this using the function or class stated above ). After the loop, you insert your array into your database.
So, I finally figured it out. I will paste it here in case someone finds any inspiration from it.
function get_working_days($start_time = "", $end_time = "") {
//getting the difference between $start_time and $end_time in days
$diff = ceil(($end_time - $start_time) / 60 / 60 / 24);
// fixing the difference if the request is for 1 day only
if (($end_time - $start_time) > 32400 && ($end_time - $start_time) < 86400) {
$diff += 1;
}
// 9:00 of the current day
$day_start = strtotime("midnight", $start_time) + 32400;
// 18:00 of the current day
$day_end = strtotime("midnight", $end_time) + 64800;
// setting the current day
$curr_day = $day_start;
// initiating the array
$return = [];
// looping through each day using the $diff variable
for ($i=0; $i < $diff; $i++) {
// adding reports while skipping weekends
if (!is_weekend($curr_day)) {
if ($i == 0 && $diff <= 1) {
// first and only iteration
$return[] = array("checkin" => $start_time, "checkout" => $end_time);
}elseif ($i == 0 && $i < ($diff - 1)) {
// first but not only iteration
$return[] = array("checkin" => $start_time, "checkout" => $curr_day + 32400);
}elseif ($i < ($diff - 1)) {
// middle
$return[] = array("checkin" => $curr_day, "checkout" => $curr_day + 32400);
}else{
// end
$return[] = array("checkin" => $curr_day, "checkout" => $end_time);
}
}
// going to the next day
$curr_day += 86400;
}
return $return;
}

Calculating shift hours worked

I'm am trying to calculate the hours for someone based on the number of hours worked and the time period in which they worked.
For example:
The shift patterns are 07:00 to 15:00 is 'morning', 15:00 to 23:00 is 'afternoons', and 23:00 to 07:00 is 'nights'.
Therefore if I started at 08:00 and finished at 17:30, this would mean that I get paid 7 'morning' hours and 2.5 'afternoon' hours.
I adapted a piece of code from a project I developed some time ago.
The function 'intersection' calculates the amount of time that overlaps between the two ranges s1-e1 and s2-e2.
Note that all the times are in seconds, and we add 3600*24 seconds when the time is in the next day.
<?php
function intersection($s1, $e1, $s2, $e2)
{
if ($e1 < $s2)
return 0;
if ($s1 > $e2)
return 0;
if ($s1 < $s2)
$s1 = $s2;
if ($e1 > $e2)
$e1 = $e2;
return $e1 - $s1;
}
$start = strtotime("07:00");
$end = strtotime("17:30");
// $end = strtotime("05:30") + 3600*24; // the work ended at 05:30 morning of the next day
$morning_start = strtotime("07:00");
$morning_end = strtotime("15:00");
$afternoon_start = strtotime("15:00");
$afternoon_end = strtotime("23:00");
$night_start = strtotime("23:00");
$night_end = strtotime("07:00") + 3600*24; // 07:00 of next day, add 3600*24 seconds
echo "morning: " . intersection( $start, $end, $morning_start, $morning_end ) / 3600 . " hours\n";
echo "afternoon: " . intersection( $start, $end, $afternoon_start, $afternoon_end ) / 3600 . " hours\n";
echo "night: " . intersection( $start, $end, $night_start, $night_end ) / 3600 . " hours\n";
You could have 3 counters, one for each shift. Then you'd need a way to increment by hour and for each hour you increment. You check if it is within each shift, and if it is within a certain one, then you increment that counter.
In the end, the values of each counter should be the amount you worked in each shift.
This is what I just threw together for fun.
There is much room for improvement and it can be easily extended to provide even more calculations, your imagination is the limit.
The array of shifts can be given named keys for readability, but I chose to remove them since 'night1' and 'night2' make no sense to me :-)
<?php
$shift_data = array(
array(
'rate' => 12.5,
'start' => strtotime('00:00:00'),
'end' => strtotime('07:00:00'),
),
array(
'rate' => 7.55,
'start' => strtotime('07:00:00'),
'end' => strtotime('15:00:00'),
),
array(
'rate' => 10,
'start' => strtotime('15:00:00'),
'end' => strtotime('23:00:00'),
),
array(
'rate' => 12.5,
'start' => strtotime('23:00:00'),
'end' => strtotime('07:00:00') + 86400, // next morning
),
);
function calculateWage($start, $end, $rate) {
$result = array();
$result['time']['seconds'] = $end - $start;
$result['time']['minutes'] = $result['time']['seconds'] / 60;
$result['time']['hours'] = $result['time']['minutes'] / 60;
$result['wages'] = $result['time']['hours'] * $rate;
//print_r($result);
return $result;
}
$shift_start = strtotime('08:00');
$shift_end = strtotime('17:30');
$shift_wages = 0;
foreach ($shift_data as $shift) {
if ($shift['start'] <= $shift_end) {
$start = ($shift_start <= $shift['start']) ? $shift['start'] : $shift_start;
$end = ($shift_end <= $shift['end']) ? $shift_end : $shift['end'];
$shift_wage = calculateWage($start, $end, $shift['rate']);
$shift_wages = $shift_wages + $shift_wage['wages'];
}
}
echo "\nTotal wages for today: $" . number_format($shift_wages, 2, '.', ',') . "\n\n";
?>
Here is another way: (I am assuming the start and end hours in unix timestamp)
Get the starting and ending day of employee
Get the work hours by a simple formula: $ending_hour - $starting_hour
If work hours is more than 8 hours, the employee should get paid for extra.
To calculate extra hours of next shift: $ending_hour - $next_shifts_starting_hour
My shift times are simplier: 22:00 -> 07:00 = nightwork, 07:00 -> 22:00 = daywork
But I got wrong answer with Javi R version if shifts are like 22:30 -> 08:00 (Result: day = 0 hours, night = 8:30 hours) and 20:00 -> 10:00 (Result: day = 2 hours, night = 9 hours) but right answer with shift time like 23:00 -> 07:00 (Result: day = 0 hours, night = 7 hours).
Sorry about that answer. I don't have enough rep but I need to get it work.

Categories