Calculate intersection between 2 date ranges - php

i would like to calculate the number of free days (absences) in a specific week. I use an API that returns the following data:
{
"count": 1,
"data": [
{
"id": "11ec62ff1df2654d8bd6f1d234a6c496",
"type": "HOLIDAY",
"from": "2021-12-22",
"to": "2021-12-23",
"resourceId": "11ec46d6547a00728be3e1ed8ff29535",
"createdAt": "2021-12-22T08:14:00"
}
],
"success": true
}
These are vacation and sickness data. I have a weekly report where I need to calculate the number of absence days during that week. I need to find an easy way to calculate the number of absence days during the week.
I have tried by using https://www.php.net/manual/de/datetime.format.php and convert it into "z" format, but it doesn't look elegant and from performance perspective i think it's not best.
//The week range
$weekStart = new DateTime("2021-12-20");
$weekEnd = new DateTime("2021-12-24");
//The Planned absence
$absenceStart = new DateTime("2021-12-22");
$absenceEnd = new DateTime("2021-12-23");
//Specify the DateInterval for calculating the period
$interval = DateInterval::createFromDateString('1 day');
//Need to add the interval to the end date in order to consider the end as well
$weekEnd->add($interval);
$absenceEnd->add($interval);
//Getting the 2 periods week and absence
$weekPeriod = new DatePeriod($weekStart, $interval, $weekEnd);
$absencePeriod = new DatePeriod($absenceStart, $interval, $absenceEnd);
$weekArray = array();
$absenceArray = array();
//put the day number format('z') into an array of the week
foreach ($weekPeriod as $i => $dt) {
$weekArray[$i] = $dt->format('z');
}
//put the day number format('z') into an array of the absence
foreach ($absencePeriod as $i => $dt) {
$absenceArray[$i] = $dt->format('z');
}
//get the intersection between both arrays
$ergebnis = array_intersect($weekArray, $absenceArray);
//calculate the number of entries
echo "The employee has <b>".count($ergebnis)."</b> free days in the week from 2021-12-20 until 2021-12-24";
This is returning the right information.
The employee has 2 free days in the week from 2021-12-20 until 2021-12-24
Can anyone please suggest if there is a better way or if i can tweak at least to make it more elegant and performat?
Thanks alot

Instead of looping over the week array, absence array, then week array again (array_intersect), you could eliminate looping through the absence array and the second week array loop if that information is not needed. So you only have 1 loop instead of 3 should be more performant.
//The week range
$weekStart = new DateTime("2021-12-20 00:00:00");
$weekEnd = new DateTime("2021-12-24 23:59:59");
//The Planned absence
$absenceStart = new DateTime("2021-12-22 00:00:00");
$absenceEnd = new DateTime("2021-12-23 23:59:59");
//Specify the DateInterval for calculating the period
$interval = DateInterval::createFromDateString('1 day');
//Getting the available period
$weekPeriod = new DatePeriod($weekStart, $interval, $weekEnd);
$availableDayCount = 0;
//put the day number format('z') into an array of the week
foreach ($weekPeriod as $dt) {
// Filter out days the employee is absent.
if($dt < $absenceStart || $dt > $absenceEnd) {
$availableDayCount += 1;
}
}
//calculate the number of entries
echo "The employee has <b>" . $availableDayCount . "</b> free days in the week from " . $weekStart->format('Y-m-d') . " until " . $weekEnd->format('Y-m-d');
Additionally, it didn't appear to be providing the correct information (maybe I am wrong?). Your question states it came to 2 days but I count 3:
2021-12-20
2021-12-21
2021-12-24
(The 22nd and 23rd being absent)
If that is incorrect, you can change $weekEnd to 2021-12-23 (I added start/end of day times so the entire day is counted).

Related

Calculating A Cycle number using dates for a 28 day repeating cycle

