Querying for a range of days and hours - php

I want to be able to calculate if something is within a range and return all results.
We have a table that has scheduled weekly activities. Each row of the table has an unique id, a day number, and an hour number.
id (auto int) | dayNumber (int from 0 to 6) | hourNumber (int from 0 to 23)
Based off of this table, we can determine when an event is supposed to occur on the schedule. For example, an entry of dayNumber=2 and hourNumber=3 would mean that an event is scheduled for Tuesday at 3 AM.
The part that I am finding difficult is that I need to run a query to get a range of scheduled events. So I will pass in a minimum dayNumber and hourNumber and a maximum dayNumber and hourNumber. My expected results will be every matching entry in that range.
The easier case is when the minimum is less than the maximum. For example (in PHP),
$minimumDayNumber = 1; $minimumHourNumber = 3;
$maximumDayNumber = 5; $maximumHourNumber = 6;
Where it gets more complicated is when I want to search the range from Saturday to Tuesday. For that, the variables would look something like this:
$minimumDayNumber = 6; $minimumHourNumber = 3;
$maximumDayNumber = 2; $maximumHourNumber = 6;
Using these inputs, I would need to query the database and find all entries within that range. There must be a good algorithm to either construct the query or a good query to do this fairly directly.
This seems easy on paper and I have done it with date ranges (actual date fields), but I just cannot get this to work correctly. Thank you in advance for your help and let me know if you need additional clarification.

Try this
where (($maximumDayNumber > $minimumDayNumber and (dayNumber+hourNumber/24) >= ($minimumDayNumber+$minimumHourNumber/24) and (dayNumber+hourNumber/24) <= ($maximumDayNumber+$maximumHourNumber/24))
or ($maximumDayNumber < $minimumDayNumber and ((dayNumber+hourNumber/24) <= ($minimumDayNumber+$minimumHourNumber/24) OR (dayNumber+hourNumber/24) >= ($maximumDayNumber+$maximumHourNumber/24))))

Will this work?
SELECT * FROM activities
WHERE IF( $minDayNum = $maxDayNum,
( dayNumber = $minDayNum AND hourNumber >= $minHourNum AND hourNumber <= $maxHourNum ),
(
IF( $minDayNum > $maxDayNum,
( dayNumber > $minDayNum AND dayNumber < $maxDayNum ),
( dayNumber > $minDayNum OR dayNumber < $maxDayNum )
)
OR ( dayNumber = $minDayNum AND hourNumber >= $minHourNum )
OR ( dayNumber = $maxDayNum AND hourNumber <= $maxHourNum )
)
)
Pardon the rubber duck:
The first IF runs a check to see if the day is the same
If so, a simple where clause will narrow down the hours of the day
If not, we go to another if statement to see if we are traversing weeks
The conditional pulls in all events between the range (Not equal to), conscious of week endings
Last two OR conditions pull in the specific start/end day/time events
I didn't mock anything up, let me know how it works out.

Related

Calculating frequency interval for tasks

I have a cron job that gets results from the DB to check it the interval set by user falls on today's date. I am currently thinking of doing it as below :
Get the time column for the row. Ex:2017-05-25 00:00:00
Get the frequency set. Ex:Every 2 weeks.
Get the current date in above format. Ex:2017-05-31 00:00:00
Get the difference in days. Ex:6 days.
Convert the frequency set to days. Ex:2 weeks = 14 days;
Divide (difference in time(days)) by (frequency in days). Ex:6/14
This way I will only get the result to be true when 2 weeks have passed since the time set. I.e., 14/14, 28/14, 42/14,...
If the frequency is in months, I can start dividing by 30. But somehow this feels like a hacky way of doing it. So my question is if there is better way of doing this calculation to check the difference.
This is what I have done as explained by above example.
` $frequency = ; // Get the relevant fields from db
$today = date(Y-m-d H:i:s);
foreach ($frequency as $key => $value) {
$frequency_in_days;
$frequency_type = $value->type;
$frequency_repeat = $value->repeat;
if($frequency_type == 1){
$frequency_in_days = $frequency_repeat;
} elseif($frequency_type == 2) {
$frequency_in_days = $frequency_repeat * 7;
} elseif($frequency_type == 3) {
$frequency_in_days = $frequency_repeat * 30;
} elseif($frequency_type == 4) {
$frequency_in_days = $frequency_repeat * 365;
}
// Get number of days spent between start_date and today in days.
$interval = date_diff($value->start_date, $today)->format('%a');
$result = $interval % $frequency_in_days;
if ($result == 0) {
// Frequency falls today! Do the job.
}
}`
Note: The cron job runs this script. The script again needs to check if the today falls under the frequency set.
Also for argument's sake, is this the best logic to calculate the difference?
Thank you.
This will work
Table "schedule"
`last_run` timestamp,
`frequency_seconds` int
example query for tasks that should go every two weeks:
SELECT *
FROM schedule
WHERE TIMESTAMPDIFF(last_run, NOW()) >= frequency_seconds
after fetching rows update last_run to NOW()

