Repeating calendar events in SQL PHP - 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.

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).

Retrieve remaining time between two dates considering business hours

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."

Finding the calendar date of an ongoing event when the start-date and weekday are known

I'm trying to create a schedule of unique, recurring events that cycle on a weekly basis (each event will have unique identifiers and repeat weekly).
In addition to other information gathered about each event, I will gather the weekday on which each event will take place and the dates (calendar dates) on which the event will begin and end.
An example:
Event: Go to gym (the event I plan to do once a week)
Day of Week: Sunday (day of week event will occur (every Sunday))
Start Date: 2014-09-01 (happens to be a Monday)
End Date: 2015-08-31 (...)
The purpose is to write a script that takes any multitude of events and their respective weekday/start/end date and recreate a calendar depicting the future calendar dates on which each event will occur.
So the first date I should see scheduled to go to the gym will be:
2014-09-07
because this is first Sunday following the start date.
To sum it up, I am gathering the weekday of each event as well as the start and end dates. How can I parse these pieces of data into something that spits out a list of the events' future dates of reoccurrence?
Keep in mind that there will be a vast number of events that will occur on different days of the week and have different start/end dates.
Thank you for all who read and respond.
Tried:
SQL: grouping, different select statements, and stuff
PHP: stuff, date/time stuff, and stuff stuff
For next event in schedule you could use strtotime function:
strtotime('2014-09-01 next Sunday');
and use returned UNIX time.
But for complete solution of your problem there can be many ways. IMHO, strtotime used in for cycle can give you a large overhead if calculated on demand. If you store schedule in DB, perhaps it will be easiest solution to generete dates and regenerate them on edit.
In other hand, dates are pain in head so algoritm to calculate all ocasions can be much bigger. You can use intervals calculated from difference of result date from first date if $firstDate = $startDate or if event happens every day once a week, then just add 7 days to it.
Post more details or your solution to find mistakes.

Best way to determine and store Pay Periods for a time clock in PHP and MySQL

I am building a Time Clock application with PHP and Laravel 4.
My boss requires that he is able to pull and build different reports based on the data I store in the database for a Time Card record.
Right now I store a DateTime for clock in and clock out as well as a Timestamp for both those times as well into the Database.
I need to be able to Query the database and build reports for different Pay Periods for a user.
So for example I will store in another Database Table, records that will be for a User ID and will have different Pay Periods. So a Start day may be the 1st of the month and end date the 15th and that is 1 pay period (roughly 2 weeks) I am not sure the best way to store these records really.
Another will be the 16th of the month to the end of the month. So the end date would be different depending on how many days are in a month
I am not sure about the best way to define these Pay periods for a user. I can't simply say 1-15 and then 16-30 since the 30 would be a different number for each month.
Would appreciate any insight into how this could be done?
So I can build reports for any Pay Periods since not every user gets paid every 2 weeks it needs to be flexible so that I can define it on a per user basis
This question is more about the Logic instead of actual code.
Welcome to the wonderful world of Time and Attendance. You are touching the tip of the iceberg. You may find that purchasing a pre-packaged product may be easier than writing your own.
That said, I can offer you the following general advice:
Be very careful of your data types and how they are used, both in PHP and in MySQL.
You need to make sure you understand local time vs UTC, time zones, and daylight saving time. In general, you don't want to store local time unless you also store its offset from UTC. Otherwise you will have ambiguity around daylight saving time changes. This is important even if you only have one time zone to deal with.
When it comes to Pay Periods, the common types are:
Weekly
Bi-Weekly
Semi-Monthly
Monthly
Every X days starting from Y
In some systems, each individual pay period can be adjusted +/- a number of days from it's normal date. When doing so, the bordering period must also be adjusted to compensate.
You should start with business logic that can calculate the start and end date for a pay period given any particular date and time. You can then expand that to easily get the prior or next pay period.
You can store each pay period into it's own table, but it's not necessarily required. That will depend on a lot of specifics about your system internals.
Because a pay period is defined by dates, you have the "Whose Day is it?" problem. It might be the day as defined by the company, or if employees are in different time zones, then it might be the "logical day". If you only have one time zone to deal with then, you are lucky in this regard.
When comparing against the pay period, use half-open intervals, [start, end). In other words:
periodStart <= punchTime < periodEnd
or likewise
periodStart <= punchTime && periodEnd > punchTime
The end of one period should be exactly the same as the start of the next. Don't try to define the end of the period at some silly value like 23:59:59.999...
As you can see, this is just the beginning. I hope this is useful to you. If you can narrow the focus of your question further, I'll be happy to help more. Otherwise, it's like asking for how to build an ERP system when you're not sure what structure to store inventory.
I think you are over thinking this. Let thte user define the start and end dates.
You will need the UserId, a timestamp (time in and time out) of the user and that should be about it.
I picture something like this:
UserId | DateIn | DateOut
On the page you could put put dropdowns (or if you want a nifty interface a datepicker that uses javascript) and allow the manager to pick a start and end date that he wants to choose.
So if he wants to see an employees time between Jan. 1 and Feb. 31 he can choose those as his start and end dates.
This will allow things to be very flexible, for example the manager can choose Feb 16 as start date and Feb 29 as end date. It makes sense to allow him to choose the data requirements so he can view whatever he wants.
EDIT:
An example from my comment below this post you could do something like:
$startDate = new DateTime();
$startDate->modify('first day of this month'); //or 16th for second part of bi-monthly
$startDate->format(#some date formatting as you need#);
$endDate = new DateTime();
$endDate->modify('last day of this month'); //or 15th for first part of bi-monthly
$endDate->format(#some date formatting as you need#);
If things are even less defined however you could always try doing special math. date('t') will give you the number of days in a month. I would refrain from using this unless your pay days are fixed such as paid every 6 days.
In general I would harness the power of the PHP DateTime class over using date() function. http://php.net/manual/en/class.datetime.php

Finding open contiguous blocks of time for every day of a month, fast

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.

Categories