Adding two DateTime objects in php [duplicate] - php

This question already has answers here:
How we can add two date intervals in PHP
(4 answers)
Closed 9 years ago.
I have two DateTime object.
$time1 = new DateTime('01:04:00');
$time2 = new DateTime('00:13:22');
Addition of this two will be : 01:17:22. How can I do it?

A "time of day" is not the same thing as a "duration of time". It doesn't makes sense to add together two time of day values - regardless of platform or language. Think about it - what does "11:00 PM" + "4:00 AM" equal? It's a nonsensical question.
You should be thinking about PHP's DateInterval class, not the DateTime class.
It should be noted that if you follow the examples on the dup posts of using strtotime it will work only when each individual input, and the final result, are all under 24 hours. Why? Because that's the maximum amount of time allowed in a standard day. That's the consequence of mixing "time of day" with "duration of time".
This should work for you:
function time_to_interval($time) {
$parts = explode(':',$time);
return new DateInterval('PT'.$parts[0].'H'.$parts[1].'M'.$parts[2].'S');
}
function add_intervals($a,$b) {
$zerodate = new DateTime('0000-01-01 00:00:00');
$dt = clone $zerodate;
$dt->add($a);
$dt->add($b);
return $zerodate->diff($dt);
}
function format_interval_hhmmss($interval){
$totalhours = $interval->h + ($interval->d * 24);
return $totalhours.$interval->format(':%I:%S');
}
$interval1 = time_to_interval('01:04:00');
$interval2 = time_to_interval('00:13:22');
$interval3 = add_intervals($interval1,$interval2);
echo format_interval_hhmmss($interval3);
Note that the choice of value for $zerodate isn't really all that important. It's just that some reference point is required, since PHP doesn't provide operations directly on DateInterval.
Also note that the the DateInterval::format function doesn't have a formatter to get you total number of hours inclusive of days, so if there's any chance the total could be 24 hours or more, then you have to format that part yourself, like I showed in the format_interval_hhmmss function.
Also note that my PHP skills are not all that great, so there may be a more efficient way to write these functions.

function addtime($time1,$time2)
{
$x = new DateTime($time1);
$y = new DateTime($time2);
$interval1 = $x->diff(new DateTime('00:00:00')) ;
$interval2 = $y->diff(new DateTime('00:00:00')) ;
$e = new DateTime('00:00');
$f = clone $e;
$e->add($interval1);
$e->add($interval2);
$total = $f->diff($e)->format("%H:%I:%S");
return $total;
}

The only built-in DateTime add/substract methods require using a DateInterval. e.g
$t1 = new DateTime('01:04:33');
$new = $t1->add(new DateInterval('PT13M22S'));
^^^^^^---13 minutes, 22 seconds
however, note that since DateTime works on DATES as well as times, you can't just slam together two times like this and expect to get reliable results. Consider what happens if you're doing the addition on an interval that happens to span a daylight savings border, or crosses over a day boundary, etc...

Related

Is there any date/time where this function could break?

I've not seen anything like this before. This is part of a function that returns the expected answer (a series of five dates)... sometimes. For example, it's been run at 6am, and the result is sometimes incorrect: one of the five dates, either at the first or last, can be missing. Other times, it's fine. Same code, run just a few hours later.
I know working with dates can be a lot more complicated than it first appears, but this has stumped me. I can only hope my inexperience with the DateTime objects is to blame.
$start = new \DateTime(date("Y-m-d", strtotime("-1 day")));
$end = new \DateTime(date("Y-m-d", strtotime("-5 days")));
$diff = $end->diff($start);
$interval = \DateInterval::createFromDateString('-1 day');
$period = new \DatePeriod($start, $interval, $diff->days);
foreach($period as $date) {
echo $date->format("Y-m-d"); // Sometimes first or last date will be missing
}
So for example, if the code is run between 2020-07-05 00:00:00 and 2020-07-05 23:59:59, it should return the last five dates:
2020-07-04
2020-07-03
2020-07-02
2020-07-01
2020-06-30
I've run the code with various date/times manually, and I cannot recreate the bug... and yet it happens once every few days in production.
This is just vanilla PHP, but it is being run as part of a Laravel project, should that factor into things. (The app timezone is set to "Europe/London".)
I'm not keen on how you're defining $start and $end. If I'm not mistaken, if the server clock happens to tick to the next second between the two variables being defined, then your interval will be 3 days, 23 hours, 59 minutes, 59 seconds - instead of exactly 4 days. This messes up your definition of $diff->days to be 3 instead of 4, leading to a missing date.
I would suggest a different approach here. Specifically, start with the current date, and subtract a day the number of times you want - since this appears to be hard-coded to 5.
$date = new DateTime();
$interval = new DateInterval("P1D");
for( $i=0; $i<5; $i++) {
$date->sub($interval);
echo $date->format("Y-m-d")."\n";
}
That $i<5 can, of course, be refactored to $i < DAYS for some appropriate constant definition, to avoid the "magic number" and allow for changing in future development.
With DateTime (and also strtotime) it is possible to process expressions like 'Today -3 Days'. Today is always 00:00 today. The calculation can be simplified as follows:
$days = 5;
for( $i=1; $i<=$days; $i++) {
echo date_create("today -$i days")->format('Y-m-d')."<br>\n";
}

PHP - Time based math

