Hướng dẫn dùng datediff calculator trong PHP

Bài viết hôm nay sẽ giải quyết câu hỏi làm thế nào tính được khoảng cách giữa 2 ngày bất kỳ và lấy kết quả trả về theo các mục đích khác nhau.

1) Tính khoảng cách giữa 2 ngày

Thuật toán được mô tả như sau :

  1. Dùng hàm strtotime() để tính ra số giây được tính từ thời điểm 01/01/1970 00:00:00 GMT đến hiện tại của 2 ngày được nhập vào.
  2. Dùng hàm abs() để lấy giá trị tuyệt đối của hiệu 2 ngày đó (vì không biết ngày nào lớn hơn).
  3. Lấy kết quả chia cho tổng số giây trong 1 ngày (24 x 60 x 60 = 86400).
  4. Cuối cùng sử dụng hàm floor() để làm tròn số ngày trả về.

Và đây là đoạn code:

<?php
$first_date = strtotime('2012-07-11');
$second_date = strtotime('2012-07-14');
$datediff = abs($first_date - $second_date);
echo floor($datediff / (60*60*24));
?>

Kết quả:

3

2) Lấy kết quả trả về theo định dạng ngày, tháng, năm

Các bạn có thể thấy kết quả trả về của đoạn code ở trên là khoảng cách (số ngày) giữa 2 ngày nhập vào. Câu hỏi đặt ra làm thế nào tính chính xác khoảng cách đó ra ngày, tháng, năm. Nói một cách dễ hiểu là, giả sử tôi nhập vào 2 ngày là 01/01/2012 và 04/05/2014. Vậy hai ngày đó cách nhau bao nhiêu năm, tháng và ngày ?

a, Cách 1

<?php
$date1 = "2012-03-24";
$date2 = "2012-03-26";
$diff = abs(strtotime($date2) - strtotime($date1));
$years = floor($diff / (365*60*60*24));
$months = floor(($diff - $years * 365*60*60*24) / (30*60*60*24));
$days = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24)/ (60*60*24));
echo $years . " years, " . $months . " months, " . $days . " days";
?>

b, Cách 2 (Đoạn code này chỉ áp dụng cho PHP 5.3+)

<?php
$date1 = new DateTime("2012-03-24");
$date2 = new DateTime("2012-03-26");
$interval = $date1->diff($date2);
echo $interval->y . " years, " . $interval->m . " months, " . $interval->d . " days ";
?>

Và kết quả

0 years, 0 months, 2 days

3) Lấy kết quả trả về theo định dạng ngày, tháng, năm, giờ, phút, giây

Tương tự như vậy, đoạn code dưới đây sẽ tính chính xác khoảng cách giữa 2 ngày ra định dạng ngày, tháng, năm, giờ, phút, giây.

Internet TV

<?php
$date1 = "2008-11-01 22:45:00";
$date2 = "2009-12-04 13:44:01";
$diff = abs(strtotime($date2) - strtotime($date1));
$years = floor($diff / (365*60*60*24));
$months = floor(($diff - $years * 365*60*60*24) / (30*60*60*24));
$days = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24) / (60*60*24));
$hours = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24 - $days*60*60*24) / (60*60));
$minutes = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24 - $days*60*60*24 - $hours*60*60) / 60);
$seconds = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24 - $days*60*60*24 - $hours*60*60 - $minutes*60));
echo $years." years, ".$months." months, ".$days." days, ".$hours." hours, ".$minutes." minutes, ".$seconds." seconds";
?>

Và kết quả

1 years, 1 months, 2 days, 14 hours, 59 minutes, 1 seconds

Hi vọng bài viết này giúp ích cho các bạn. Thân ái!

TL; DR làm không sử dụng timestamps UNIX. Không sử dụngtime() . Nếu bạn làm như vậy, hãy chuẩn bị nếu độ tin cậy 98,0825% của nó làm bạn thất vọng. Sử dụng DateTime (hoặc Carbon).

