Retrieve remaining time between two dates considering business hours - php

I am trying to write a php solution to calculate the planned end time considering the target in business hours.
It shouldn't consider some days (retrieved from setting saved in db) such as holidays.
Also business hours are retrieved from db (morning_from (8:30am), morning_to (1:00pm), evening_from (2:30pm), evening_to (6:30pm)).
I want to develop this script because I want that my page shows the remaining time for technical resolution of an opened ticket every day.
For example:
customer having contract with 10 working hours SLA opens a ticket
today (friday) 31/01/2020 16:00:00, considering that in the
noBusinessDays = array("saturday", "sunday") and businessHours set as mentioned before(8:30-13:00/14:30-18:30), the result will have to
be monday 3/02/2020 17:30:00.
Code example:
$noBusinessDays = array("saturday", "sunday");
$businessHours = array("morning_from" => "8:30", "morning_to" => "13:00", "evening_from" => "14:30", "evening_to" => "18:30");
$SLA = "10"; //hours
$ticketDate = new DateTime();
$ticketDate->setTimestamp(strtotime("31/01/2020 16:00:00"));
// I don't know how to use my arrays to say in this calculation how to use them
$maximumLimit = $ticketDate->add(new DateInterval("PT" . $SLA ."H"));
Thank you in advance.

You may use the following function
// intersection of 2 time intervals
// input - Unix timestamps (start,end)
// output - intersection in seconds
function time_union($b_1,$e_1,$b_2,$e_2)
{
return max(0,$e_1-$b_1 - max(0,$e_1-$e_2) - max(0,$b_2-$b_1));
}
You will start with an empty time interval [X, Y) where X is the timestamp of the ticket creation and Y initially is equal to X.
Then you start adding days to Y - one by one. Each time you expand the time interval to contain another day - you use the above function to check how much of the SLA hours are covered (i.e. overlapping) with the working hours in the day you have just added. If the day is marked as a holiday - you simple skip it and continue with the next date.
If you find out that SLA hours are partially covered with either the morning or evening business hours - you should simply subtract the extra hours.
In the end Y will be equal to the timestamp that you want to show in your application.

I think I'd break down the problem into pieces. After calculating the total number of days in the interval, first dispose of the trivial case that it's all happening in one week.
begin by calculating the number of "whole weeks." Each "whole week" is five business days. Subtract the corresponding interval of time and proceed. Now, look at the day-of-the-week of the start-date: each day adds a certain number of days. Then the day-of-week of the end date, likewise. You can then consider hour-of-the-day as needed.
Holidays are a simple table: if the day falls within the range, subtract one day.
Now ... having said all of that, the very first thing that I would do is to search GitHub and SourceForge! Because I am extremely sure that somebody out there has already done this. :-D
"Actum Ne Agas: Do Not Do A Thing Already Done."

Related

Extract Working/Non-Working Hours With Weekend/Weekday Breakdown For A Working Schedule

