Finding the number of days between two dates

后端 未结 30 2508
心在旅途
心在旅途 2020-11-21 23:47

How to find number of days between two dates using PHP?

相关标签:
30条回答
  • 2020-11-22 00:02
    function get_daydiff($end_date,$today)
    {
        if($today=='')
        {
            $today=date('Y-m-d');
        }
        $str = floor(strtotime($end_date)/(60*60*24)) - floor(strtotime($today)/(60*60*24));
        return $str;
    }
    $d1 = "2018-12-31";
    $d2 = "2018-06-06";
    echo get_daydiff($d1, $d2);
    
    0 讨论(0)
  • 2020-11-22 00:02
    $diff = strtotime('2019-11-25') - strtotime('2019-11-10');
    echo abs(round($diff / 86400));
    
    0 讨论(0)
  • 2020-11-22 00:03

    If you're using PHP 5.3 >, this is by far the most accurate way of calculating the difference:

    $earlier = new DateTime("2010-07-06");
    $later = new DateTime("2010-07-09");
    
    $diff = $later->diff($earlier)->format("%a");
    
    0 讨论(0)
  • 2020-11-22 00:03

    TL;DR do not use UNIX timestamps. Do not use time(). If you do, be prepared should its 98.0825% reliability fail you. Use DateTime (or Carbon).

    The correct answer is the one given by Saksham Gupta (other answers are also correct):

    $date1 = new DateTime('2010-07-06');
    $date2 = new DateTime('2010-07-09');
    $days  = $date2->diff($date1)->format('%a');
    

    Or procedurally as a one-liner:

    /**
     * Number of days between two dates.
     *
     * @param date $dt1    First date
     * @param date $dt2    Second date
     * @return int
     */
    function daysBetween($dt1, $dt2) {
        return date_diff(
            date_create($dt2),  
            date_create($dt1)
        )->format('%a');
    }
    

    With a caveat: the '%a' seems to indicate the absolute number of days. If you want it as a signed integer, i.e. negative when the second date is before the first, then you need to use the '%r' prefix (i.e. format('%r%a')).


    If you really must use UNIX timestamps, set the time zone to GMT to avoid most of the pitfalls detailed below.


    Long answer: why dividing by 246060 (aka 86400) is unsafe

    Most of the answers using UNIX timestamps (and 86400 to convert that to days) make two assumptions that, put together, can lead to scenarios with wrong results and subtle bugs that may be difficult to track, and arise even days, weeks or months after a successful deployment. It's not that the solution doesn't work - it works. Today. But it might stop working tomorrow.

    First mistake is not considering that when asked, "How many days passed since yesterday?", a computer might truthfully answer zero if between the present and the instant indicated by "yesterday" less than one whole day has passed.

    Usually when converting a "day" to a UNIX timestamp, what is obtained is the timestamp for the midnight of that particular day.

    So between the midnights of October 1st and October 15th, fifteen days have elapsed. But between 13:00 of October 1st and 14:55 of October 15th, fifteen days minus 5 minutes have elapsed, and most solutions using floor() or doing implicit integer conversion will report one day less than expected.

    So, "how many days ago was Y-m-d H:i:s"? will yield the wrong answer.

    The second mistake is equating one day to 86400 seconds. This is almost always true - it happens often enough to overlook the times it doesn't. But the distance in seconds between two consecutive midnights is surely not 86400 at least twice a year when daylight saving time comes into play. Comparing two dates across a DST boundary will yield the wrong answer.

    So even if you use the "hack" of forcing all date timestamps to a fixed hour, say midnight (this is also done implicitly by various languages and frameworks when you only specify day-month-year and not also hour-minute-second; same happens with DATE type in databases such as MySQL), the widely used formula

     FLOOR((unix_timestamp(DATE2) - unix_timestamp(DATE1)) / 86400)
    

    or

     floor((time() - strtotime($somedate)) / 86400)
    

    will return, say, 17 when DATE1 and DATE2 are in the same DST segment of the year; but even if the hour:minute:second part is identical, the argument might be 17.042, and worse still, 16.958 when they are in different DST segments and the time zone is DST-aware. The use of floor() or any implicit truncation to integer will then convert what should have been a 17 to a 16. In other circumstances, expressions like "$days > 17" will return true for 17.042, even if this will look as if the elapsed day count is 18.

    And things grow even uglier since such code is not portable across platforms, because some of them may apply leap seconds and some might not. On those platforms that do, the difference between two dates will not be 86400 but 86401, or maybe 86399. So code that worked in May and actually passed all tests will break next June when 12.99999 days are considered 12 days instead of 13. Two dates that worked in 2015 will not work in 2017 -- the same dates, and neither year is a leap year. And between 2018-03-01 and 2017-03-01, on those platforms that care, 366 days will have passed instead of 365, making 2018 a leap year (which it is not).

    So if you really want to use UNIX timestamps:

    • use round() function wisely, not floor().

    • as an alternative, do not calculate differences between D1-M1-YYY1 and D2-M2-YYY2. Those dates will be really considered as D1-M1-YYY1 00:00:00 and D2-M2-YYY2 00:00:00. Rather, convert between D1-M1-YYY1 22:30:00 and D2-M2-YYY2 04:30:00. You will always get a remainder of about twenty hours. This may become twenty-one hours or nineteen, and maybe eighteen hours, fifty-nine minutes thirty-six seconds. No matter. It is a large margin which will stay there and stay positive for the foreseeable future. Now you can truncate it with floor() in safety.

    The correct solution though, to avoid magic constants, rounding kludges and a maintenance debt, is to

    • use a time library (Datetime, Carbon, whatever); don't roll your own

    • write comprehensive test cases using really evil date choices - across DST boundaries, across leap years, across leap seconds, and so on, as well as commonplace dates. Ideally (calls to datetime are fast!) generate four whole years' (and one day) worth of dates by assembling them from strings, sequentially, and ensure that the difference between the first day and the day being tested increases steadily by one. This will ensure that if anything changes in the low-level routines and leap seconds fixes try to wreak havoc, at least you will know.

    • run those tests regularly together with the rest of the test suite. They're a matter of milliseconds, and may save you literally hours of head scratching.


    Whatever your solution, test it!

    The function funcdiff below implements one of the solutions (as it happens, the accepted one) in a real world scenario.

    <?php
    $tz         = 'Europe/Rome';
    $yearFrom   = 1980;
    $yearTo     = 2020;
    $verbose    = false;
    
    function funcdiff($date2, $date1) {
        $now        = strtotime($date2);
        $your_date  = strtotime($date1);
        $datediff   = $now - $your_date;
        return floor($datediff / (60 * 60 * 24));
    }
    ########################################
    
    date_default_timezone_set($tz);
    $failures   = 0;
    $tests      = 0;
    
    $dom = array ( 0, 31, 28, 31, 30,
                      31, 30, 31, 31,
                      30, 31, 30, 31 );
    (array_sum($dom) === 365) || die("Thirty days hath September...");
    $last   = array();
    for ($year = $yearFrom; $year < $yearTo; $year++) {
        $dom[2] = 28;
        // Apply leap year rules.
        if ($year % 4 === 0)   { $dom[2] = 29; }
        if ($year % 100 === 0) { $dom[2] = 28; }
        if ($year % 400 === 0) { $dom[2] = 29; }
        for ($month = 1; $month <= 12; $month ++) {
            for ($day = 1; $day <= $dom[$month]; $day++) {
                $date = sprintf("%04d-%02d-%02d", $year, $month, $day);
                if (count($last) === 7) {
                    $tests ++;
                    $diff = funcdiff($date, $test = array_shift($last));
                    if ((double)$diff !== (double)7) {
                        $failures ++;
                        if ($verbose) {
                            print "There seem to be {$diff} days between {$date} and {$test}\n";
                        }
                    }
                }
                $last[] = $date;
            }
        }
    }
    
    print "This function failed {$failures} of its {$tests} tests";
    print " between {$yearFrom} and {$yearTo}.\n";
    

    The result is,

    This function failed 280 of its 14603 tests
    

    Horror Story: the cost of "saving time"

    This actually happened some months ago. An ingenious programmer decided to save several microseconds off a calculation that took about thirty seconds at most, by plugging in the infamous "(MidnightOfDateB-MidnightOfDateA)/86400" code in several places. It was so obvious an optimization that he did not even document it, and the optimization passed the integration tests and lurked in the code for several months, all unnoticed.

    This happened in a program that calculates the wages for several top-selling salesmen, the least of which has a frightful lot more clout than a whole humble five-people programmer team taken together. One day some months ago, for reasons that matter little, the bug struck -- and some of those guys got shortchanged one whole day of fat commissions. They were definitely not amused.

    Infinitely worse, they lost the (already very little) faith they had in the program not being designed to surreptitiously shaft them, and pretended - and obtained - a complete, detailed code review with test cases ran and commented in layman's terms (plus a lot of red-carpet treatment in the following weeks).

    What can I say: on the plus side, we got rid of a lot of technical debt, and were able to rewrite and refactor several pieces of a spaghetti mess that hearkened back to a COBOL infestation in the swinging '90s. The program undoubtedly runs better now, and there's a lot more debugging information to quickly zero in when anything looks fishy. I estimate that just this last one thing will save perhaps one or two man-days per month for the foreseeable future.

    On the minus side, the whole brouhaha costed the company about €200,000 up front - plus face, plus undoubtedly some bargaining power (and, hence, yet more money).

    The guy responsible for the "optimization" had changed job a year ago, before the disaster, but still there was talk to sue him for damages. And it didn't go well with the upper echelons that it was "the last guy's fault" - it looked like a set-up for us to come up clean of the matter, and in the end, we're still in the doghouse and one of the team is planning to quit.

    Ninety-nine times out of one hundred, the "86400 hack" will work flawlessly. (For example in PHP, strtotime() will ignore DST, and report that between the midnights of the last Saturday of October and that of the following Monday, exactly 2 * 24 * 60 * 60 seconds have passed, even if that is plainly not true... and two wrongs will happily make one right).

    This, ladies and gentlemen, was one instance when it did not. As with air-bags and seat belts, you will perhaps never really need the complexity (and ease of use) of DateTime or Carbon. But the day when you might (or the day when you'll have to prove you thought about this) will come as a thief in the night. Be prepared.

    0 讨论(0)
  • 2020-11-22 00:05

    You can find dates simply by

    <?php
    $start  = date_create('1988-08-10');
    $end    = date_create(); // Current time and date
    $diff   = date_diff( $start, $end );
    
    echo 'The difference is ';
    echo  $diff->y . ' years, ';
    echo  $diff->m . ' months, ';
    echo  $diff->d . ' days, ';
    echo  $diff->h . ' hours, ';
    echo  $diff->i . ' minutes, ';
    echo  $diff->s . ' seconds';
    // Output: The difference is 28 years, 5 months, 19 days, 20 hours, 34 minutes, 36 seconds
    
    echo 'The difference in days : ' . $diff->days;
    // Output: The difference in days : 10398
    
    0 讨论(0)
  • 2020-11-22 00:06

    Here is my improved version which shows 1 Year(s) 2 Month(s) 25 day(s) if the 2nd parameter is passed.

    class App_Sandbox_String_Util {
        /**
         * Usage: App_Sandbox_String_Util::getDateDiff();
         * @param int $your_date timestamp
         * @param bool $hr human readable. e.g. 1 year(s) 2 day(s)
         * @see http://stackoverflow.com/questions/2040560/finding-the-number-of-days-between-two-dates
         * @see http://qSandbox.com
         */
        static public function getDateDiff($your_date, $hr = 0) {
            $now = time(); // or your date as well
            $datediff = $now - $your_date;
            $days = floor( $datediff / ( 3600 * 24 ) );
    
            $label = '';
    
            if ($hr) {
                if ($days >= 365) { // over a year
                    $years = floor($days / 365);
                    $label .= $years . ' Year(s)';
                    $days -= 365 * $years;
                }
    
                if ($days) {
                    $months = floor( $days / 30 );
                    $label .= ' ' . $months . ' Month(s)';
                    $days -= 30 * $months;
                }
    
                if ($days) {
                    $label .= ' ' . $days . ' day(s)';
                }
            } else {
                $label = $days;
            }
    
            return $label;
        }
    }
    
    0 讨论(0)
提交回复
热议问题