Normalize DateInterval in PHP - php

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.

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

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.)

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.

Related

Diff between dates calculates incorrect

I have two dateTime fields and I am trying to get the difference between them. However my results are coming out less than they should. I have looked at examples and the code seems fine.
$entryTime = new \DateTime($journalEntry->entry_date_time->toTimeString());
$closeTime = new \DateTime($journalEntry->close_date_time->toTimeString());
$interval = $entryTime->diff($closeTime);
debug($journalEntry->entry_date_time);
debug($journalEntry->close_date_time);
echo $interval->format('%d days %h hours %m minutes');
I am getting 0 days 11 hours 0 minutes
/src/Controller/JournalEntriesController.php (line 145)
object(Cake\I18n\FrozenTime) {
'time' => '2020-09-05 07:49:04.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}
/src/Controller/JournalEntriesController.php (line 146)
object(Cake\I18n\FrozenTime) {
'time' => '2020-09-07 19:36:53.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}
If you want to diff date and time, then there's no reason to create new DateTime objects, especially not from a time-only string, which would result in diffing only the time.
Simply diff the instances that you already have, they extend from \DateTime and \DateTimeImmutable respectively:
$interval = $journalEntry->entry_date_time->diff($journalEntry->close_date_time);
echo $interval->format('%d days %h hours %i minutes');
and as mentioned in the comments, m is for months, the pattern character for minutes is i.
CakePHP's datetime objects also provide additional diffing functionality for obtaining specific units (diffInHours(), diffInMinutes(), etc) or human readable formats (diffForHumans()).
See also
Chronos Cookbook > Chronos > Generating Differences
Chronos API > \Cake\Chronos\Traits\DifferenceTrait
PHP Manual > Function Reference > Date and Time Related Extensions > Date/Time > DateInterval > DateInterval::format

PHP Time Since Function Bug

i am writing a time since function to return the time since a given mysql datetime. When taking the $oldtime from current time() it is returning a negative int when i need a positive int. I have written similar functions before in other languages but i have become blind to this problem, so any help would be much appreciated.
function timeSince($time){
$today = date("Y");
$oldtime = strtotime($time);
$time = time() - $oldtime;
$tokens = array (
3600 => 'h',
60 => 'm',
1 => 's'
);
if($time >= 86400){
}
}
echo timeSince('2016-02-25 14:35:00');
it could be much more convenient if you use PHP's DateTime and DateInterval classes and their methods:
function timeSince($datetime) {
$now = strtotime("now");
$then = strtotime($datetime);
$dt_now = new DateTime("#" . $now);
$dt_then = new DateTime("#" . $then);
//DateTime's diff method returns a DateInterval object which got a format method:
return $dt_now->diff($dt_then)->format('%a days, %h hours, %i minutes and %s seconds');
}
some test cases:
//my local date & time is around "2016-02-25 19:49:00" when testing
echo '<pre>';
echo timeSince('2016-02-25 19:30:00');
//0 days, 0 hours, 19 minutes and 11 seconds
echo PHP_EOL;
echo timeSince('2013-11-02 15:43:12');
//845 days, 4 hours, 4 minutes and 3 seconds
echo PHP_EOL;
echo timeSince('2017-01-31 00:22:45');
//340 days, 4 hours, 35 minutes and 30 seconds
echo PHP_EOL;
echo timeSince('1950-05-14 07:10:05');
//24028 days, 12 hours, 37 minutes and 10 seconds
echo PHP_EOL;
code partially based on this answer: https://stackoverflow.com/a/19680778/3391783
strtotime uses timezone in your PHP settings. Depending on timezone set, it might convert to the time that is yet to happen. For example, on my ukrainian server, strtotime('2016-02-25 14:35:00') converts to 1456403700, on a server in another timezone (US/Pacific) it converts to 1456439700.
Quote from PHP docs:
The function expects to be given a string containing an English date format and will try to parse that format into a Unix timestamp (the number of seconds since January 1 1970 00:00:00 UTC), relative to the timestamp given in now, or the current time if now is not supplied.
Each parameter of this function uses the default time zone unless a time zone is specified in that parameter. Be careful not to use different time zones in each parameter unless that is intended. See date_default_timezone_get() on the various ways to define the default time zone.
You can add UTC/GMT offset to your datetime (1st param), for example strtotime('2016-02-25 14:35:00 +0800') or ('2016-02-25 14:35:00 GMT+08:00') will convert to 1456382100
In your example, $oldtime must be smaller value than current time().
So, if you want to count time between larger value, simply reverse your equation:
This line:
$time = time() - $oldtime;
Becomes:
$time = $oldtime - time();

Calculate months between two dates using DateInterval without wrapping within a year

I am aware this topic is pretty exhausted, but obviously not quite enough!
$temp_d1 = new DateTime(date('Y-m-d', $fromTime)); // 2012-01-01
$temp_d2 = new DateTime(date('Y-m-d', $endTime)); // 2013-02-01
$interval = $temp_d2->diff($temp_d1);
$monthsAhead = $interval->format('%m'); // returns 1, but I am expecting 13
How do you calculate the number of months between two dates without wrapping within a 12 month scale?
I was confusing exactly what:
$monthsAhead = $interval->format('%m');
does.
Obviously, format('%m') is just formatting the month component of the DateInterval object, not necessarily 'give me the interval as a number of months'.
In my case, I was looking for/to do this:
$monthsAhead = $interval->m + ($interval->y * 12);
http://www.php.net/manual/en/class.dateinterval.php
Hope this helps other fools in the future!

PHP DateTime format not working properly

I use this code:
$today_date = new DateTime('now');
$final_date = new DateTime('2012-03-22 09:00');
$interval = $today_date->diff($final_date);
$time_left = $interval->format('%d:%H:%i:%s');
I want $time_left to have the format dd:hh:mm:ss format (d-> day, h-> hour, m-> minute, s-> seconds).
But when i used %d:%H:%i:%s as format, it sometimes displays single number, that is without 0 padding for minute. For example: 11:14:7:45. Expected result: 11:14:07:45
Any mistakes ?
More a comment: Don't confuse the format of DateInterval with format of DateTime: i is Minutes, numeric, e.g. 1, 3, 59. In case you run into a problem or you use a function you don't know in and out, please consult the manual first.
Per DateInterval, %i is "Minutes, numeric: 1, 3, 59". Try %I which uses leading zeroes.

php: datetime arithmetic

I'm a bit stuck with the DateInterval class of PHP. What I really want is the number of seconds elapsed between two DateTime stamps.
$t1 = new DateTime( "20100101T1200" );
$t2 = new DateTime( "20100101T1201" );
// number of seconds between t1 and t2 should be 60
echo "difference in seconds: ".$t1->diff($t2)->format("%s");
Yet all I get is zero. Is the DateInterval class not suited for arithmetic? How can I get the 'exact' number of seconds (or hours, or whatever) between two time stamps?
If you just want the seconds quickly you might aswell use
$diff = abs($t1->getTimestamp() - $t2->getTimestamp());
Your code returns 0, because the actual seconds difference is 0, the difference in your example is 1 minute (1 minute, 0 seconds). If you print the %i format, you will get 1, which is the correct diff of $t1 and $t2.

Categories