I do need a calculation script for my project which would be calculating 4 things in minutes for me.
Total assigned minutes in working hours for weekdays (Between 08.30 - 17.30)
Total assigned minutes in out of working hours for weekdays (Except 08.30 - 17.30)
Total assigned minutes in working hours for weekends (Between 08.30 - 17.30)
Total assigned minutes in out of working hours for weekends (Except 08.30 - 17.30)
Basically, I am creating and using a schedule Google Calendar actually but doesnt matter, I just have start-end datetime objects in my hand for the employees on a calendar and assigning datetime ranges to employees to make them responsible for answering the customer calls in a certain time range which also could last a week, a few hours, a few minutes or a few days. The thing here is those date ranges are pretty flexible.
I've tried looping over the unix timestamp, creating a DateTime object per loop and check those 4 things but that would have been too much memory&cpu usage as I locked my computer a few times. I would be able to loop over hours in day if the events could only last a day at maximum but they are very flexible so I need a strong algorithm here.
For example a schedule would look like below:
Start(DateTime Object) => 2022-01-27 00:00:00
End(DateTime Object) => 2022-01-29 13:30:00
The function should take those two objects as an argument and should create an output like in the picture I ve shared below. Should be similiar to this:
function createReport(DateTime $employeeWorkStart, DateTime $employeeWorkEnd) : array {
...
return [
'weekday_in-working-hours' => XXX,
'weekday_out-working-hours' => XXX,
'weekend_in-working-hours' => XXX,
'weekday_out-working-hours' => XXX,
]
}
So I need to create a monthly-basis report which shows how many minutes I've assigned for each employee in the schedule.
My working hours are between 08.30 - 17.30, saturday & sunday is considered as weekend.
Example Report Output
So, not able to provide an actual code-answer right now. But how I would approach it is the following:
Get the amount of days between the two dates, subtract weekends. Also subtract one day if the $employeeWorkEnd is the current day (today) and if you want to have extra precision. I found this gist that gives you the working days (weekends and holidays excluded): https://gist.github.com/quawn/8503445
Multiply this by 9 (working hours) and then by 60 to get the total minutes. This is the total time worked.
If you wanted the extra precision in step 1, now just take the difference in minutes between 09:30 and the current time (date_diff can provide this). Add this difference to the total you had so far.
Execute this procedure for every employee you have. The above procedure will not do any loops, I believe it should be possible to do it just by subtraction and multiplication (if you want to exclude holidays using the code in the gist, this will introduce a small loop assuming low amount of holidays).
Your example output shows hours but your description shows minutes. The above story will give you minutes but it's just as easy to get the hours (or milliseconds for that matter).

Split a Carbon period into full months and reminder in days for the start and end

What I have is:
$period = CarbonPeriod::create("03-09-2022", '1 month', "20-03-2023"); // 3rd of September and 20th of March
From this I can tell the number of months on which the period spans. But what I need is to have a way to calculate a salary based on that period knowing a monthly salary.
So what I'm actually trying to get is:
28 Days(for the first month) followed by 5 full months followed by 20 days (for the last month)
Is there any way I can get this from CarbonPeriod or CarbonInterval?
The following will give you a good approximation:
$salaryPerMonth = 1000;
$totalSalary = Carbon::create('03-09-2022')->floatDiffInRealMonths('20-03-2023') * $salaryPerMonth;
But unless you're working 7/7 days, the approach is not exact, you would need to take into account only business days (excluding, holidays, etc.)
If you want to go to this deeper precision level, take a look at https://github.com/kylekatarnls/business-day and https://github.com/kylekatarnls/business-time

Calculate partial time elapsed between two times

I have an interface written in PHP that documents employees time worked. I'd like to add a new algorithm that measures the amount of time worked between certain hours for the purpose of calculating shift differentials. I'm using MySQL's unix_time() for the timestamps.
Example: Employee works from 6PM until 2AM. There is a paid shift differential between 10PM and 6AM. Employee receives regular pay for 4 hours and their pay + differential for 4 hours.
$start_time = 1459015200; // 18:00 03/26/2016
$end_time = 1459044000; // 02:00 03/27/2016
$diff_start = mktime('22','00','00','3','26','2016');
$diff_end = mktime('06','00','00','3','27','2016');
I'd like to write a script that calculates the amount of time worked between 10PM and 6PM, given a large set of possibilities of times worked. This answer accomplishes appears to do what I'm looking for, except in MySQL: Calculating time difference before 6am and after 10pm in MySQL
This answer is similar to my problem: Calculate the number of hours in a given timeframe between two datetimes but relies on a loop. Given that times are entered in seconds based on unix_time, I would presume to use a loop would require a one second increment (which means a lot of passes through the loop for thousands of records per week).
Is there an efficient way (such as the MySQL example) to do this in PHP that doesn't require looping every incremental second/minute/hour as suggested in the second answer?
There is: Use Arrays with specially formed keys. First of all
mktime('22','00','00','3','26','2016') => 1459026000
mktime('06','00','00','3','27','2016') => 1459051200
Now build your differential array:
$shifts=array (
0 => 0,
1459026000 => $diff,
1459051200 => 0
);
This means, that from timestamp 0..1459025999 the differential is 0, from 1459026000 to 1459051199 the differential is $diff and from 1459051200 to infinity it is 0 again. Ofcourse your real shift table will be much larger.
You now run through this array exactly once with soemthing like foreach ($shifts as $start=>$differential) and compare $start to $start_time and $end_time, with these cases:
$start>$end_time: ignore, this is in the future
$start<$start_time: ignore, this is in the past
else: calculate number of seconds and use $differential
Loved this question. Looked like an interview question. I spent some time to implement this efficiently with no loops. First of all I implemented the following:
function date_intersection($a_from, $a_to, $b_from, $b_to);
returning the seconds in between two time ranges.
Then, with the constraint that a shift can't be longer than 24 hours, I needed to build two range blocks:
the range with differential which is before your current time shift
the range with differential which is after
After it, solution is straightforward:
$total = $end_time - $start_time;
$differ = date_intersection($b1s, $b1e, $start_time, $end_time) + date_intersection($b2s, $b2e, $start_time, $end_time);
$normal = $total-$differ;
Efficiently solve this question involve not using mktime() or date() to extract part of your date, as you really can solve the problem with module operator over unix-timestamp. But taking care of DST really made it complex.
If you only had not asked this question tonight I would have answer before (incorrectly) :)

