Algorithm for occurrence - php

Can someone help me figuring out how to get if a current week is inside an occurrence .
I have the following vars : start_date , end_date , current_date week_occurrence .
and i have a function that return the # of occurrence
// will return the number of weeks between start - end
function get_weeks_count($start , $end) {
return floor(abs(strtotime($start) - strtotime($end)) / 604800);
}
now i have to know if a current date is a valid date .
I have an entry with occurrence = every N weeks . How to know that N is valid .
Less abstract : If we are in December and the occurrence is every 3 weeks , start_date is 1st and end_date is 30 December)
It will return :
TRUE for 1st week
FALSE for the second week
FALSE for the third week
TRUE for the last week

Here's how I would approach the problem - this applies for an occurrence every $n weeks.
$n = $week_occurrence;
$occurrence = false;
// To begin, get the number of weeks between the start and current dates.
$weeks = get_weeks_count($start_date , $current_date); // Using the function you already have
// Now check if $weeks == 0
if ($weeks == 0) {
$occurrence = true;
// If not, check if $weeks is divisible by $n without any remainder
} else if ($weeks % $n == 0) {
$occurrence = true;
}
If $occurrence is still false then the current week does not fall within the the correct occurrence, if it's true then the week does fall within the scope.
Effectively all we're doing here is checking that the current number of weeks since the start date is either equal to zero (we're still in the first week) or is divisible by the ocurrence without a remainder.
I hope this helps.
P.S. I've only answered the specific question that you asked. However, if you would like to know more about how this premiss could be used for scheduling etc., then feel free to ask and I'll expand on my answer accordingly

A combination of DateTime and DateInterval should help you achieve this easily.
function get_occurences(DateTime $start, DateTime $end, DateInterval $period) {
$weeks = array();
$cursor = clone $start;
$rate = DateInterval::createFromDateString('1 week');
do {
/* We can check to see if it's within the occurrence period */
if ($cursor == $start) {
$isOccurrence = true;
$start->add($period); // Move the start period up
} else {
$isOccurrence = false;
}
$weeks[$cursor->format('Y-m-d')] = $isOccurrence;
} while($cursor->add($rate) < $end);
return $weeks;
}
$period = DateInterval::createFromDateString('3 week');
$start = new DateTime('2012-12-01');
$end = new DateTime('2012-12-30');
/* From this array you can get both the number of occurrences as well as their respective dates*/
var_dump(get_occurences($start, $end, $period));
/** Output:
array(5) {
["2012-12-01"]=>
bool(true)
["2012-12-08"]=>
bool(false)
["2012-12-15"]=>
bool(false)
["2012-12-22"]=>
bool(true)
["2012-12-29"]=>
bool(false)
}
*/

Related

how may days are between two dates in specific year

I'm solving following task>
I have two dates - $start and $end and target year as $year.
dates are php DateTime objects, year is string.
add:dates comes acutaly from MySql field from this format 2017-02-01 15:00:00 ...
add2: if end date is null, I use todays date ...
I need to figure out how many days are between these two dates for specific year.
Also I need to round it for whole days, even if one minute in day should be counted as whole day ...
I can solve it by many many following ifs.
Expected results for values I used in example are
2016 is 0 days
2017 is 31 days
2018 is 32 days
2019 is 0 days
But are there any elegant php functions which can help me with this ?
What I did it seems to be wrong way and giving bad results - seems it counts full days only ...
Please see my code here >
<?php
$diff = True;
$start = DateTime::createFromFormat('Y-m-d H:i:s','2017-12-01 23:05:00');
$end = DateTime::createFromFormat('Y-m-d H:i:s','2017-12-03 00:05:00');
$year = '2017';
// start date
if ($start->format('Y')<$year)
{
$newstart = new DateTime('first day of January '. $year);
}
if ($start->format('Y')==$year)
{
$newstart = $start;
}
if ($start->format('Y')>$year)
{
$result = 0;
$diff = False;
}
// end date
if ($end->format('Y')>$year)
{
$newend = new DateTime('last day of December '. $year);
}
if ($end->format('Y')==$year)
{
$newend = $end;
}
if ($end->format('Y')<$year)
{
$result = 0;
$diff = False;
}
// count if diff is applicable
if ($diff)
{
$result = $newend->diff($newstart)->format("%a");
}
echo $result;
?>
But are there any elegant php functions which can help me with this ?
Read about DateTime::diff(). It returns a DateInterval object that contains the number of days (in $days) and by inspecting the values of $h, $i and $s you can tell if you have to increment it to round the result. You can also use min() and max() to crop the time interval to the desired year.
function getDays(DateTimeInterface $start, DateTimeInterface $end, $year)
{
// Extend the start date and end date to include the entire day
$s = clone $start; // Don't modify $start and $end, use duplicates
$s->setTime(0, 0, 0);
$e = clone $end;
$e->setTime(0, 0, 0)->add(new DateInterval('P1D')); // start of the next day
// Crop the input interval to the desired year
$s = min($s, new DateTime("$year-01-01 00:00:00"));
$year ++;
$e = max(new DateTime("$year-01-01 00:00:00"), $end); // start of the next year
if ($e <= $s) {
// The input interval does not span across the desired year
return 0;
}
// Compute the difference and return the number of days
$diff = $e->diff($s);
return $diff->days;
}
$d1 = strtotime('2017-05-15');
$d2 = strtotime('2017-05-31');
$div = 24 * 3600;
echo abs(($d2 - $d1) / $div); // 16 days
Just make sure and ONLY have the date part and you shouldn't have to deal with rounding.

