Repeating Events on the "nth" Weekday of Every Month
Asked Answered
C

3

7

I've looked at at least 2 dozen topics about this and haven't really found a good answer yet, so I come to you to ask once again for answers regarding the dreaded topic of Repeating Events.

I've got Daily, Weekly, Monthly, and Yearly repeats working out fine for now (I still need to revamp the system with Exception events and whatnot, but it works for the time being). But, we want to be able to add the ability to repeat events on the (1st,2nd,3rd,4th,5th) [Sun|Mon|Tue|Wed|Thu|Fri|Sat] of every month, every other month, and every three months.

Now, if I can just understand the logic for the every month, I can figure out the every other month and the every three months.

Here's a bit of what I have so far (note: I'm not saying I have the best way of doing it or anything, but the system is one we update very slowly over time when we aren't busy with other projects, so I make the code more efficient as I have the time).

First I get the starting and ending dates formatted for date calculations:

$ending = $_POST['end_month'] . "/" . $_POST['end_day'] . "/" . substr($_POST['end_year'], 2, 2);
$starting = $_POST['month'] . "/" . $_POST['day'] . "/" . substr($_POST['year'], 2, 2);

Then I get the difference between those two to know how many times to repeat using a function I'm fairly certain I found on here some time ago and dividing that amount by 28 days to get just about how many TIMES it needs to repeat so that there is one a month:

$repeat_number = date_diff($starting, $ending) / 28;
//find the difference in DAYS between the two dates
function date_diff($old_date, $new_date) {
$offset = strtotime($new_date) - strtotime($old_date);
return $offset/60/60/24;
}

Then I add the (1st,2nd,etc...) part to the [Sun|Mon|etc...] part to figure out what they are looking for giving me somehthing like 'first Sunday' :

$find = $_POST['custom_number']. ' ' . $_POST['custom_day'];

Then I use a loop that runs the number of times this needs to repeat (the $repeat_number from above):

for($m = 0; $m <= $repeat_number; $m++) {
if($m == 0) {
     $month = date('F', substr($starting,0,2));
} else {
     $month = date('F', strtotime($month . ' + ' . $m . ' months'));
}
$repeat_date = strtotime($find . ' in ' . $month);
}

Now, I'm aware this code doesn't work, I at one time did have code that turned up the correct month and year for the repeat, but wouldn't necessarily find the first tuesday or whatever it was that was being looked for.

If anyone could point me back in the right direction, it would be most appreciated. I've been lurking in the community for some time now, just started trying to actively participate recently.

Thanks in advance for any input or advice you can provide.

Cyst answered 21/3, 2011 at 16:14 Comment(3)
Don't forget to support the same pattern from the end of the month, or atleast last <insert weekday> every month.Breakable
You should rename your date_diff function -- when you upgrade to PHP 5.3, it will conflict with the built in date_diff function.Crumby
Thanks for the information Charles. I wasn't aware they added a date_diff function.Cyst
C
8

Here's a possible alternative for you. strtotime is powerful, and the syntax includes really useful bits like comprehensive relative time and date wording.

You can use it to generate the first through Nth specific weekdays of a specific month by using the format " of ". Here's an example using date_create to invoke a DateTime object, but regular old strtotime works the same way:

php > $d = date_create('Last Friday of March 2011'); if($d instanceof DateTime) echo $d->format('l F d Y H:i:s');
Friday March 25 2011 00:00:00
php > $d = date_create('First Friday of March 2011'); if($d instanceof DateTime) echo $d->format('l F d Y H:i:s');
Friday March 04 2011 00:00:00
php > $d = date_create('First Sunday of March 2011'); if($d instanceof DateTime) echo $d->format('l F d Y H:i:s');
Sunday March 06 2011 00:00:00
php > $d = date_create('Fourth Sunday of March 2011'); if($d instanceof DateTime) echo $d->format('l F d Y H:i:s');
Sunday March 27 2011 00:00:00
php > $d = date_create('Last Sunday of March 2011'); if($d instanceof DateTime) echo $d->format('l F d Y H:i:s');
Sunday March 27 2011 00:00:00

It will also overflow the month, if you ask for an invalid one:

php > $d = date_create('Ninth Sunday of March 2011'); if($d instanceof DateTime) echo $d->format('l F d Y H:i:s');
Sunday May 01 2011 00:00:00

