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) :)
Related
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).
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."
It's hard to articulate the problem I'm having in the title, but here's the scenario: I'm working the backend of a 3-tiered exam taking website. I'm currently updating my function used for when students request an exam. The exam has start and end dates and times, as well as a time limit. What I want to do is specific to the case when the amount of time left in the exam period is less than the time limit for the exam, For example, the exam period ends at 5:00PM. The time is now 4:30PM. The exam time limit is an hour and a half. I want to replace the hour and a half time limit with 30 minutes. To do that, there are 3 dates I need, and I will explain how I get each:
Timestamp for my function page. When the controller calls my page, after I open the session, I set the default timezone to America/New York (my region). To get the timestamp, I use the code:
$date = date('Y-m-d H:i:s');
$currenttime = date('H:i:s', strtotime($date));
I use a function call to my database to acquire the time limit and the time the exam ends (I also use the dates for other comparisons to check exam availability). The exam end time is stored in resultant array $res[10] and the time limit is in $res[11].
Assuming the due date for the exam is equal to the date of the timestamp, and it is in the exam period (2 checks I perform before), I want to check if the difference between the current time and the end of the exam is less than the time limit. If the time until the exam ends is less than the time limit, I want to set that amount of time as the new time limit. My front end expects my result in the form HH:mm:ss (php's equivalent is H:i:s). So if there is 30 minutes until the exam ends and the time limit is an hour and a half, I want the new time limit to be 30 minutes (or 00:30:00). My code is currently not working as desired. I am meeting the criteria for the if statement, which is my desired result. When I am converting it to the form H:i:s is where the problem seems to be (I keep getting a result around 19:00:00). Every article I've read suggests to do it this way, but I may be missing something.
$compareTime = strtotime($res[10]) - strtotime($currenttime);
if(compareTime < strtotime($res[11]){
$timelimit = date('H:i:s', $compareTime);
}else{
$timelimit = $res[11];
}
If there is a better way of doing this, I'd be much obliged to know. I'm fairly new to php, and admittedly I realize this may be a strange problem, but that's the way the group decided to go.
Maybe it's because the databse and PHP server are using different timezones. Check it out by printing these variables to see their values.
I am working on a booking availability system for a group of several venues, and am having a hard time generating the availability of time blocks for days in a given month. This is happening server-side in PHP, but the concept itself is language agnostic -- I could be doing this in JS or anything else.
Given a venue_id, month, and year (6/2012 for example), I have a list of all events occurring in that range at that venue, represented as unix timestamps start and end. This data comes from the database. I need to establish what, if any, contiguous block of time of a minimum length (different per venue) exist on each day.
For example, on 6/1 I have an event between 2:00pm and 7:00pm. The minimum time is 5 hours, so there's a block open there from 9am - 2pm and another between 7pm and 12pm. This would continue for the 2nd, 3rd, etc... every day of June. Some (most) of the days have nothing happening at all, some have 1 - 3 events.
The solution I came up with works, but it also takes waaaay too long to generate the data. Basically, I loop every day of the month and create an array of timestamps for each 15 minutes of that day. Then, I loop the time spans of events from that day by 15 minutes, marking any "taken" timeslot as false. Remaining, I have an array that contains timestamp of free time vs. taken time:
//one day's array after processing through loops (not real timestamps)
array(
12345678=>12345678, // <--- avail
12345878=>12345878,
12346078=>12346078,
12346278=>false, // <--- not avail
12346478=>false,
12346678=>false,
12346878=>false,
12347078=>12347078, // <--- avail
12347278=>12347278
)
Now I would need to loop THIS array to find continuous time blocks, then check to see if they are long enough (each venue has a minimum), and if so then establish the descriptive text for their start and end (i.e. 9am - 2pm). WHEW! By the time all this looping is done, the user has grown bored and wandered off to Youtube to watch videos of puppies; it takes ages to so examine 30 or so days.
Is there a faster way to solve this issue? To summarize the problem, given time ranges t1 and t2 on day d, how can I determine the remaining time left in d that is longer than the minimum time block m.
This data is assembled on demand via AJAX as the user moves between calendar months. Results are cached per-page-load, so if the user goes to July a second time, the data that was generated the first time would be reused.
Any other details that would help, let me know.
Edits
Per request, the database structure (or the part that is relevant here)
*events*
id (bigint)
title (varchar)
*event_times*
id (bigint)
event_id (bigint)
venue_id (bigint)
start (bigint)
end (bigint)
*venues*
id (bigint)
name (varchar)
min_block (int)
min_start (varchar)
max_start (varchar)
Events always start on the 15 -- :00, :15, :30, :45
Data dump of some of the actual time stamps: http://pastebin.com/k1PRkj44
This should get you in the right direction (I hope). It iterates over the database records that fall within a period (e.g. one month).
From that set it will find the "gaps" between the bookings and fill an array (with date as key).
$days = array();
$stmt = $db->prepare('SELECT
DATE(FROM_UNIXTIME(start)) AS sdate,
GROUP_CONCAT(HOUR(FROM_UNIXTIME(start)),",", MINUTE(FROM_UNIXTIME(start)) ORDER BY start ASC SEPARATOR ",") AS from_hours,
GROUP_CONCAT(HOUR(FROM_UNIXTIME(end)), ",", MINUTE(FROM_UNIXTIME(end)) ORDER BY start ASC SEPARATOR ",") AS to_hours
FROM event_time
WHERE start >= ? AND end < ? AND start < end
GROUP BY sdate
ORDER BY sdate');
$stmt->execute(array($from, $to));
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
// from and to are formatted as: [hh,mm,hh,mm,hh,mm,...]
$from = explode(',', $row['from_hours']);
$to = explode(',', $row['to_hours']);
// skew the two arrays:
// - add 00:00 in the front of $to
// - add 23:59 at the back of $from
array_unshift($to, 0, 0);
array_push($from, 23, 59);
for ($i = 0, $n = count($from); $i != $n; $i += 2) {
// create time values
$start = new DateTime("{$to[$i]}:{$to[$i+1]}");
$end = new DateTime("{$from[$i]}:{$from[$i+1]}");
// calculate difference
$diff = $start->diff($end);
// difference must be positive and at least 5 hours apart (depending on venue)
if (!$diff->invert && $diff->h >= 5) {
$days[$row['sdate']][] = array($start->format('H:i'), $end->format('H:i'));
}
}
}
At the end, $days will contain:
[2012-06-30] => Array
(
[0] => Array
(
[0] => 00:00
[1] => 05:30
)
[1] => Array
(
[0] => 11:30
[1] => 23:59
)
)
There are a few variables that you should change to make your calculations:
minimum time (e.g. from how early in the morning)
maximum time (e.g. until how late at night)
minimum booking time (depending on venue)
Also, keys that are missing in the resulting array are available for the whole day, so you should prime the $days array before you start the loop with all the days within the period you're querying.
Let me know if that helped you :)
Create a list of available times. Each entry has a start time and an end time. Start with one entry that runs from the beginning to the end of time. Read used times from the database. If one falls at the start or end of an existing entry, shorten it appropriately. If it falls in the middle, you've got to shorten one and add a new one (to cover the same time but with a gap in the middle). This gets you away from having to look at 15 minute slots on hours-long events. And it'll still work if your slots become 5 minutes instead of 15.
Once you've read the DB, you have all the free periods in one chronological list. You might want to put them in a separate list sorted by size, as well.
A linked list might be the most logical choice, as you'll be accessing it mostly sequentially. It allows for quick adds and removes. An array of some sort ought to be slower, but arrays are very fast these days and would also allow binary searches. For really heavy usage, some sort of tree-based (for sorted sequential access) dictionary or map would give you the best of both worlds (fast adds and removes and random access). I think in this case I'd go with some sort of array.
This is a bit of work, but it could give you some real speed.
How do I accurately determine the number of seconds in a month using PHP? Is the best way to take the number of seconds in a year and divide by 12?
Multiply the number of days in the month by 60 * 60 * 24.
Due to daylights savings... take a good datetime library in your language and calculate the difference between the first day of the month 0:00:00 and the first day of the next month 0:00:00 and extract the number of seconds.
How accurate do you need to be?
60 seconds * 60 minutes * 24 hours * Z days in the month gives you an accurate number for a given month.
If you need an average month go for number of seconds in the year and divide by twelve.
In some domains, such as billing or legal domains a 'month' might actually be exactly 30 days.
If you are working across multiple years or doing tight integration between disperse systems, you'll need to consult resource to determine leap seconds. For historical data this could be a table, but otherwise you'd be better suited by synchronizing to a trusted time source.
http://en.wikipedia.org/wiki/Leap_second
60 (seconds) * 60 (minutes) * 24 (hours) * ## (days in the month)
Given that there are 86,400 seconds in a day, you can multiply this number by the result of the DateTime.DaysInMonth function (in C#). The following function does just that:
public double SecondsInMonth(int year, int month)
{
return DateTime.DaysInMonth(year, month) * 86400;
}
E.g., find the seconds in the current month:
double secondsInCurrentMonth = SecondsInMonth(DateTime.Now.Year, DateTime.Now.Month);
Number of days in the given month * hours/day * minutes/hour * seconds/minute
is the best way.
If you're doing this in pure math it would be 60 * 60 * 24 * <number of days in month>.
What's the use case?
No, use the date API available for a particular lannguage and determine the number of days in the current month. Then calculate the number of seconds. Also take into account leap years.
Depends on if you want an average month or a specific month....your way gets an average. For a specific month count days and multiply by 86400 (seconds per 24.0 hour day)
This isn't really a programming question. Months have different lengths, so dividing the number of seconds in a year by 12 will give you nothing useful. It's easy to determine the days in a month - a simple lookup table plus a calcualation of leap years will do it. Then just multiply by the number of seconds in a day.
If you are being really precise you might need to include calculations of leap seconds, but since they are unpredictably assigned based on astronimical calculations, and not predictable in advance, I would probably ignore them.
Number of days vary in each month.Proper algorithm for this is to get number of days in moth and multiply it with 86400 (number of seconds in a day).You might also need average count or leap years calculation ...
The trivial answer is to find the number of days in the month and then multiply by 86400. That will work perfectly if you are dealing with dates and times in UTC. However, if you are using a local time zone then this approach yields a slightly incorrect result if that time zone observes daylight saving time. The error is somewhat small over a one month period, but will magnify if you need to make similiar calculation over short periods like a day. I definitely recommend doing all processing and storage in UTC, but depending on the application you will have to convert your UTC times to the local time zone that the end user is expecting. And it might even be plausible that you have to calculate durations using the local time zone. Again, use UTC as much as possible so that you avoid most of the problems.
I came up with this solution in C#. It is compatible with UTC and local time zones alike. You just have to tell the GetNumberOfSecondsInMonth which time zone you want the calculation to be based on. In my example I chose November of 2010 because here in Missouri we observe DST and there was one extra hour this month. Daylight saving time rules change so I used an API that pulls the DST information from the operating system so that the calculation will be correct for years prior to 2007 (that is when the United States expanded DST for most regions).
I should point out that my solution does not handle leap seconds in UTC. For me that is never an issue. But it would be easy to account for that by using a lookup table if you really needed ultra high precision timing.
public class Program
{
static void Main(string[] args)
{
int seconds = GetNumberOfSecondsInMonth(2010, 11, DateTimeKind.Local);
}
public static int GetNumberOfSecondsInMonth(int year, int month, DateTimeKind kind)
{
DateTime start = new DateTime(year, month, 1);
DateTime end = start.AddMonths(1);
int seconds = (int)(end - start).TotalSeconds;
if (kind == DateTimeKind.Local)
{
DaylightTime dt = TimeZone.CurrentTimeZone.GetDaylightChanges(year);
seconds = (dt.Start > start) ? seconds - 3600 : seconds;
seconds = (dt.End < end) ? seconds + 3600 : seconds;
}
return seconds;
}
}
It's a problem with years ang months as there is not a fixed number of days in them. But after a lot of thought I have figured out how to do it. It was not a good idea to calculate months with either 30 or 31 days in them, because it looks bad, for example converting from 1 year to months would give an answer of 11 months and 25 days if I had 30 days in each month, or 12 months and 5 days if I have 31 days in each month.
Instead I loop through a series of days per month: 30,30,31,30,31,30,31,30,31,30,31,30 which makes a total of 365 days in a year. So if I want the number of days in 4 months I add 30+30+31+30. And if I start with 23 months it would go through the loop almost twice (23 times 30 or 31). It's done in a while/until loop. For every 4 years I add 1 day, making it 366 days (the first 30 is changed to 31 in the list). It's rather complex but it works and the result looks better.