Dividing timestamps into weeks

I'm looking to divide a span between two timestamps into weeks, in PHP.
Essentially, what I want to do is:
function divide_into_weeks($start_time, $end_time) {
// Iterate back from the end time,
// creating an array of timestamps broken into calendar weeks.
$done = FALSE;
$divider = $end_time;
for ($i = 0; !$done; $i++) {
$timeslots[$i]->end = $divider;
// Set the start time to the start of the current week.
$timeslots[$i]->start = strtotime("monday this week", $divider);
if ($timeslots[$i]->start <= $start_time) {$done = TRUE;}
// If we loop again, end the previous week one second before the start of this week.
$divider = $timeslots[$i]->start - 1;
}
}
However, the function hits an infinite loop.
Why? Because...
strtotime("monday this week", $monday_of_next_week -1) == $monday_of_last week;
... is true, strangely. This is unlike other strtotime operations I've tested. In other cases, you knock a second off, repeat, and it iterate back by one of whatever unit you've asked for. But not for Monday (or Sunday) of this week.
For example:
$today = strtotime("midnight");
$yesterday = strtotime("midnight", $today -1);
...produces sensible results.
But my attempts to use the same technique with "monday of this week" or "sunday of this week" have proven fruitless, so far.
So, can someone show how to iterate timestamps back by weeks?
I think this would help you:
function divide_into_weeks($start_time, $end_time, $tz) {
$tz = new DateTimezone($tz);
$start = new DateTime("#$start_time");
$end = new DateTime("#$end_time");
$start ->setTimezone($tz)->modify($start->format('o-\WW-1 00:00:00'));
$end ->setTimezone($tz)->modify($end ->format('o-\WW-7'));
$weeks = [];
do {
$weeks[] = [
'start' => clone $start,
'end' => new DateTime($start->format('o-\WW-7 23:59:59'), $tz),
];
$start->modify('+7 day');
} while ($start < $end);
return $weeks;
}
demo
This ended up being the workaround that did the trick:
(With some of the irrelevant bits trimmed.)
function divide_timespan_into_units($start_second, $end_second, $time_unit) {
$timeslots = array();
// The time_formatter guides strtotime in what parts of a timestamp to trim off to leave timestamps split on calendar divisions.
switch ($time_unit) {
case "hour":
$time_formatter = 'Y-m-d H:00:00';
break;
case "day":
$time_formatter = 'Y-m-d 00:00:00';
break;
case "week":
$time_formatter = "monday this week";
break;
}
$done = FALSE;
$divider = $end_second;
for ($i = 0; !$done; $i++) {
$timeslots[$i] = (object) array('end' => $divider);
if ($time_unit == "week") {
// Dividing timestamps up into calendar weeks requires special handling.
//
// Note on the strange constants "86399" & "86400" in the two lines following:
// This is a workaround for a fluke in how strtotime treats weeks:
//
// strtotime can't decide if Sunday or Monday is the start of the week.
// You have to force it by rolling back a full day plus one second from the start of Monday, to get into the previous week.
//
// Simply rolling back a second from Sunday by 1 second doesn't get you into the previous week,
// because if you ask for "this Sunday" on any day other than Sunday, it will return the coming Sunday, not the past Sunday.
// However, if you back up 1 second from the stroke of midnight Monday, and ask what week you're in,
// you'll be told that you're still in the same week.
//
// This nudge of an extra 86400 seconds (a full extra day) back and forth forces the week to click over.
//
// This will likely be settled in a later version of PHP, but as of 5.3.5 (when this is being coded) it's an issue.
$timeslots[$i]->start = strtotime("monday this week", $divider-86400);
$divider = $timeslots[$i]->start+86399;
} else {
$timeslots[$i]->start = strtotime(date($time_formatter, $divider));
}
if ($timeslots[$i]->start <= $start_second) {$done = TRUE;}
$divider = $timeslots[$i]->start - 1;
}
}

