简体   繁体   中英

PHP calculate a date based on function for working days

I am using this function (that I found on this forum) to calculate the number of working days between a range:

<?php
//The function returns the no. of business days between two dates and it skips the holidays
function getWorkingDays($startDate,$endDate,$holidays){
// do strtotime calculations just once
$endDate = strtotime($endDate);
$startDate = strtotime($startDate);


//The total number of days between the two dates. We compute the no. of seconds and divide it to 60*60*24
//We add one to inlude both dates in the interval.
$days = ($endDate - $startDate) / 86400 + 1;

$no_full_weeks = floor($days / 7);
$no_remaining_days = fmod($days, 7);

//It will return 1 if it's Monday,.. ,7 for Sunday
$the_first_day_of_week = date("N", $startDate);
$the_last_day_of_week = date("N", $endDate);

//---->The two can be equal in leap years when february has 29 days, the equal sign is added here
//In the first case the whole interval is within a week, in the second case the interval falls in two weeks.
if ($the_first_day_of_week <= $the_last_day_of_week) {
    if ($the_first_day_of_week <= 6 && 6 <= $the_last_day_of_week) $no_remaining_days--;
    if ($the_first_day_of_week <= 7 && 7 <= $the_last_day_of_week) $no_remaining_days--;
}
else {
    // (edit by Tokes to fix an edge case where the start day was a Sunday
    // and the end day was NOT a Saturday)

    // the day of the week for start is later than the day of the week for end
    if ($the_first_day_of_week == 7) {
        // if the start date is a Sunday, then we definitely subtract 1 day
        $no_remaining_days--;

        if ($the_last_day_of_week == 6) {
            // if the end date is a Saturday, then we subtract another day
            $no_remaining_days--;
        }
    }
    else {
        // the start date was a Saturday (or earlier), and the end date was (Mon..Fri)
        // so we skip an entire weekend and subtract 2 days
        $no_remaining_days -= 2;
    }
}

//The no. of business days is: (number of weeks between the two dates) * (5 working days) + the remainder
//---->february in none leap years gave a remainder of 0 but still calculated weekends between first and last day, this is one way to fix it
$workingDays = $no_full_weeks * 5;
if ($no_remaining_days > 0 )
{
  $workingDays += $no_remaining_days;
}

//We subtract the holidays
foreach($holidays as $holiday){
    $time_stamp=strtotime($holiday);
    //If the holiday doesn't fall in weekend
    if ($startDate <= $time_stamp && $time_stamp <= $endDate && date("N",$time_stamp) != 6 && date("N",$time_stamp) != 7)
        $workingDays--;
}

return $workingDays;
}

//Example:

$holidays=array("2008-12-25","2008-12-26","2009-01-01");

echo getWorkingDays("$startdate","$enddate",$holidays)
?>

Now I'd like to extend this function a bit. I would like to generate what date it will be if I add X working days from the starting date. Say for example i have a variable that holds the value 20

$workingdays = "20";

And the $startdate is 2012-06-01 i would like to make this function calculate that the startdate + 20 working days will be 2012-06-28. Would this be possible?

I did a similar thing a while back using the below function. the key here is skipping the weekends, you can extend this to skip holidays as well.

Example:

Call the function - > addDays(strtotime($startDate), 20, $skipdays,$skipdates = array())

 <?php
    function addDays($timestamp, $days, $skipdays = array("Saturday", "Sunday"), $skipdates = NULL) {
        // $skipdays: array (Monday-Sunday) eg. array("Saturday","Sunday")
        // $skipdates: array (YYYY-mm-dd) eg. array("2012-05-02","2015-08-01");
       //timestamp is strtotime of ur $startDate
        $i = 1;

        while ($days >= $i) {
            $timestamp = strtotime("+1 day", $timestamp);
            if ( (in_array(date("l", $timestamp), $skipdays)) || (in_array(date("Y-m-d", $timestamp), $skipdates)) )
            {
                $days++;
            }
            $i++;
        }

        return $timestamp;
        //return date("m/d/Y",$timestamp);
    }
    ?>

[Edit]: Just read an amazing article on nettuts, Hope this helps http://net.tutsplus.com/tutorials/php/dates-and-time-the-oop-way/

If someone's interested, I'm using this function to add X business days to a date. The function takes in a timestamp and returns a timestamp. It's possible to specify the holidays via an array (if in the US, you can use usBankHolidays() ).

At the moment, it's assuming Saturday and Sunday are not business days but that can be changed easily.

