2

The situation/issue

I have an issue with DateInterval in PHP.

Given the following code

$interval = new DateInterval("PT6000S");
echo $interval->h; // 0
echo $interval->i; // 0
echo $interval->s; // 6000
echo $interval->format("%h:%i"); // 0:0

I want this to represent 1 hour and 40 minutes, not 6000 seconds.

The question

Is there a built-in way to normalize the DateInterval? A specific way of writing the duration string? Or is this something that must be done normally?

I can modify the way it's created and formated if anyone has a "work-around" to suggest.

I have done my research, but strangely enough I have not found anything helpful with this issue.

Noah Boegli
  • 750
  • 1
  • 7
  • 24

3 Answers3

3

The DateInterval class is a bit annoying like that. Strictly it's doing the right thing, since your interval spec string specified zero minutes and hours, but it's definitely not helpful that it doesn't roll over excess seconds into minutes and hours.

One option I've used before is to add the interval to a new DateTime object initialised to the Unix epoch, then format the resulting time instead. This would mean using the standard date format strings (so h rather than %h, etc)

echo (new DateTime('@0'))->add(new DateInterval("PT6000S"))->format('h:i');
// 01:40

See https://3v4l.org/OX6JF

iainn
  • 16,826
  • 9
  • 33
  • 40
  • This solution is limited to times up to 24 hours. If the number of seconds is greater, the results will be incorrect. Try https://3v4l.org/XLKfTL – jspit Apr 21 '22 at 10:25
0

The solution

Like every time I ask for help, I cannot stop trying and I generally find the answer right after. No exception this time.

I've found a user contributed note in the DateInterval documentation.

You have to create two DateTime (they can be any date and time), add your interval to the second and substract the first from the second. This will populate the DateInterval in a normalized way.

Here is the code the user wrote, wrapped in a handy-dandy function:

public function createDateInterval(string $duration): DateInterval
{
    $d1 = new DateTime();
    $d2 = new DateTime();
    $d2->add(new DateInterval($duration));
    return $d2->diff($d1);
}

(Note that this function can throw an exception if your $duration string is not correctly formatted.)

Noah Boegli
  • 750
  • 1
  • 7
  • 24
0

This solution uses DateTime with a fixed time. Why ?

DateTime also supports microseconds. There is therefore a certain probability that 2 calls to new Date() will not return the same time. Then we can get wrong results.

//360000 Sec = 100 hours = 4 Days + 4 hours
$di = new DateInterval("PT360000S");

$diNormalize = date_create('@0')->diff(date_create('@0')->add($di));

var_export($diNormalize);
/*
DateInterval::__set_state(array(
   'y' => 0,
   'm' => 0,
   'd' => 4,
   'h' => 4,
   'i' => 0,
   's' => 0,
   'f' => 0.0,
   'weekday' => 0,
   'weekday_behavior' => 0,
   'first_last_day_of' => 0,
   'invert' => 0,
   'days' => 4,
   'special_type' => 0,
   'special_amount' => 0,
   'have_weekday_relative' => 0,
   'have_special_relative' => 0,
)) 
*/

echo $diNormalize->format('%a days %h hours %i minutes');
//4 days 4 hours 0 minutes 

DateInterval has a special format method for the output. This should be used here and no detour via date, gmdate or DateTime should be taken.

jspit
  • 7,276
  • 1
  • 9
  • 17