I have a job that runs every 28 days. and I want to assign it a cycle number based on a starting reference date.
e.g
1st cycle is 01/27/22. and that cycle number would be 2201.
subsequently I want to calculate the cycle number based on the current date. but for each year there could be either 13 or 14 cycles.
I've managed to figure out the number of cycles since the reference date to figure out the latest cycle date (see below)
const REF_ZERO_DATE = '01/27/2022';
const REF_ZERO_CYCLE_YEAR = "22";
const REF_ZERO_CYCLE_NUM = "01";
$today = new \DateTime("2023/12/29");
echo ("Today = ".$today->format("Y/m/d")."\n");
$ref_zero = new \DateTime(self::REF_ZERO_DATE);
echo ("ref_zero = ".$ref_zero->format("Y/m/d")."\n");
$number_of_days_since_ref_zero = $today->diff($ref_zero)->format("%a");
echo ("Number of days since ref zero = ".$number_of_days_since_ref_zero."\n");
$number_of_cycles_since_ref_zero = floor($number_of_days_since_ref_zero/28);
echo ("Number of cycles since ref zero = ".$number_of_cycles_since_ref_zero."\n");
$interval = 'P' . $number_of_cycles_since_ref_zero*28 . 'D';
echo ("Interval = ".$interval);
$date_of_lastest_cycle = date_add($ref_zero,new \DateInterval($interval));
echo ("last Cycle Date = ".$date_of_lastest_cycle->format("Y/m/d")."\n");
But my math for the cycle adjustment is missing coping with 12 or 13 cycle in a specific year.
It is not explicitly stated whether the cycle of the previous year continues into the next or not.
The scenario in which the cycles can overlap between years is more complicated, so this is assumed.
The interval count code was extracted to the following function:
function calculateIntervalCount($startDate, $endDate, $interval) {
$start = new \DateTime($startDate);
$end = new \DateTime($endDate);
$interval = new \DateInterval($interval);
$periodDays = intval($end->diff($start)->format('%a'));
$intervalDays = intval($interval->format('%d'));
return floor($periodDays / $intervalDays);
}
There are two cases when calculating the interval count of a particular year:
year of start and end are the same year
year of end is after year of start
In the first case the interval count is the same as the interval count of the whole period.
In the second case the interval count of a particular year can be calculated from the difference between the interval counts of the whole period and the period before the end year.
The following function returns the cycle number:
function calculateCycleNumber($startDate, $endDate, $interval) {
$totalCycles = calculateIntervalCount($startDate,$endDate,$interval);
$startYear = intval((new \DateTime($startDate))->format('Y'));
$endYear = intval((new \DateTime($endDate))->format('Y'));
if($startYear < $endYear) {
$endOfLastYearDate = (new \DateTime($endDate))->modify('last day of December last year')->format('Y-m-d');
$cyclesSinceEndOfLastYear = calculateIntervalCount($endOfLastYearDate, $endDate, $interval);
$yearCycle = $totalCycles - $cyclesSinceEndOfLastYear + 1;
} else {
$yearCycle = $totalCycles;
}
$yearCode = substr($endYear,-2);
$yearCycleCode = sprintf('%02d', $yearCycle);
return $yearCode . $yearCycleCode;
}
A cycle number of 2314 was obtained with the inputs provided.
echo calculateCycleNumber('01/27/2022','2023/12/29','P28D');
Note that 14 is possible in case of overlapping cycles.
You can use timestamp, where you add 28 days each time so you get the next date and so on.
Get the next timestamp
$next_date = strtotime('+28 day', $timestamp);
Convert to readable date
echo date('m/d/Y', $next_date);

count occurrence of date (e.g 14th) between two dates

How can I count occurrences of 14th of a month between two dates
For example between 07.05.2018 and 04.07.2018
I have 2 occurrences of the 14th
Try this. Note that I've changed your date format, but you can just do a createFromFormat if you're really keen on your own format.
$startDate = new DateTime('2018-05-07');
$endDate = new DateTime('2018-07-04');
$dateInterval = new DateInterval('P1D');
$datePeriod = new DatePeriod($startDate, $dateInterval, $endDate);
$fourteenths = [];
foreach ($datePeriod as $dt) {
if ($dt->format('d') == '14') { // Note this is loosely checked!
$fourteenths[] = $dt->format('Y-m-d');
}
}
echo count($fourteenths) . PHP_EOL;
var_dump($fourteenths);
See it in action here: https://3v4l.org/vPZZ0
EDIT
This is probably not an optimal solution as you loop through every day in the date period and check whether it's the fourteenth. Probably easier is to modify the start date up to the next 14th and then check with an interval of P1M.
You don't need to loop at all.
Here's a solution that does not loop at all and uses the less memory and performance hungry date opposed to DateTime.
$start = "2018-05-07";
$end = "2018-07-04";
$times = 0;
// Check if first and last month in the range has a 14th.
if(date("d", strtotime($start)) <= 14) $times++;
if(date("d", strtotime($end)) >= 14) $times++;
// Create an array with the months between start and end
$months = range(strtotime($start . "+1 month"), strtotime($end . "-1 month"), 86400*30);
// Add the count of the months
$times += count($months);
echo $times; // 2
https://3v4l.org/RevLg