Note that it only works with the ordinal number wording. You can't pass "1st" or "3rd", unfortunately.

Once you use this to grab the proper Nth weekday, you can then simply add the number of needed weekdays to skip (7 for one week, 14 for two, 21 for three, etc) as required until the designated end date or designated number of weeks has passed. Once again, strtotime to the rescue, using the second argument to chain together relativeness:

php > echo date('l F d Y H:i:s', strtotime('+14 days', strtotime('First Thursday of March 2011')));
Thursday March 17 2011 00:00:00

(My local copy also accepted '+14 days First Thursday of March 2011', but that felt kind of weird.)

Crumby answered 21/3, 2011 at 17:2 Comment(4)
Just a heads up to anyone else who plans to use this, the "of" part of strtotime doesn't work on PHP v4.3.9.Cyst
@ericissocial, why are you still writing code designed for that prehistoric PHP version? PHP4 has been dead and unsupported for literally years now.Crumby
We have our CMS and clients on an old server because my boss has just always had everything there for the last 5 years or so. I've actually convinced him we need to move it, it just takes a bit of preparation to move the system controlling all of your clients' sites over to a new server (and it takes some work to prepare people for change). Your answer worked wonders on my test server and on the server we plan to move the CMS to, though. My fingers are crossed that in the next week I won't have to deal with PHP 4.3.9 anymore.Cyst
Note that most of these are only available post 5.3. If you tried Last Monday of March 2013 it wouldn't work in 5.2Germanophile
A
3

I have a more simple method, although it may seem hack-ish.

What we can do is take the start date, and infer it's "n" value from it by taking the ceiling of its day divided by 7.

For example, if your start date is the 18th, we divide by 7 and take the ceiling to infer that it is the 3rd "whatever-day" of the month.

From there we can simply repeatedly add 7 days to this to get a new date, and whenever the new date's ceilinged 7-quotient is 3, we've hit the next "nth whatever-day" of the month.

Some code can be seen below:

$day_nth = ceil(date("j", strtotime($date))/7);
for ($i = 0; $i < $number_of_repeats; $i++) {

    while (ceil(date("j", strtotime($date))/7) != $day_nth)
        $date = date("Y-m-d", strtotime("+7 days", strtotime($date)));

    /* Add your event, or whatever */

    /* Now we increment date by 7 days before the loop starts again,
    ** otherwise the while loop would never execute and we'd just be
    ** adding to the same date repeatedly. */
    $date = date("Y-m-d", strtotime("+7 days", strtotime($date)));
}

What's nice about this method is that we don't even have to concern ourselves with which day of the week we're dealing with. We just infer everything from the start date and away we go.

Amanita answered 15/1, 2014 at 19:8 Comment(1)
Thank you, I had foolishly been using floor($start / 7) + 1 which is wrong on the 7th, 14th, 21st and 28th, but ok on the other days :)Basement
A
0

I make it like that:

            //$typeOfDate = 0;
            $typeOfDate = 1;
            $day = strtotime("2013-02-01");
            $to = strtotime(date("Y-m-d", $day) . " +6 month");

            if ($typeOfDate == 0) { //use the same day (number) of the month
                $dateArray[] = date("Y-m-d", $day);
                $event_day_number = date("d", $day);
                while ($day <= $to) {
                    $nextMonth = date("m", get_x_months_to_the_future($day));
                    $day = strtotime(date("Y-" . $nextMonth . "-d", $day));
                    $dateArray[] = date("Y-m-d", $day);
                }
            }

            if ($typeOfDate == 0) { //use the same day (like friday) of the month
                $dateArray[] = date("Y-m-d", $day);
                $monthEvent = date("m", $day);
                $day = strtotime(date("Y-m-d", $day) . " +4 weeks");
                $newMonthEvent = date("m", $day);
                if ($monthEvent == $newMonthEvent) {
                    $day = strtotime(date("Y-m-d", $day) . " +7 days");
                }
                while ($day <= $to) {
                    $dateArray[] = date("Y-m-d", $day);
                    $monthEvent = date("m", $day);
                    $day = strtotime(date("Y-m-d", $day) . " +4 weeks");
                    $newMonthEvent = date("m", $day);
                    if ($monthEvent == $newMonthEvent) {
                        $day = strtotime(date("Y-m-d", $day) . " +7 days");
                    }
                }
            }
Ardel answered 26/2, 2013 at 22:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.