3
\$\begingroup\$

The object is to be able to pass some dates (start date, holiday) and the number of days you want to skip. We only want to skip working days, not weekends and holidays.

Just let me know what you'd do different than what I have. The code works, but I was told there are issues with it, but not told what issues they are.

function getWDays($startDate,$holiday,$wDays){

$d = new DateTime( $startDate );
$t = $d->getTimestamp();
$h = strtotime($holiday);

// loop for $wDays days
for($i=0; $i<$wDays; $i++){

    // 1 day = 86400 seconds
    $addDay = 86400;

    $nextDay = date('w', ($t+$addDay));
    if($nextDay == 0 || $nextDay == 6) {
        $i--;
    }
    $t = $t+$addDay;
    if ($t == $h) {
        // lets make sure the holiday isn't one of our weekends
        if(!$nextDay == 0 || !$nextDay == 6) {
            $t = $t+$addDay;    
        }
    }
}

$d->setTimestamp($t);


return $d->format( 'Y-m-d' );

}

echo getWDays("2013-08-29","2013-09-02", 3)
\$\endgroup\$
2

2 Answers 2

1
\$\begingroup\$

Here is another way of doing the same thing

<?php

function getWDays($startDate,$holiday,$wDays) {

    // using + weekdays excludes weekends
    $new_date = date('Y-m-d', strtotime("{$startDate} +{$wDays} weekdays"));

    $holiday_ts = strtotime($holiday);

    // if holiday falls between start date and new date, then account for it
    if ($holiday_ts >= strtotime($startDate) && $holiday_ts <= strtotime($new_date)) {

        // check if the holiday falls on a working day
        $h = date('w', $holiday_ts);
            if ($h != 0 && $h != 6 ) {
            // holiday falls on a working day, add an extra working day
            $new_date = date('Y-m-d', strtotime("{$new_date} + 1 weekdays"));
        }
    }

    return $new_date;
}

// here is an example
$start = "2013-08-29";
$holiday = "2013-09-02";
$wDays = 3;

echo "Start: ",date("Y-m-d D", strtotime($start)),"<br />";
echo "Holiday: ",date("Y-m-d D", strtotime($holiday)),"<br />";
echo "WDays: $wDays<br />";

echo getWDays($start, $holiday, $wDays);
\$\endgroup\$
6
  • \$\begingroup\$ Thanks for the reply. I like your use of Weekdays. I need to use that more often. Much appreciated. \$\endgroup\$
    – pjmetzger
    Sep 5, 2013 at 21:59
  • \$\begingroup\$ what about multiple holidays ? \$\endgroup\$ Oct 12, 2019 at 18:02
  • \$\begingroup\$ see here : stackoverflow.com/questions/21142917/… \$\endgroup\$ Nov 11, 2020 at 15:13
  • \$\begingroup\$ Why is this answer accepted? Isn't the point of codereview to review code and point out problems and possible improvements instead of supplying a complete answer without explaining what is wrong with the code? (this kind of invalidates the possible learning experience IMHO). \$\endgroup\$ Oct 1, 2022 at 9:44
  • \$\begingroup\$ Also, the snippet in this answer repeats the problem in the question of passing only 1 holiday. IMHO, the code is pretty useless if only one holiday is passed, see also point 3 in actual review: codereview.stackexchange.com/a/275392/155974 \$\endgroup\$ Oct 1, 2022 at 9:45
5
\$\begingroup\$
  1. Use full words to express your function's ability so that your code is intuitive to other developers.
  2. Organize the incoming parameters so that optional values are listed last. Assign a default value to optional parameters so that they can be omitted from function calls.
  3. It is too limiting to make the "holiday" parameter a string type variable. Because it is possible that multiple holidays might be encountered while incrementing the date, make the parameter an array. Because it is possible that there are no holidays, design your function to work even if the array is empty.
  4. Use type hinting on your function parameter to make your code clear and easy to maintain.
  5. I'll demonstrate using a Datetime object as the hero of the process, but strtotime() will work too.
  6. Use a classic for() loop to increment the counter variable.
  7. Use modify() to advance the date to the next available weekday.
  8. Temporarily store the modified date as Y-m-d so that it can be compared in the next step and eventually returned.
  9. Use a post-loop check to potentially advance the date past a forbidden date (holiday).
  10. To safeguard the function from breakage when an integer less than 1 is passed as the second parameter, I am using the null coalescing operator to fallback to the starting date. Other developers may instead prefer to throw an exception -- I would support that technique as well.

Code: (Demo)

function getFutureWeekday(
    string $startDate,
    int $numberOfDays,
    array $holidays = []
): string
{
    $dt = new DateTime($startDate);
    for ($i = 0; $i < $numberOfDays; ++$i) {
        do {
            $date = $dt->modify('+1 weekday')->format('Y-m-d');
        } while (in_array($date, $holidays));
    }
    return $date ?? $startDate;
}

echo getFutureWeekday('2022-03-30', 4, ['2022-04-05']);
\$\endgroup\$
2
  • 1
    \$\begingroup\$ works amazingly \$\endgroup\$
    – demo7up
    Dec 21, 2022 at 10:28
  • 1
    \$\begingroup\$ Woo very nice and well explained :) \$\endgroup\$ Aug 17, 2023 at 12:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.