Code :

function addBusinessDays($date, $days, $holidays = array()) {
    $output = new DateTime();
    $output->setTimestamp($date);
    while ($days > 0) {
        $weekDay = $output->format('N');

        // Skip Saturday and Sunday
        if ($weekDay == 6 || $weekDay == 7) {
            $output = $output->add(new DateInterval('P1D'));
            continue;
        }

        // Skip holidays
        $strDate = $output->format('Y-m-d');
        foreach ($holidays as $s) {
            if ($s == $strDate) {
                $output = $output->add(new DateInterval('P1D'));
                continue 2;
            }
        }

        $days--;
        $output = $output->add(new DateInterval('P1D'));
    }
    return $output->getTimestamp();
}

function usBankHolidays($format = 'datesonly') {
    $output = array(
        array('2015-05-25', 'Memorial Day'),
        array('2015-07-03', 'Independence Day'),
        array('2015-09-07', 'Labor Day'),
        array('2015-10-12', 'Columbus Day'),
        array('2015-11-11', 'Veterans Day'),
        array('2015-11-26', 'Thanksgiving Day'),
        array('2015-12-25', 'Christmas Day'),
        array('2016-01-01', 'New Year Day'),
        array('2016-01-18', 'Martin Luther King Jr. Day'),
        array('2016-02-15', 'Presidents Day (Washingtons Birthday)'),
        array('2016-05-30', 'Memorial Day'),
        array('2016-07-04', 'Independence Day'),
        array('2016-09-05', 'Labor Day'),
        array('2016-10-10', 'Columbus Day'),
        array('2016-11-11', 'Veterans Day'),
        array('2016-11-24', 'Thanksgiving Day'),
        array('2016-12-25', 'Christmas Day'),
        array('2017-01-02', 'New Year Day'),
        array('2017-01-16', 'Martin Luther King Jr. Day'),
        array('2017-02-20', 'Presidents Day (Washingtons Birthday)'),
        array('2017-05-29', 'Memorial Day'),
        array('2017-07-04', 'Independence Day'),
        array('2017-09-04', 'Labor Day'),
        array('2017-10-09', 'Columbus Day'),
        array('2017-11-10', 'Veterans Day'),
        array('2017-11-23', 'Thanksgiving Day'),
        array('2017-12-25', 'Christmas Day'),
        array('2018-01-01', 'New Year Day'),
        array('2018-01-15', 'Martin Luther King Jr. Day'),
        array('2018-02-19', 'Presidents Day (Washingtons Birthday)'),
        array('2018-05-28', 'Memorial Day'),
        array('2018-07-04', 'Independence Day'),
        array('2018-09-03', 'Labor Day'),
        array('2018-10-08', 'Columbus Day'),
        array('2018-11-12', 'Veterans Day'),
        array('2018-11-22', 'Thanksgiving Day'),
        array('2018-12-25', 'Christmas Day'),
        array('2019-01-01', 'New Year Day'),
        array('2019-01-21', 'Martin Luther King Jr. Day'),
        array('2019-02-18', 'Presidents Day (Washingtons Birthday)'),
        array('2019-05-27', 'Memorial Day'),
        array('2019-07-04', 'Independence Day'),
        array('2019-09-02', 'Labor Day'),
        array('2019-10-14', 'Columbus Day'),
        array('2019-11-11', 'Veterans Day'),
        array('2019-11-28', 'Thanksgiving Day'),
        array('2019-12-25', 'Christmas Day'),
        array('2020-01-01', 'New Year Day'),
        array('2020-01-20', 'Martin Luther King Jr. Day'),
        array('2020-02-17', 'Presidents Day (Washingtons Birthday)'),
        array('2020-05-25', 'Memorial Day'),
        array('2020-07-03', 'Independence Day'),
        array('2020-09-07', 'Labor Day'),
        array('2020-10-12', 'Columbus Day'),
        array('2020-11-11', 'Veterans Day'),
        array('2020-11-26', 'Thanksgiving Day'),
        array('2020-12-25', 'Christmas Day '),
    );

    if ($format == 'datesonly') {
        $temp = array();
        foreach ($output as $item) {
            $temp[] = $item[0];
        }
        $output = $temp;
    }

    return $output;
}

Usage:

$deliveryDate = addBusinessDays(time(), 7, usBankHolidays());

From @this.lau_ answer a rewrite simpler and more logical algorithm, with dynamic (fixed) holydays