calculate sundays between two dates

I want to calculate all Sunday's between given two dates. I tried following code. It works fine if days are less but if i enter more days. It keeps processing and Maximum execution time exceeds i changed the time but it even keeps processing even execution time is 200sec.
code is
<?php
$one="2013-01-01";
$two="2013-02-30";
$no=0;
for($i=$one;$i<=$two;$i++)
{
$day=date("N",strtotime($i));
if($day==7)
{
$no++;
}
}
echo $no;
?>
please help.
John Conde's answer is correct, but here is a more efficient and mathy solution:
$start = new DateTime('2013-01-06');
$end = new DateTime('2013-01-20');
$days = $start->diff($end, true)->days;
$sundays = intval($days / 7) + ($start->format('N') + $days % 7 >= 7);
echo $sundays;
Let me break it down for you.
$start = new DateTime('2013-01-06');
$end = new DateTime('2013-01-20');
First, create some DateTime objects, which are powerful built-in PHP objects meant for exactly this kind of problem.
$days = $start->diff($end, true)->days;
Next, use DateTime::diff to find the difference from $start to $end (passing true here as the second parameter ensures that this value is always positive), and get the number of days between them.
$sundays = intval($days / 7) + ($start->format('N') + $days % 7 >= 7);
Here comes the big one - but it's not so complicated, really. First, we know there is one Sunday for every week, so we have at least $days / 7 Sundays to begin with, rounded down to the nearest int with intval.
On top of that, there could be a Sunday in a span of time less than a week; for example, Friday to Monday of the next week contains 4 days; one of them is a Sunday. So, depending on when we start and end, there could be another. This is easy to account for:
$start->format('N') (see DateTime::format) gives us the ISO-8601 day of the week for the start date, which is a number from 1 to 7 (1 is Monday, 7 is Sunday).
$days % 7 gives us the number of leftover days that don't divide evenly into weeks.
If our starting day and the number of leftover days add up to 7 or more, then we reached a Sunday. Knowing that, we just have to add that expression, which will give us 1 if it's true or 0 if it's false, since we're adding it to an int value.
And there you have it! The advantage of this method is that it doesn't require iterating over every day between the given times and checking to see if it's a Sunday, which will save you a lot computation, and also it will make you look really clever. Hope that helps!
<?php
$no = 0;
$start = new DateTime('2013-01-01');
$end = new DateTime('2013-04-30');
$interval = DateInterval::createFromDateString('1 day');
$period = new DatePeriod($start, $interval, $end);
foreach ($period as $dt)
{
if ($dt->format('N') == 7)
{
$no++;
}
}
echo $no;
See it in action
Here is a solution if you want the Sundays in a specific date range.
function dateRange($begin, $end, $interval = null)
{
$begin = new DateTime($begin);
$end = new DateTime($end);
$end = $end->modify('+1 day');
$interval = new DateInterval($interval ? $interval : 'P1D');
return iterator_to_array(new DatePeriod($begin, $interval, $end));
}
/* define date range */
$dates = dateRange('2018-03-01', '2018-03-31');
/* define weekdays */
$weekends = array_filter($dates, function ($date) {
$day = $date->format("N");
return $day === '6' || $day === '7';
});
/* weekdays output */
foreach ($weekends as $date) {
echo $date->format("D Y-m-d") . "</br>";
}
/* define sundays */
$sundays = array_filter($dates, function ($date) {
return $date->format("N") === '7';
});
/* sundays output */
foreach ($sundays as $date) {
echo $date->format("D Y-m-d") . "</br>";
}
/* define mondays */
$mondays = array_filter($dates, function ($date) {
return $date->format("N") === '1';
});
/* mondays output */
foreach ($mondays as $date) {
echo $date->format("D Y-m-d") . "</br>";
}
Just change the number for any days you want in your output:
Monday = 1
Tuesday = 2
Wednesday = 3
Thursday = 4
Friday = 5
Saturday = 6
Sunday = 7

