7

I am using the code below to determine employee service in a year:

$datePay1 = new DateTime($date1);
$datePay2 = new DateTime($date2);
$interval = $datePay1->diff($datePay2);
$vYears = $interval->y;
$vMonths = $interval->m;
$vDays = $interval->d;
$service = $vYears." years, ".$vMonths." months, ".$vDays." days"; 

Case 1:

$date1 = '2016-03-01';
$date2 = '2017-03-01';

Service = 0 years, 11 months, 30 days

Case 2:

$date1 = '2017-03-01';
$date2 = '2018-03-01';

Service = 1 years, 0 months, 0 days

Case 1 seems to be incorrect. Why is this? Is it because 2016 was a leap year? The server runs PHP v5.6.

PeterC
  • 79
  • 1
  • 3
    I just tried both cases and the diff is actually correct. https://imgur.com/a/bnTD7yz – GiamPy Aug 23 '18 at 09:31
  • 5
    I've tried a couple of times and I can't seem to reproduce the results. I get `1 years, 0 months, 0 days` for Case 1. – Nigel Ren Aug 23 '18 at 09:31
  • here's an eval snippet for reference, it works on 5.6 https://3v4l.org/lvdui – Kevin Aug 23 '18 at 09:32
  • 1
    @Ghost If I change the dates on your snippet I do get the results that Peter showed: https://3v4l.org/N9IWG – Dirk Scholten Aug 23 '18 at 09:33
  • here's another eval snipped that _does_ reproduce the problem: https://3v4l.org/pVm02 (but only for 1st of march, all other dates are fine, even 2nd of march...) – Jeff Aug 23 '18 at 09:33
  • 2
    I get the same issue as OP with PHP 7.2.1. Setting the time to anything else than midnight fixes it. – AymDev Aug 23 '18 at 09:36
  • Check this question, @PeterC, might help you: https://stackoverflow.com/questions/29317183/age-calculation-leap-year-issue-in-php – Rafael Aug 23 '18 at 09:38
  • https://carbon.nesbot.com/ easily solves these issues. Check it out and do not reinvent the wheel! – GiamPy Aug 23 '18 at 09:40
  • 2
    In 2016 there where also leap-_seconds_ added to the time. This _might_ be related.. https://en.wikipedia.org/wiki/Leap_second – Jeff Aug 23 '18 at 09:42
  • Many thanks for your prompt replies. I have since changed the code to use strtotime which is working okay. I am interested why diff() seems to work "sometimes". – PeterC Aug 23 '18 at 09:43
  • to add to what @Jeff mentioned it does seem to be the the leap year/seconds and php version (possibly). There is this comment on [php.net](http://php.net/manual/en/datetime.diff.php#122270) and if I change Case1 start date to '2016-02-29' (calculated as 366 days instead of 365) then the eval examples for 5.6 show one year. 7.x shows one year for 365 days as well as 366 days. – David 'the bald ginger' Aug 23 '18 at 09:59

2 Answers2

1

This is related to your timezone settings and leap year - in 2016 February had 29 days. Intervals are calculated using UTC timezone, and depending on your timezone DateTime may evaluate to different time that you may expecting. For example if you're using Europe/Warsaw timezone, DateTime('2016-03-01') (which is the same as DateTime('2016-03-01 00:00:00')) will evaluate to to 2016-02-29 23:00:00.

Sample:

date_default_timezone_set('Europe/Warsaw');
$date1 = '2016-03-01';
$date2 = '2017-03-01';
$datePay1 = new DateTime($date1);
$datePay2 = new DateTime($date2);

echo 'datePay1: ' . $datePay1->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s')
    . "\ndatePay2: " . $datePay2->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s');

Result:

datePay1: 2016-02-29 23:00:00
datePay2: 2017-02-28 23:00:00

https://3v4l.org/mKNhX

As you can see you're actually comparing 2016-02-29 23:00:00 to 2017-02-28 23:00:00. And since there is one day difference between February 29 and February 28, it is not considered as full year difference.


To make it more consistent and timezone independent, specify UTC as timezone explicitly:

date_default_timezone_set('Europe/Warsaw');
$date1 = '2016-03-01';
$date2 = '2017-03-01';
$datePay1 = new DateTime($date1, new DateTimeZone('UTC'));
$datePay2 = new DateTime($date2, new DateTimeZone('UTC'));
$interval = $datePay1->diff($datePay2);
$vYears = $interval->y;
$vMonths = $interval->m;
$vDays = $interval->d;
echo  "$vYears years, $vMonths months, $vDays days";

Result:

1 years, 0 months, 0 days

https://3v4l.org/lFnkL

rob006
  • 21,383
  • 5
  • 53
  • 74
0

try this:

        $tz = date_default_timezone_get();
        $usa = 'America/Chicago'; // America/*
        date_default_timezone_set($usa);

        //Case 1:
        $date1 = '2016-03-01';
        $date2 = '2017-03-01';

        //Case 2:
        //$date1 = '2017-03-01';
        //$date2 = '2018-03-01';

        $datePay1 = new \DateTime($date1);
        $datePay2 = new \DateTime($date2);
        $interval = $datePay1->diff($datePay2);
        $vYears = $interval->y;
        $vMonths = $interval->m;
        $vDays = $interval->d;
        $service = $vYears." years, ".$vMonths." months, ".$vDays." days";
        print_r($interval); // interval: + 1y

        date_default_timezone_set($tz);
TsV
  • 629
  • 4
  • 7
  • 3
    Please add some comments and an explanation how your code would solve the problem. – Jeff Aug 23 '18 at 09:49
  • @Jeff http://php.net/manual/ru/datetime.diff.php#106464 When using datediff make sure your time zone is correct – TsV Aug 23 '18 at 09:57