How exactly is this done? There's so many questions on stack-overflow about what I'm trying to do; However all of the solutions are to edit the MYSQL Query, and I need to do this from within PHP.
I read about the strtotime('-30 days') method on another question and tried it, but I can't get any results. Here's what I'm trying:
$current_date = date_create();
$current_date->format('U');
... mysql code ...
$transaction_date = date_create($affiliate['Date']);
$transaction_date->format('U');
if($transaction_date > ($current_date - strtotime('-30 days'))) {
} else if(($transaction_date < (($current_date) - (strtotime('-30 days'))))
&& ($transaction_date > (($current_date) - (strtotime('-60 days'))))) {
}
Effectively, I'm trying to sort all of the data in the database based on a date, and if the database entry was posted within the last 30 days, I want to perform a function, then I want to see if the database entry is older than 30 days, but not older than 60 days, and perform more actions.
This epoch math is really weird, you'd think that getting the epoch of the current time, the epoch of the data entry, and the epoch of 30 and 60 days ago would be good enough to do what I wanted, but for some reason it's not working, everything is returning as being less than 30 days old, even if I set the date in the database to last year.
No need to convert to unix timestamp, you can already compare DateTime objects:
$current_date = data_create();
$before_30_day_date = date_create('-30 day');
$before_60_day_date = date_create('-60 day');
$transaction_date = date_create($affiliate['Date']);
if ($transaction_date > $before_30_day_date) {
# transation date is between -30 day and future
} elseif ($transaction_date < $before_30_day_date && $transaction_date > $before_60_day_date) {
# transation date is between -60 day and -30 day
}
This creates (inefficiently, see my comment above) an object:
$current_date = date_create(date("Y-m-d H:i:s"));
From which you try to subtract an integer:
if($transaction_date > ($current_date - strtotime('-30 days'))) {
which is basically
if (object > (object - integer))
which makes no sense.
you're mixing the oldschool time() system, which deals purely with unix timestamps, and the newer DateTime object system, which deals with objects.
What you should have is
$current_date = date_create(); // "now"
$d30 = new DateInterval('P30D'); // 30 days interval
$transaction_date = date_create($affiliate['Date']);
if ($transaction_date > ($current_date->sub($d30)) { ... }
You might consider DatePeriod class, which in essence gives you the ability to deal with a seires of DateTime objects at specified intervals.
$current_date = new DateTime();
$negative_thirty_days = new DateInterval::createFromDateString('-30 day');
$date_periods = new DatePeriod($current_date, $negative_thrity_days, 3);
$thirty_days_ago = $date_periods[1];
$sixty_day_ago = $date_periods[2];
Here you would use $thirty_days_ago, $sixty_days_ago, etc. for your comparisons.
Just showing this as alternative to other options (which will work) as this is more scalable if you need to work with a larger number of interval periods.

Check if time elapsed since date is more than X days

I need to create a PHP script that pulls the timestamps of various stuff from a database (logs, messages, logins, etc) and removes them if they are older than X amount of days. I am poor at doing work with time and am a bit stumped on the best way to do this.
I realize I could separate the day/month/year in the string using explode() and compare these with a bunch of If statements, but would like to use a more efficient method. Something like the following would be the correct way to do this correct?
$dt = "2011-03-19 10:05:44";
//if $dt is older than 90 days
if((time()-(60*24*90)) > strtotime($dt))
{
}
Subtract (minutes*hours*days) from time() or are the numbers wrong?
You can use DateTime class for this. Example:
$dt = "2011-03-19 10:05:44";
$date = new DateTime($dt);
$now = new DateTime();
$diff = $now->diff($date);
if($diff->days > 90) {
echo 'its greater than 90 days';
}

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 - Exclude all non-business hours / days from time difference

I have a table which shows the time since a job was raised.
// These are unix epoch times...
$raised = 1360947684;
$now = 1361192598;
$difference = 244914;
$difference needs to exclude any time outside of business hours (ex, 9-5 and weekends).
How could I tackle this?
The thing you have to do are 3 in numbers.
You take your start date and calculate the rest time on this day (if it is a business day)
You take your end date and calulate the time on this day and
you take the days in between and multiply them with your business hours (just those, that are business days)
And with that you are done.
Find a little class attached, which does those things. Be aware that there is no error handling, time zone settings, daylight saving time, ...
input:
start date
end date
output:
difference time in seconds
adjustable constants:
Business hours
Days that are not business days
Very bad idea, but I had no choice because I'm on php 5.2
<?php
date_default_timezone_set('Asia/Seoul');
$start = 1611564957;
$end = 1611670000;
$res = 0;
for($i = $start; $i<$end; $i++){
$h = date("H", $i);
if($h >= 9 && $h < 18){
//echo date("Y-m-d H:i:s", $i) . "<br>";
$res = $res + 1;
}
}
echo $res;
Use DateTime.
Using UNIX time for this is slightly absurd, and you would have to literally remake DateTime.
Look up relative formats where you can specify the hour on the day, e.g.
$date = new DateTime($raised);
$business_start = new DateTime("09:00"); // 9am today
$business_end = new DateTime("17:00"); // 5pm today
The rest is for you to work out.
Oh, and instead of start/end, you could probably use DateInterval with a value of P8H ("period 8 hours")
The problem with using timestamps directly is that you are assigning a context to a counter of seconds. You have to work backwards from the times you want to exclude and work out their timestamps beforehand. You might want to try redesigning your storage of when a job is raised. Maybe set an expiry time for it instead?

Categories