PHP check if time range is in a time range

I want to insert a time bounded to someone into the database.
For example: Peter 13:00 - 19:00
Peter told us he can't work from 18:00 - 22:00. (This is located in the database).
so the time 13:00 - 19:00 can't be inserted into the database because the end time (19:00) is within the timerange 18:00 - 22:00.
How to check this?
I've tried several things but I dont know how to do it right.
Got this right now
$van_besch = '18:00';
$tot_besch = '22:00';
$van = '13:00';
$tot = '19:00';
if (($van_besch >= $van) && ($tot_besch <= $tot)) {
$error ='<font color="red">Time '.$van_besch.' - '.$tot_besch.' Not available</font>';
}
This won't work.
Thanks for reading!
There are actually two cases you have to check:
$van is equal or between $van_besch and $tot_besch
$tot is equal or between $van_besch and $tot_besch
if $van and $tot are both between $van_besch and $tot_besch both cases are true.
Further more you need to handle shifts that straddle a day break e.g. 17:00 - 02:00. Another problem is you need to handle is 20:00 - 0:00, since 0:00 is smaller than 20:00.
Therefore we project the real time to our own time format.
That means 17:00 - 02:00 will become 17:00 - 26:00. Note that we need to do this for both $van_besch - $tot_besch and $van - $tot$.
In code that would look be something like:
if ($tot < $van){
$tot = 24:00 - $van + $tot;
}
if ($tot_besch < $van_besch){
$tot = 24:00 - $van + $tot;
}
if (($van >= $van_besch) && ($van <= $tot_besch) ) || (($tot >= $tot_besch) && ($tot <= $tot_besch)){
... Peter is not available ...
}
It is better you convert them and compare using strtotime. Something like this would work for you:
$van_besch = '18:00';
$tot_besch = '22:00';
$van = '13:00';
$tot = '19:00';
if (strtotime($van) < strtotime($van_besch) || strtotime($tot ) > strtotime($tot_besch)) {
//code goes here
}
You can handle this in an SQL query for sure. And if you're just storing time, it would be best to do it as an int 0000 - 2359.
So our table would look something like this.
CREATE TABLE off_time (
id int(12) AUTO_INCREMENT,
employee int(12) NOT NULL,
start_time smallInt(4) unsigned NOT NULL,
end_time smallInt(4) unsigned NOT NULL,
PRIMARY KEY (id)
);
We're storing a start and end time to create a block of time that they don't work. Obviously in your code it's important that you makes sure these blocks make sense and start_time <= end_time.
Now we can query.
// Here we use just < and > but you can make them <= and >= if you don't want time slots to possibly overlap start/end times.
SELECT ot.id, IF( 1300 < ot.end_time AND 1900 > ot.start_time, 1, 0) AS blocked
FROM off_time as ot
WHERE ot.employee = 1;
You will get a table back where each row will have the id of the blocked time slot, and a blocked column with 1 meaning it's blocked and 0 meaning it's not. So if any of the entries are 1, then you know your time is blocked out. Doing it this way is also handy because you can also relate a reasons table, and be able to respond with a reason. LEFT JOIN time_off_reasons as r on r.id = ot.id
If you want to be specific with dates. Just store timestamp for start and end times. The comparison will be the same, just with timestamps instead.
EDIT
Marc B. made a good point in the comments about time blocks that stretch over the midnight mark i.e. 2200 - 0200. With my method you would want to make sure you split those apart as separate entries.
So INSERT INTO off_time (start_time, end_time) VALUES(2200, 2400)(0000,0200)
This will still work as expected. But you'll just have two entries for 1 time off block, which might be annoying, but still easier than writing extra code to compensate for the logic.

Calculating different bands of overtime in php