Repeating calendar events in SQL PHP

I have a loop that loops through all the days in a given month that a person can create events from a start date/time and specify an ending date/time. I am incorporating where events can be repeated every xx days, xx months or xx years.
I have no idea how to match up the current day in the loop to see if the repeating has started.
Say I had the event "Pickup Silver" that was 3 days long.
I want this to happen every 7 days starting on the 2nd. The calendar should show this event every 7 days from the starting date/time. Starting on the days 2,9,16,30 and so on.
#Loop starts and math calulates the current unix start of each day
##The SQL QUERY to lookup if there are any events starting or ending on this day
#$SQL ="
#SELECT
# Name as OutputTitle,
# ID
# FROM
#safe_calendar
#WHERE
#AutherID = '[AccountID]'
#AND
#AutherTable = '[TableName]'
#AND
#(UnixFrom - 86400) < [UnixThisDay]
#AND
#UnixEnd >= [UnixThisDay]
#";
I would suggest taking a look at iCalendar RRULEs. If you are doing a pretty basic recurrence pattern, then don't bother with the bulk of this because it's quite large but very comprehensive. This will at least give you a good idea, that is a standard, on how to structure your data and what you need to store. Specifically, in your case, it sounds like you'd need to store the start and end date of the recurrence as well as the start date and length of the event. From that, you can deduce if your current day intersects with a recurrence day.
Something to keep in mind is calendar calculations are actually quite difficult. You need to account for timezone differences, daylight savings (not only on what days but how to deal with the extra or missing hour), leap years, etc. Even what appears to be simple can become quite complex if you factor in all the outlier situations.

PHP Number of days for billing cycle with only day of month known

I am trying to calculate number of days in a billing cycle and the number of days left in a billing cycle. I have a value (i.e., 5) which represents the billing date every month (the 5th of every month in this case). I need a way to:
a. Calculate the number of days between the 5th of this month and the 4th of the following month
b. Calculate the number of days between today and the 4th of next month
Where I keep getting stuck is, let's say, today is the 3rd of the month. Well the 5th of this month hasn't happened yet so the calculation would need to look back to the 5th of last month (which will ultimately calculate that there are two days left in the billing cycle). Can I get some help how to property calculate this? I'm sorry I don't have any code snippets to post - every combination of mktime / date / strtotime I try fails miserably so I don't have anything helpful to put up.
Thank you!
Your question is hard to understands and your explanation doesn't make it easier, but it seems that you have a certain date X (which is known completely, with day, month and year), and the next n-th day of the month.
Assume you have $d, $m, $y (day, month, year of current date) and $n (billing on n-th day). You can use "date()" to extract the parts if you don't have them split yet.
If $d > $n, then you will bill them next month. If $d < $n, it will be this month. If $d == $n billing is now or next month, your choice.
Adding a month (remember to correctly handle December) will give you the billing date, and you can calculate the difference.
Alternatively, just add a day to the current date in a loop until you hit the billing date...

Categories