Figure out what day each array starts and ends on?

I have an array of words and each word is valid from 5PM that day until 5PM the next day, then the next word is valid. Except on weekends in which a word for Friday lasts until Monday at 5PM.
Now what I am trying to do is determine if the users inputted word is valid for that period of time. I have a working example which works fine, but my problem is the weekends mess everything up. And I can't seem to figure out how to make them work.
I got a function to figure out how many weekends occur between two timestamps
// Figure out how many weekends occured between two dates
function weekends($date1, $date2) {
$d1 = new DateTime('#' . $date1);
$d2 = new DateTime('#' . $date2);
// Compare the two dates
$diff = $d1->diff($d2);
// Get number of days between them
$days = $diff->format('%a');
// Find out day of the week we start on
$start = $d1->format('w');
// Verify we are not on a weekend
if($start == 0 || $start == 6)
return false;
// Number of days until weekend
$until_weekend = 7 - $start; // (6 is Saturday but we are counting today)
// Find out how many days are left between the first weekend and the end
$left = $days - $until_weekend;
// How many weekends
$weekends = floor($left / 7) + 1;
return $weekends;
}
And then I got a function to determine if the word is valid for that date range
// Keyword Validation
function keyword_validate($keywords = array()) {
if(empty($keywords)) return false;
// Break into values
$keyword = $keywords['keyword'];
$contest = $keywords['contest'];
$keywords = $contest['keywords'];
// Get some dates
$now = new DateTime('now');
$start = new DateTime('#' . $contest['start_time']);
$s1 = new DateTime('#' . $contest['start_time']); // value for timestamps
$s2 = new DateTime('#' . $contest['end_time']); // value for timestamps
$end = new DateTime('#' . $contest['end_time']);
// Verify keyword exists
if(in_array($keyword, $keywords) === FALSE)
return false;
// Get index
$index = array_search($keyword, $keywords);
// See if we somehow got more then one keyword
if(count($index) != 1)
return false;
// get number of weekends
$weekends = weekends($start->getTimestamp(), $end->getTimestamp());
// Based on index get the two container timestamps
$s = $s1->add(new DateInterval('P' . $index + $weekends . 'D'));
// Verify start doesn't equal Friday or a Weekend
$e = $s2->add(new DateInterval('P' . $index + $weekends + 1 . 'D'));
if($s === FALSE || $e === FALSE)
return false; // Something really bad happened
// Based on dates find out if the keyword works.
print $s->getTimestamp();
print $e->getTimestamp();
// Get the current time
}
As you can seem the keyword function doesn't work atm. What I am doing atm is matching the index of the keyword to day, but if it is say Tuesday (2 weekends after) how can I make it so the index is increased by 4. Sorry if this doesn't make any sense, I'm a little lost.
Try redefining the problem to make it simpler. Instead of trying to do math with funny exceptions to figure out which item is associated with which day, perhaps try creating a new array with a value of the word for each day. It could look like this:
array(
'cake',
'pie',
'pudding',
'happy', //friday
'happy', //saturday
'happy', //sunday
'nothappy',
...
);
Building this array should be simpler than the things you're trying to do now. Once you have this array, checking should be trivial.
Could you not just have an array that can contain seven spaces? When the "friday" index is changed, set the "Saturday"/"Sunday" to mirror it.

Finding all weekdays in a month