This is my first time posting here so I'm sorry if I get something wrong. I'm trying to calculate how many hours overtime a worker has worked based on when they signed in. The problem is that we have different bands of overtime:
If the worker works between 5 and 7 then it's 25% extra per hour
If they worked between 7pm and 10pm then its 50% extran for each hour
If the worker works between 10 and 12 then it's 75% extra
If the worker works between 12am and 7am is 100% more
I need to count how many hours they worked at each of the overtime bands
$number_of_25_percent_hours=0;
$number_of_50_percent_hours=0;
$number_of_75_percent_hours=0;
$number_of_100_percent_hours=0;
$clockInTime=$arr['4'];
$clockOutTime=$arr['5'];
$startingPieces=explode(':',$clockInTime);
$startingHour=$startingPieces[0];
$finishingPieces=explode(':',$clockInTime);
$finishingHour=$finishingPieces[0];
//Regular hours are between 7am and and 5pm
//If the worker works between 5 and 7 then it's 25% extra per hour
if(($startingHour<=5)&&($finishingHour>=6)){$number_of_25_percent_hours++;}
if(($startingHour<=6)&&($finishingHour>=7)){$number_of_25_percent_hours++;}
The problem with using the lines above is that it does not work if for example they worked an hour from 6:30 to 7:30.
I'm interested in finding other ways to do this.
you need to store the data more exactly. From your script it looks as if you were only saving the starting hour - which propably is a full number (1,2,3,4 whatsoever)
You script however needs a exact time representation. There are surely many ways to do this but for the sake of a better Script (and as you will propably be able to use some of these more exact values later on) I'd recommend you to store it as a UNIX Timestamp, then get the hour of the Timestamp :
$startingHour = date('H' $timeStampStored)
and check if it's in any of your "bonus" segments. If the user started working at 6:30, the value will hold 6.
This code is completely off the top of my head, untested etc. It's intended as a suggestion of one method you might use to solve the problem, not as a robust example of working code. It uses integers instead of dates, relies on array data being entered in order etc, and probably wouldn't even run.
The basic idea is to set up the scales for each level of overtime multiplier, as well as the hours for non-overtime pay in an array, then loop through that array checking how many hours of each level of overtime have been worked between the inputted times, meanwhile keeping track of a total billable hours value.
$PayMultipliers = array();
$PayMultipliers[0] = array(17,19,1.25);
$PayMultipliers[1] = array(19,22,1.5);
$PayMultipliers[2] = array(22,24,1.75);
$PayMultipliers[3] = array(0,7,1.5);
$PayMultipliers[4] = array(7, 17, 1);
$Start = 3;
$End = 11;
$TotalHours = 0;
for($i = 0; $i <= count($PayMultipliers); $i++)
{
if($Start > $PayMultipliers[$i][0] && $Start < $PayMultipliers[$i][1])
{
$TotalHours += ($PayMultipliers[$i][1] - $Start) * $PayMultipliers[$i][2];
$Start = $PayMultipliers[$i][1];
}
}
echo $TotalHours;
If you want to calculate from 6:30 to 7:30 you'll have to caclulate in minutes, not hours. You can convert the hours and minutes to timestamps, check each time period, and then convert the seconds back to hours.
<?php
$number_of_overtime_hours = array();
$clockInTime = "18:30:00";
$clockOutTime = "19:30:00";
$startingPieces = explode(':',$clockInTime);
$finishingPieces = explode(':',$clockOutTime);
//Timestamps
$startTimestamp = mktime($startingPieces[0],$startingPieces[1],$startingPieces[2]);
$finishTimestamp = mktime($finishingPieces[0],$finishingPieces[1],$finishingPieces[2]);
//finish after 0h
if ($finishTimestamp < $startTimestamp){
$finishTimestamp += 3600 * 24;
}
//set starting and ending points
$overtimePeriods = array(
25 => array (17,19),
50 => array (19,22),
75 => array (22,24),
100 => array (24,31)
);
$overtimeWork = array();
foreach ($overtimePeriods as $key => $val){
//create Timestamps for overtime periods
$beginTimestamp = mktime($val[0],0,0);
$endTimestamp = mktime($val[1],0,0);
//calculate hours inside the given period
$overtimeWork[$key] = (min($finishTimestamp,$endTimestamp) - max($startTimestamp,$beginTimestamp)) / 3600;
//negative values mean zero work in this period
if ($overtimeWork[$key] < 0) $overtimeWork[$key] = 0;
}
var_dump($overtimeWork);

Check if appointment time fits users working time with SQL - Wrong proposal during night