How to loop over weeks and find the exact date of some days?

I'm working on a website where the user can create some events every X days (where X is the name of a day in the week). Then, he needs to enter the number of events he wants to create in the future.
For example, the user selects every Monday and Tuesday and decides to create 150 events.
Here is the code I have made until now :
// Init the date counter
$cpt_date_found = 0;
// Number of date to find
$rec_occ = 150;
// Init an ending date far in the future
$endDate = strtotime('+10 years', time());
// Loop over the weeks
for($i = strtotime('Monday', strtotime(date("d.m.Y"))); $i <= $endDate; $i = strtotime('+1 week', $i)) {
// -- Monday date found, create the event in the database
$cpt_date_found++;
// Break the loop if we have enough dates found
if($cpt_date_found == $rec_occ) {
break;
}
}
This code finds the date of every Monday in the future and breaks the loop once we have reached the number of occurrences the user specified.
I have entered an ending date far in the future to make sure I can break the loop before the end of the occurrences count specified by the user.
First I'm not sure about the "quality" of my code... I know that breaking the loop is not the best idea and am wondering if another solution would better fit my needs.
Then, instead of repeating the loop more times if the user specified several days (let's say, Monday, Tuesday and Friday), is there a way to loop one time for every provided days?
Thanks!
The following code will loop over a period of 5 years. For each week in those 5 years it will generate a DatePeriod containing each day of that week. It will compare each of those days to your preset array with days you are looking for. You can then generate your event after which the code will countdown for a certain amount of times. If the counter hits zero, you are done.
$searchDates = array('Mon', 'Tue', 'Fri');
$amountOfTimes = 27;
$startDate = new DateTime();
$endDate = new DateTime('next monday');
$endDate->modify('+5 years');
$interval = new DateInterval('P1W');
$dateRange = new DatePeriod($startDate, $interval ,$endDate);
// Loop through the weeks
foreach ($dateRange as $weekStart) {
$weekEnd = clone $weekStart;
$weekEnd->modify('+6 days');
$subInterval = new DateInterval('P1D');
// Generate a DatePeriod for the current week
$subRange = new DatePeriod($weekStart, $subInterval ,$weekEnd);
foreach ($subRange as $weekday) {
if (in_array($weekday, array('Mon', 'Fri', 'Sun'))) {
// Create event
// Countdown
$amountOfTimes--;
}
if ($amountOfTimes == 0) {
break;
}
}
}

PHP MySQL Count every day between DatePeriod

I have a DatePeriod
$start = new DateTime('2016-03-01');
$end = new DateTime('2016-03-01 + 1 month');
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
and a SQL Table with workdates:
Now i need a SQL Query to Count every day in the table between the Dateperiod by tagnr (daynumber)
The following code doesn't work for me.
SELECT
COUNT(tagnr) AS totalsumme
FROM
arbeitszeiten
WHERE
tagnr IN ($period)
If you are trying to count the number of dates in the table that are contained in the php date period specified, then the only insurmountable problem I see is that your posted data doesn't contain actual dates. In particular tagnr appears to be the number day of the week (Mon-Sun : 1-7).
Let's presume for a minute your table data does contain actual dates as such:
id tag tagnr ddate
1 Montag 1 2016-03-07
2 Dienstag 2 2016-03-08
3 Mittwoche 3 2016-03-09
4 Donnerstag 4 2016-03-10
5 Freitag 5 2016-03-11
Then the task isn't too difficult to build a sql query using the dates in the range:
<?php
$start = new DateTime('2016-03-01');
$end = new DateTime('2016-03-01 + 1 month');
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
$date_range = "";
// iterate through date range and build up sql string
foreach($period as $date){
// append each date, comma delimited
$date_range .= "'" . $date->format("Y-m-d") . "',";
}
// trim trailing comma
$date_range = rtrim($date_range,",");
$sql = "
SELECT
COUNT(tagnr) AS totalsumme
FROM
arbeitszeiten
WHERE
ddate IN ($date_range)
";
// execute $sql with mysqli, PDO, etc.
Curiously, since your table only shows exactly 5 typical working days, then I wonder if your intention is to actually count how many working days (from the table) exist in the php date interval you've specified. In which case, you can use the php date format after translating from German. Moreover, unless your work days of the week are changing, then it doesn't make sense to hit a database for that info, so you should just hard code it in an array. If they are changing, then you will also need the actual dates for context per my other answer. But, to find the number of working days in the specified php date range, the following code will do this:
<?php
$working_days = array(
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday"
);
$start = new DateTime('2016-03-01');
$end = new DateTime('2016-03-01 + 1 month');
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
$count_of_working_days = 0;
// iterate through date range and count each instance
foreach($period as $date){
// get the day of the week from the date
$day_of_week = $date->format('l');
// check if it's one of the working days
if (in_array($day_of_week,$working_days))
$count_of_working_days++;
}
echo $count_of_working_days;
Output:
23

Find NOW() within two odd date selections

I have used numorous date functions in the past, but I need help on a specific function...
Is there a function where I can assign two unknown dates and the system knows where I am within these two dates? Let me explain:
Our monthly salaries run from the 23rd of each month to the 22nd of the next month. Because earnings work on an hourly basis, my boss wants to know anywhere within the month where the accumulative salaries are. For instance the salary period started on the 23rd of September 2012 and we are now on the 29th, I want my query to be able to know where we are in the current salary period.
As you know, months move on, thus my script must automatically know in which period and where whithin that period we are now.
I can do the queries surrounding this, I just need to know the date function (s) to use to assign this array...
Any help will be appreciated - Thanx
You can use PHP's DateTime classes to do this quite easily:-
$periodStart = new DateTime('23rd September');
$now = new DateTime();
$interval = $now->diff($periodStart);
echo "We are {$interval->d} days into the payment period";
Output:
We are 6 days into the payment period.
I prefer to extend the DateTime class for this kind of thing, so everything is in the same place:-
class MyDateTime extends DateTime
{
public function elapsedDays(DateTime $since = null)
{
if ($since === null) {
$since = new DateTime();
}
$interval = $since->diff($this);
return (int) $interval->d;
}
}
$periodStart = new MyDateTime('23rd September');
echo "We are {$periodStart->elapsedDays()} days into the payment period";
Gives the same output.
You can then create periods and intervals and iterate over it to aggregate the sum like:
$datePeriodStart = new DateTime('23rd September');
$datePeriodEnd = clone $datePeriodStart;
$datePeriodEnd->add(new DateInterval('P1M'));
$dateToday = new DateTime();
$interval1 = $dateToday->diff($datePeriodStart);
$interval2 = $dateToday->diff($datePeriodEnd);
echo "We are {$interval1->d} day(s) into the payment period, {$interval2->d} day(s) left.\n";
$period = new DatePeriod($datePeriodStart, new DateInterval('P1D'), $dateToday);
$days = new IteratorIterator($period);
$totalSalary = 0;
$totalDays = 0;
foreach($days as $day)
{
$salary = get_salary_for_day($day);
$totalSalary += $salary;
$totalDays++;
printf("#%d: %s %s\n", $totalDays, $day->format('Y-m-d'), number_format($salary));
}
printf("Total Salary for %d day(s): %s\n", $totalDays, number_format($totalSalary));
Example output:
We are 6 day(s) into the payment period, 23 day(s) left.
#1: 2012-09-23 12,500
#2: 2012-09-24 12,500
#3: 2012-09-25 12,500
#4: 2012-09-26 12,500
#5: 2012-09-27 12,500
#6: 2012-09-28 12,500
#7: 2012-09-29 12,500
Total Salary for 7 day(s): 87,500
You could simply use a TIMESTAMP value (seconds since epoch, also called a "unix timestamp") and just test to see if the current date in unix timestamp is in between the first and last unix timestamp dates.
Essentially, that way you are merely converting the date into a big integer (number of seconds since 1969/70), and arithmetic and testing functions become a LOT easier to handle.
Get FROM and TO date:
$to = new DateTime();
$from = new DateTime($to->format('Y-m-23'));
if ($to->format('j') < 23) {
$from->modify('-1 month');
}
Var_dump:
var_dump($from->format('Y-m-d')); # 2012-09-23
var_dump($to->format('Y-m-d')); # 2012-09-29
SQL
$sql = "
SELECT ...
FROM ...
WHERE some_time BETWEEN '" . $from->format('Y-m-d') . "' AND '" . $to->format('Y-m-d') ."'
";

Categories