Các câu trả lời đúng là người do Saksham Gupta (câu trả lời khác cũng là chính xác):

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

Hoặc thủ tục như một lớp lót:

/**
 * 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');
}

Với một cảnh báo: '% a' dường như cho biết số ngày tuyệt đối . Nếu bạn muốn nó là số nguyên đã ký, tức là âm khi ngày thứ hai ở trước ngày đầu tiên, thì bạn cần sử dụng tiền tố '% r' (tức là format('%r%a')).


Nếu bạn thực sự phải sử dụng dấu thời gian UNIX, hãy đặt múi giờ thành GMT để tránh hầu hết các cạm bẫy chi tiết bên dưới.


Câu trả lời dài: tại sao chia cho 24 * 60 * 60 (còn gọi là 86400) là không an toàn

Hầu hết các câu trả lời sử dụng dấu thời gian UNIX (và 86400 để chuyển đổi thành ngày) đưa ra hai giả định, có thể dẫn đến các tình huống có kết quả saicác lỗi tinh vi có thể khó theo dõi và phát sinh ngay cả vài ngày, vài tuần hoặc vài tháng sau triển khai thành công. Không phải là giải pháp không hoạt động - nó hoạt động. Hôm nay. Nhưng nó có thể ngừng hoạt động vào ngày mai.

Lỗi đầu tiên là không xem xét rằng khi được hỏi, "Đã bao nhiêu ngày trôi qua kể từ ngày hôm qua?", Một máy tính có thể trả lời trung thực bằng 0 nếu giữa hiện tại và tức thời được chỉ ra bởi "ngày hôm qua" chưa đầy một ngày trôi qua.

Thông thường khi chuyển đổi "ngày" thành dấu thời gian UNIX, cái thu được là dấu thời gian cho nửa đêm của ngày cụ thể đó.

Vì vậy, giữa các ngày giữa tháng 1 và 15 tháng 10, mười lăm ngày đã trôi qua. Nhưng trong khoảng thời gian từ 13:00 ngày 1 tháng 10 đến 14:55 ngày 15 tháng 10, mười lăm ngày trừ đi 5 phút đã trôi qua và hầu hết các giải pháp sử dụng floor()hoặc thực hiện chuyển đổi số nguyên ngầm sẽ báo cáo ít hơn một ngày so với dự kiến .

Vậy, "Ymd H: i: s" cách đây bao nhiêu ngày? sẽ mang lại câu trả lời sai .

Sai lầm thứ hai là tương đương một ngày với 86400 giây. Điều này gần như luôn luôn đúng - nó thường xảy ra đủ để bỏ qua những lần nó không xảy ra. Nhưng khoảng cách tính bằng giây giữa hai lần giữa chừng liên tiếp chắc chắn không phải là 86400 ít nhất hai lần một năm khi thời gian tiết kiệm ánh sáng ban ngày phát huy tác dụng. So sánh hai ngày trên một ranh giới DST sẽ cho câu trả lời sai.

Vì vậy, ngay cả khi bạn sử dụng "hack" để buộc tất cả các dấu thời gian ngày thành một giờ cố định, hãy nói là nửa đêm (điều này cũng được thực hiện hoàn toàn bằng các ngôn ngữ và khung khác nhau khi bạn chỉ xác định ngày-tháng-năm và không phải giờ-phút-giây; cùng happpens với loại DATE trong cơ sở dữ liệu như MySQL), công thức được sử dụng rộng rãi

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

hoặc là

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

sẽ trở lại, giả sử, 17 khi DATE1 và DATE2 nằm trong cùng phân khúc DST của năm; nhưng nó có thể trả về 17.042 và tệ hơn nữa là 16.958. Việc sử dụng sàn () hoặc bất kỳ phép cắt ngầm nào thành số nguyên sau đó sẽ chuyển đổi số 17 thành số 16. Trong các trường hợp khác, các biểu thức như "$ ngày> 17" sẽ trả về true17.042 ngay cả khi điều này cho biết số ngày đã trôi qua là 18.

Và mọi thứ thậm chí còn xấu hơn vì mã như vậy không thể di động trên các nền tảng, bởi vì một số trong số chúng có thể áp dụng giây nhuận và một số có thể không . Trên những nền tảng mà làm , sự khác biệt giữa hai ngày sẽ không được 86400 nhưng 86.401, hoặc có thể 86399. Vì vậy, mã mà làm việc vào tháng Năm và thực sự thông qua tất cả các bài kiểm tra sẽ phá vỡ tháng sáu tới khi 12,99999 ngày được coi là 12 ngày thay vì 13. Hai ngày đã làm việc trong năm 2015 sẽ không hoạt động trong năm 2017 - cùng ngày và không năm nào là năm nhuận. Nhưng giữa 2018/03/01 và 2017/03/01, trên những nền tảng mà chăm sóc, 366 ngày đã trôi qua sẽ thay vì 365, làm cho 2017 một năm nhuận.

Vì vậy, nếu bạn thực sự muốn sử dụng dấu thời gian UNIX:

  • sử dụng round()chức năng một cách khôn ngoan, không floor().

  • thay thế, không tính toán sự khác biệt giữa D1-M1-YYY1 và D2-M2-YYY2. Những ngày đó sẽ thực sự được coi là D1-M1-YYY1 00:00:00 và D2-M2-YYY2 00:00:00. Thay vào đó, chuyển đổi giữa D1-M1-YYY1 22:30:00 và D2-M2-YYY2 04:30:00. Bạn sẽ luôn nhận được phần còn lại của khoảng hai mươi giờ. Điều này có thể trở thành hai mươi mốt giờ hoặc mười chín, và có thể mười tám giờ, năm mươi chín phút ba mươi sáu giây. Không vấn đề. Đó là một biên độ lớn sẽ ở đó và tích cực cho tương lai gần. Bây giờ bạn có thể cắt nó với floor()an toàn.

Các đúng giải pháp tuy nhiên, để tránh các hằng số ma thuật, kludges tròn và một món nợ bảo trì, là để

  • sử dụng thư viện thời gian (Datetime, Carbon, bất cứ điều gì); đừng tự lăn

  • viết các trường hợp kiểm tra toàn diện bằng cách sử dụng các lựa chọn ngày thực sự xấu - qua các ranh giới DST, qua các năm nhuận, qua các giây nhuận, v.v., cũng như các ngày phổ biến. Lý tưởng nhất là (các cuộc gọi đến datetime rất nhanh !) Tạo ra các ngày có giá trị trong bốn năm (và một ngày) bằng cách tập hợp chúng từ các chuỗi, theo tuần tự và đảm bảo rằng sự khác biệt giữa ngày đầu tiên và ngày được kiểm tra tăng dần theo từng ngày. Điều này sẽ đảm bảo rằng nếu có bất cứ điều gì thay đổi trong các thói quen ở mức độ thấp và các giây khắc phục lỗi cố gắng phá hoại, thì ít nhất bạn sẽ biết .

  • chạy các bài kiểm tra thường xuyên cùng với phần còn lại của bộ kiểm tra. Chúng là vấn đề của một phần nghìn giây và có thể giúp bạn tiết kiệm hàng giờ đồng hồ .


Dù giải pháp của bạn là gì, hãy kiểm tra nó!

Hàm funcdiffbên dưới thực hiện một trong các giải pháp (như nó xảy ra, một giải pháp được chấp nhận) trong một kịch bản thế giới thực.

<?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 between {$yearFrom} and {$yearTo}.\n";

Kết quả là,

This function failed 280 of its 14603 tests

Câu chuyện kinh dị: chi phí "tiết kiệm thời gian"

Điều này thực sự đã xảy ra vài tháng trước. Một lập trình viên khéo léo đã quyết định tiết kiệm vài micro giây trong một phép tính mất tối đa ba mươi giây, bằng cách cắm mã "khét tiếng" (MidnightOfDateB-MidnightOfDateA) / 86400 "ở một số nơi. Rõ ràng là một sự tối ưu hóa mà anh ta thậm chí không ghi nhận nó, và việc tối ưu hóa đã vượt qua các bài kiểm tra tích hợp và ẩn trong mã trong vài tháng, tất cả đều không được chú ý.

Điều này đã xảy ra trong một chương trình tính toán tiền lương cho một số nhân viên bán hàng bán chạy nhất, ít nhất trong số đó có sức mạnh đáng sợ hơn nhiều so với toàn bộ đội ngũ lập trình viên năm người khiêm tốn. Một ngày cách đây vài tháng, vì những lý do ít quan trọng, lỗi đã xảy ra - và một vài trong số những người đó đã bị đổi một ngày hoa hồng béo. Họ chắc chắn không thích thú.

Tệ hơn nữa, họ đã mất niềm tin (đã rất ít) mà họ có trong chương trình không được thiết kế để lén lút trục trặc họ, và giả vờ - và thu được - một bản đánh giá mã đầy đủ, chi tiết với các trường hợp kiểm tra đã chạy và nhận xét theo các điều khoản của giáo dân (cộng với rất nhiều điều trị thảm đỏ trong những tuần tiếp theo).

Tôi có thể nói gì: về mặt tích cực, chúng tôi đã thoát khỏi rất nhiều khoản nợ kỹ thuật, và đã có thể viết lại và tái cấu trúc một vài mớ hỗn độn spaghetti được nghe lại từ sự phá hoại của COBOL trong thập niên 90. Chương trình chắc chắn chạy tốt hơn bây giờ, và có nhiều thông tin gỡ lỗi hơn để nhanh chóng chuyển sang số 0 khi mọi thứ trông có vẻ tanh. Tôi ước tính rằng chỉ một điều cuối cùng này sẽ tiết kiệm được một hoặc hai ngày công mỗi tháng trong tương lai gần.

Về mặt trừ, toàn bộ brouhaha đã tiêu tốn của công ty khoảng 200.000 euro tiền mặt - cộng với khuôn mặt, cộng với chắc chắn là một số quyền lực thương lượng (và, do đó, còn nhiều tiền hơn).

Anh chàng chịu trách nhiệm "tối ưu hóa" đã thay đổi công việc một năm trước, trước thảm họa, nhưng vẫn có người nói chuyện để kiện anh ta về những thiệt hại. Và nó đã không thành công với tiếng vang trên rằng đó là "lỗi của người cuối cùng" - nó trông giống như một sự sắp đặt để chúng ta giải quyết vấn đề, và cuối cùng, chúng ta vẫn ở trong nhà ổ chuột và một trong những nhóm đang lên kế hoạch bỏ việc.

Chín mươi chín lần trong số một trăm, "hack 86400" sẽ hoạt động hoàn hảo. (Ví dụ: trong PHP, strtotime()sẽ bỏ qua DST và báo cáo rằng giữa các ngày thứ Bảy cuối cùng của tháng Mười và thứ Hai tuần sau, chính xác là 2 * 24 * 60 * 60 giây đã trôi qua, ngay cả khi điều đó hoàn toàn không đúng ... và hai cái sai sẽ hạnh phúc làm cho một cái đúng).

Điều này, thưa quý vị và quý ông, là một ví dụ khi nó không xảy ra. Như với túi khí và dây an toàn, có lẽ bạn sẽ không bao giờ thực sự cần sự phức tạp (và dễ sử dụng) của DateTimehoặc Carbon. Nhưng ngày mà bạn có thể (hoặc ngày mà bạn sẽ phải chứng minh bạn nghĩ về điều này) sẽ đến như một tên trộm trong đêm. Được chuẩn bị.

79 hữu ích 2 bình luận chia sẻ