How do I go about getting all the work days (mon-fri) in a given time period (let's say, today till the end of the next month) ?
If you're using PHP 5.2+ you can use the library I wrote in order to handle date recursion in PHP called When.
With the library, the code would be something like:
$r = new When();
$r->recur(<start date here>, 'weekly')
->until(<end date here>)
->wkst('SU')
->byday(array('MO', 'TU', 'WE', 'TH', 'FR'));
while($result = $r->next())
{
echo $result->format('c') . '<br />';
}
This sample does exactly what you need, in an quick and efficient way.
It doesn't do nested loops and uses the totally awesome DateTime object.
$oDateTime = new DateTime();
$oDayIncrease = new DateInterval("P1D");
$aWeekDays = array();
$sStart = $oDateTime->format("m-Y");
while($oDateTime->format("m-Y") == $sStart) {
$iDayInWeek = $oDateTime->format("w");
if ($iDayInWeek > 0 && $iDayInWeek < 6) {
$aWeekDays[] = clone $oDateTime;
}
$oDateTime->add($oDayIncrease);
}
Try it here: http://codepad.org/wuAyAqnF
To use it, simply pass a timestamp to get_weekdays. You'll get back an array of all the weekdays, as timestamps, for the rest of the current month. Optionally, you can pass a $to argument - you will get all weekdays between $from and $to.
function get_weekdays ($from, $to=false) {
if ($to == false)
$to = last_day_of_month($from);
$days = array();
for ($x = $from; $x < $to; $x+=86400 ) {
if (date('w', $x) > 0 && date('w', $x) < 6)
$days[] = $x;
}
return $days;
}
function last_day_of_month($ts=false) {
$m = date('m', $ts);
$y = date('y', $ts);
return mktime(23, 59, 59, ($m+1), 0, $y);
}
I suppose you could loop through the dates and check the day for each one, and increment a counter.
Can't think of anything else off the top of my head.
Pseudocode coming your way:
Calculate the number of days between now and the last day of the month
Get the current day of the week (i.e. Wednesday)
Based on the current day of the week, and the number of days left in the month, it's simple calculation to figure out how many weekend days are left in the month - it's going to be the number of days remaining in the month, minus the number of Sundays/Saturdays left in the month.
I would write a function, something like:
daysLeftInMonth(daysLeftInMonth, startingDayOfWeek, dayOfWeekToCalculate)
where:
daysLeftInMonth is last day of the month (30), minus the current date (15)
startingDayOfWeek is the day of the week you want to start on (for today it would be Wednesday)
dayOfWeekToCalculate is the day of the week you want to count, e.g. Saturday or Sunday. June 2011 currently has 2 Sunday, and 2 Saturdays left 'til the end of the month
So, your algorithm becomes something like:
getWeekdaysLeft(todaysDate)
...getWeekdaysLeft is something like:
sundaysLeft = daysLeftInMonth(lastDayOfMonth - todaysDate, "Wednesday", "Sunday");
saturdaysLeft = daysLeftInMonth(lastDayOfMonth - todaysDate, "Wednesday", "Saturday");
return ((lastDayOfMonth - todaysDate) - (sundaysLeft + saturdaysLeft));
This code does at least one part you ask for. Instead of "end of next month" it simply works with a given number of days.
$dfrom = time();
$fourweeks = 7 * 4;
for ($i = 0; $i < $fourweeks; $i ++) {
$stamp = $dfrom + ($i * 24 * 60 * 60);
$weekday = date("D", $stamp);
if (in_array($weekday, array("Mon", "Tue", "Wed", "Thu", "Fri"))) {
print date(DATE_RSS, $stamp) . "\n";
}
}
// Find today's day of the month (i.e. 15)
$today = intval(date('d'));
// Define the array that will hold the work days.
$work_days = array()
// Find this month's last day. (i.e. 30)
$last = intval(date('d', strtotime('last day of this month')));
// Loop through all of the days between today and the last day of the month (i.e. 15 through 30)
for ( $i = $today; $i <= $last; $i++ )
{
// Create a timestamp.
$timestamp = mktime(null, null, null, null, $i);
// If the day of the week is greater than Sunday (0) but less than Saturday (6), add the timestamp to an array.
if ( intval(date('w', $timestamp)) > 0 && intval(date('w', $timestamp)) < 6 )
$work_days[] = mktime($timestamp);
}
The $work_days array will contain timestamps which you could use this way:
echo date('Y-m-d', $work_days[0]);
The code above with work in PHP 4 as well as PHP 5. It does not rely on the functionality of the DateTime class which was not available until PHP 5.2 and does not require the use of "libraries" created by other people.

Categories