I defined hours where an employee is working aka working hours, e.g.
$start = "09:00:00";
$end = "18:00:00";
A user can now select an appointment, and the query should validate it, e.g.
$appointment_start = "09:00:00";
$appointment_end = "10:00:00";
My SQL query should now check if the employee is working at that specific time. If that is the case, propose this appointment to the user. This is how I do it:
...
AND w.start <= '$appointment_start'
AND w.end >= '$appointment_end';
There seems to be a problem during night when the day changes, e.g. when start is 23:00:00 and end is 00:30:00. This should not be a valid appointment time, but my query proposes it:
start substr: 23:00:00 || end substr: 00:00:00
start substr: 23:30:00 || end substr: 00:30:00
How do I have to change the WHERE statement of my SQL query to fix this issue?
You could use a datetime rather than just a time to avoid this issue altogether.
Or you could do something like:
AND (( w.start < w.end
AND w.start <= '$appointment_start'
AND w.end >= '$appointment_end') OR
( w.start > w.end
AND w.start >= '$appointment_start'
AND w.end <= '$appointment_end' ) )
Basically, you invert your comparison operators when start happens after end.
If you are unable to use a datetime you could massage your values.
$appointment_end = ($appointment_end < $appointment_start) ? $appointment_end + [24hours] : $appointment_end;
$end = ($end < $start) ? $end + [24hours] : $end;
Basically if the end time is less than the start time, assume it's the next day and add 24 hours, then do the check as normal. (not sure of the syntax in php to add the [24hours])

Displaying Scheduled Events

I had this problem some years ago and back then I implemented a "different logic" in order to deliver the project but the doubt remains in my mind and hopefully with your help I'll be able to understand it now.
Suppose I have some scheduled events on my database that may or may not spawn over several days:
id event start end
-----------------------------------------------
1 fishing trip 2009-12-15 2009-12-15
2 fishCON 2009-12-18 2009-12-20
3 fishXMAS 2009-12-24 2009-12-25
Now I wish to display the events in a calendar, lets take the month of December:
for ($day = 1; $day <= 31; $day++)
{
if (dayHasEvents('2009-12-' . $day) === true)
{
// display the day number w/ a link
}
else
{
// display the day number
}
}
What query should the dayHasEvents() function do to check if there are (or not) events for the day? I'm guessing SELECT .. WHERE .. BETWEEN makes the most sense here but I've no idea how to implement it. Am I in the right direction?
Thanks in advance!
#James:
Lets say we're on December 19th:
SELECT *
FROM events
WHERE start >= '2009-12-19 00:00:00'
AND end <= '2009-12-19 23:59:59'
Should return the event #2, but returns nothing. =\
You should scratch that approach and grab all events for the given month up front so you only need to perform a single query as opposed to N queries where N is the number of days in the month.
You could then store the returned results in a multidimensional array like so:
// assume event results are in an array of objects in $result
$events = array();
foreach ($result as $r) {
// add event month and day as they key index
$key = (int) date('j', strtotime($r->start));
// store entire returned result in array referenced by key
$events[$key][] = $r;
}
Now you'll have a multidimensional array of events for the given month with the key being the day. You can easily check if any events exist on a given day by doing:
$day = 21;
if (!empty($events[$day])) {
// events found, iterate over all events
foreach ($events[$day] as $event) {
// output event result as an example
var_dump($event);
}
}
You're definitely on the right track. Here is how I would go about doing it:
SELECT *
FROM events
WHERE start <= '2009-12-01 00:00:00'
AND end >= '2009-12-01 23:59:59'
And you obviously just replace those date values with the day you're checking on.
James has the right idea on the SQL statement. You definitely don't want to run multiple MySQL SELECTs from within a for loop. If daysHasEvents runs a SELECT that's 31 separate SQL queries. Ouch! What a performance killer.
Instead, load the days of the month that have events into an array (using one SQL query) and then iterate through the days. Something like this:
$sql= "SELECT start, end FROM events WHERE start >= '2009-12-01' AND end <= '2009-12-31'";
$r= mysql_query($sql);
$dates= array();
while ($row = mysql_fetch_assoc($r)) {
// process the entry into a lookup
$start= date('Y-m-d', strtotime($row['start']));
if (!isset($dates[$start])) $dates[$start]= array();
$dates[$start][]= $row;
$end= date('Y-m-d', strtotime($row['end']));
if ($end != $start) {
if (!isset($dates[$end])) $dates[$end]= array();
$dates[$end][]= $row;
}
}
// Then step through the days of the month and check for entries for each day:
for ($day = 1; $day <= 31; $day++)
{
$d= sprintf('2009-12-%02d', $day);
if (isset($dates[$d])) {
// display the day number w/ a link
} else {
// display the day number
}
}
For your purposes a better SQL statement would be one that grabs the start date and the number of events on each day. This statement will only work properly if the start column is date column with no time component:
$sql= "SELECT start, end, COUNT(*) events_count FROM events WHERE start >= '2009-12-01' AND end <= '2009-12-31' GROUP BY start, end";

Categories