public function addBusinessDays($date, $days) {

    $output = new DateTime();
    $output->setTimestamp($date);

    while ($days > 0) {

        $output = $output->add(new DateInterval('P1D'));
        $weekDay = $output->format('N');
        $strDate = $output->format('Y-m-d');

        // Skip Saturday and Sunday
        if ($weekDay == 6 || $weekDay == 7) {

            continue;

        }

        // Skip holidays
        $holidays = $this->_getHolidays();            

        foreach ($holidays as $holiday_date => $holiday_name) {

            if ($holiday_date == $strDate) {

                continue 2;

            }

        }

        $days--;

    }

    return $output->getTimestamp();

}



public function _getHolidays() {

    $feste = array(
        date("Y") . "-01-01" => "Capodanno", 
        date("Y") . "-01-06" => "Epifania", 
        date("Y") . "-04-25" => "Liberazione", 
        date("Y") . "-05-01" => "Festa Lavoratori", 
        date("Y") . "-06-02" => "Festa della Repubblica", 
        date("Y") . "-08-15" => "Ferragosto", 
        date("Y") . "-11-01" => "Tutti Santi", 
        date("Y") . "-12-08" => "Immacolata", 
        date("Y") . "-12-25" => "Natale", 
        date("Y") . "-12-26" => "St. Stefano"
    );

    return $feste;

}

Call the function with

$deliveryDate = addBusinessDays(time(), 7);

Using this.lau_ function , I was abble to reverse it, for subtraction dates, for use of a expiration date. Hope this helps someone:

function subBusinessDays( $date, $days, $holidays = array() ) {
            $output = new DateTime();
            $output->setTimestamp( $date );

            while ( $days > 0 ) {

                $output = $output->sub( new DateInterval( 'P1D' ) );

                // Skip holidays
                $strDate = $output->format( 'Y-m-d' );
                if ( in_array( $strDate, $holidays ) ) {
                    // Skip Saturday and Sunday
                    $output = $output->sub( new DateInterval( 'P1D' ) );
                    continue;
                }

                $weekDay = $output->format( 'N' );
                if ($weekDay <= 5 ) {
                    $days --;
                }

            }

            return $output->getTimestamp();
        }

I've got a better solution with only two parameters on the function.

Just call the function

addDays($currentdate, $WordkingDatestoadd);

And then, use the below function

function addDays($timestamp, $days) {

$workingDays = [1, 2, 3, 4, 5]; # date format = N (1 = Monday, ...)
$holidayDays = ['*-12-25', '*-01-01']; # variable and fixed holidays

$timestamp = new DateTime($timestamp);
$days = $days;

for($i=1 ; $i <= $days; $i++){
    $timestamp = $timestamp->modify('+1 day');
    if ((!in_array($timestamp->format('N'), $workingDays)) && (!in_array($timestamp->format('*-m-d'), $holidayDays))){
        $i--;
    }
}

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

Here I use the HolidayAPI for the holiday's list dynamically.HolidayAPI is free to implement. I am Bangladeshi that's why I use the country code BD. API link - https://holidayapi.com/v1/holidays?pretty&key=your_key&country=your_country_code&year=2020

// cURL start
$cURLConnection = curl_init();
curl_setopt($cURLConnection, CURLOPT_URL, 'your api');
curl_setopt($cURLConnection, CURLOPT_RETURNTRANSFER, true);
$phoneList = curl_exec($cURLConnection);
curl_close($cURLConnection);
$dateResponse = json_decode($phoneList);
// cURL end

$holidays = array(); //declear holidays array`enter code here`
for ($i=0;$i<count($dateResponse->holidays);$i++){
    $holidays[] = $dateResponse->holidays[$i]->date;  //inserting holidays in array from api
}

$startDate = "2020-02-26";  //insert value thats you prefer
$timestamp  = strtotime($startDate);  // convert date to time
$weekends = array("Friday", "Saturday");
$days = 0;  //initialization days counter

while (1){  //
    $timestamp = strtotime("+1 day", $timestamp); //day increment
    if ( (in_array(date("l", $timestamp), $weekends)) || (in_array(date("Y-m-d", $timestamp), $holidays)) ) //checking weekends and holidays
    {
        continue;
    }else{
        $days++;``
        if($days >= 5){  //interval - What will be the working days after 5 days
            echo date("Y-m-d", $timestamp); // print expected output
            break;
        }
    }
}

Input: 2020-02-26 Output: 2020-03-05

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM