This comes from my previous question on getting the average time interval over a specified data set, [located here][1]. I'll post the entire function again:
function getATBData($siteID, $fromDate, $toDate)
{
global $pdo;
$ATBarray = array();
$maxATB;
$minATB;
$avgATB;
$totalATB=new DateTime("#0");
$totalEvents=0;
$timetable;
$query = "SELECT id, siteID, start_time, end_time FROM atb_log WHERE siteID=:siteID AND (start_time BETWEEN :fromDate AND :toDate) AND (end_time BETWEEN :fromDate AND :toDate)";
$stmt = $pdo->prepare($query);
$stmt->bindParam(":siteID", $siteID);
$stmt->bindParam(":fromDate", $fromDate);
$stmt->bindParam(":toDate", $toDate);
$stmt->execute();
foreach ($stmt as $row)
{
$timeDiff = date_diff(new DateTime($row['start_time']),new DateTime($row['end_time']), true); //force absolute
if(!isset($maxATB) OR dateIntervalInSeconds($timeDiff) > dateIntervalInSeconds($maxATB))
$maxATB = $timeDiff;
if(!isset($minATB) OR dateIntervalInSeconds($timeDiff) < dateIntervalInSeconds($minATB))
$minATB = $timeDiff;
$totalATB->add($timeDiff);
echo "added " . $timeDiff->format("%H:%I:%S") . " total is now: " . $totalATB->format("H:i:s") . "<br />";
$totalEvents++;
}
if($totalEvents!=0)
{
$avgATB = average_time($totalATB->format("H:i:s"),$totalEvents,0);
}
else
{
$avgATB=0;
$maxATB=new DateInterval('PT0S');
$minATB=new DateInterval('PT0S');
}
//$avgSeconds = new DateInterval("PT" . $avgATB . "S");
$ATBarray['max'] = $maxATB->format("%H:%I:%S");
$ATBarray['min'] = $minATB->format("%H:%I:%S");
$ATBarray['avg'] = $avgATB;
$ATBarray['total'] = $totalATB->format("H:i:s");
$ATBarray['events'] = $totalEvents;
return $ATBarray;
}
Given this function, I have added an output statement to try to debug why I was getting such a large time interval for my total time (when most of the values are a small number of seconds) and this is what it's outputting:
added 00:00:02 total is now: 01:00:02
added 00:00:00 total is now: 02:00:02
added 00:00:01 total is now: 03:00:03
added 00:00:01 total is now: 04:00:04
added 00:00:00 total is now: 05:00:04
added 00:00:02 total is now: 06:00:06
added 00:00:00 total is now: 07:00:06
and so on. So it seems like, despite the time to be added only being a couple seconds, it adds an hour every time. The call to add() on $timeDiff above is how I'm adding.
So the question is - is there a different way to call the add() function such that it will only add the seconds? Am I calling it incorrectly?
Hm, average difference in seconds, why that PHP swath of code if your database can give it to you:
SELECT
SEC_TO_TIME(MAX(TIME_TO_SEC(TIMEDIFF(end_time,start_time)))) AS max_timediff,
SEC_TO_TIME(MIN(TIME_TO_SEC(TIMEDIFF(end_time,start_time)))) AS min_timediff,
SEC_TO_TIME(AVG(TIME_TO_SEC(TIMEDIFF(end_time,start_time)))) AS avg_timediff,
SEC_TO_TIME(SUM(TIME_TO_SEC(TIMEDIFF(end_time,start_time)))) AS sum_timediff,
COUNT(id) as total_events
FROM atb_log
WHERE
siteID=:siteID
AND start_time > :fromDate
AND end_time < :toDate
Format those min/max/avg/sum of seconds as you like.
As I wrote in the answer to your other question, this is a DST (Daylight Savings Time) problem. In the U.S., DST has already begun; in Europe not.
Try this code:
timecheck("Europe/Amsterdam");
timecheck("America/Los_Angeles");
function timecheck($timezone) {
date_default_timezone_set($timezone);
$totalATB=new DateTime("#0");
$t1 = "2014-01-01 17:30:00";
$t2 = "2014-01-01 17:35:00";
$dt1 = new DateTime($t1);
$dt2 = new DateTime($t2);
$timeDiff = date_diff($dt1, $dt2, true);
printf("[%s] Starting with with: %s\n", $timezone, $totalATB->format("H:i:s"));
$totalATB->add($timeDiff);
printf("[%s] added %s, total is now: %s\n", $timezone, $timeDiff->format("%H:%I:%S"), $totalATB->format("H:i:s"));
}
The output:
[Europe/Amsterdam] Starting with with: 00:00:00
[Europe/Amsterdam] added 00:05:00, total is now: 00:05:00
[America/Los_Angeles] Starting with with: 00:00:00
[America/Los_Angeles] added 00:05:00, total is now: 01:05:00
Related
I am coding to calculate a duration between an empty timestamp date and an other, non-empty, Please help me.
I would like to calculate this duration to set a confirmation's code expires when a participant in the forum comes back too late or doesn't come back over 2 days as a maximum delay
This code works fine except when the come back date is empty:
$date_participation= strtotime($row['date_participation']);
$date_come_back= strtotime($row['date_come_back']);
$expiration_delay = 2;
$days = $expiration_delay * 86400;
echo "<br> duration in seconds is: " . $date_come_back-
$date_participation;
if (($date_come_back- $date_participation) >= $days){
$sql_set_expired = "UPDATE `winners` SET `confirmation_status` = 'expired' WHERE id_recharge_winner ='$i'";
$set_expired_result = mysqli_query($conn, $sql_set_expired);
}
the `$i` is the participant's row in table winners
This is how I result my problem whiout worry if there is an Empty date:
I maked my one fuction
Difference_between_timestampdate()
than I specify one day like an expiration duration. It works fine, Thanks to those who helped me.
function Difference_between_timestampdate($first_date, $last_date){
$first_date = strtotime($first_date );
$last_date = strtotime($last_date);
$days = $expiration_delay * 82800;
if(empty($last_date)){
$duration = time() - $first_date;
}else{
$duration = $last_date - $first_date;
}
return $duration;
}
$date_participation = $row['date_participation'];
$date_come_back = $row['date_come_back'];
$expiration_delay = 1; //One day
if ((Difference_between_timestampdate($date_participation, $date_come_back)/82800 )
>= $expiration_delay){echo 'Expired duration';}else{echo 'Valid duration';}
I'm working on a PHP page that calculates the percentage of completion of a project. For example, if you had a start date of January 1st, 2015, and an end date of March 3rd, 2015, and today was February 2nd, 2015, the project would be estimated to be about 50% done. So far I've attempted using the DateTime class and the date_diff function, but I couldn't divide the two, so I'm back at square one. Obviously I need to take Daylight Saving and leap years into account, so that adds an additional layer of complexity to the matter. Any ideas? Here the current block.
try {
$dbh = new PDO('mysql:host=localhost; dbname=jkaufman_hartmanbaldwin', $username, $password, array(
PDO::MYSQL_ATTR_SSL_KEY => '../php_include/codekaufman_com.key',
PDO::MYSQL_ATTR_SSL_CERT => '../php_include/codekaufman_com.crt',
PDO::MYSQL_ATTR_SSL_CA => '../php_include/codekaufman_com.ca_bundle'
));
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$projectName = $_GET['project'];
$sth = $dbh->prepare('SELECT start, end FROM projects WHERE name = ?');
$sth->execute([$projectName]);
if($sth->rowCount()) {
$row = $sth->fetchAll(PDO::FETCH_ASSOC);
date_default_timezone_set('America/Los_Angeles');
$date = strtotime($row[0]['start']);
$start = date('m/d/Y', $date);
echo $start;
$date = strtotime($row[0]['end']);
$end = date('m/d/Y', $date);
echo " " . $end;
$today = date('m/d/y');
echo $end - $start;
}
} catch(PDOException $e) {
echo $e->getMessage();
}
With reference to How to Minus two dates in php:
$start = new DateTime($row[0]['start']);
$end = new DateTime($row[0]['end']);
$today = new DateTime();
$total = $start->diff($end);
$current = $start->diff($today);
$completion = $current->days / $total->days;
MySQL has some pretty easy to use functions for this sort of thing, you can just gather the info you need in your query:
SELECT start, end, DATEDIFF(end, start) as total_days, DATEDIFF(end, NOW()) as days_remaining
FROM projects WHERE name = ?
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_datediff
From there you just need to divide days_remaining by total_days to get the percentage. Datediff should take leap years and DST into account.
You can use TIMEDIFF in place of DATEDIFF if you need to be more precise, just make sure to convert the sql timestamps to integers with strtotime.
You may also need to set the timezone:
SET time_zone = 'America/Los_Angeles';
The formula is:
percentage = (date - start) / (end - start) * 100
As a PHP function:
function date_progress($start, $end, $date = null) {
$date = $date ?: time();
return (($date - $start) / ($end - $start)) * 100;
}
Example:
$start = strtotime("January 1st 2015");
$end = strtotime("March 3rd 2015");
$date = strtotime("February 2nd 2015");
$percent = date_progress($start, $end, $date);
// "You are 52.46% there!"
echo 'You are ', round($percent, 2), '% there!';
Get your SQL output in the right format (YYYY-MM-DD) and then shove it into the code below:
<?php
$startDate = date_create('2015-01-01');
$endDate = date_create('2015-01-30');
$currentDate = date_create('2015-01-08');
$totalTime = date_diff($endDate, $startDate);
$elapsedTime = date_diff($currentDate, $startDate);
$totalTimeDays = $totalTime->format("%d");
$elapsedTimeDays = $elapsedTime->format("%d");
echo "Total project time = " . $totalTimeDays . "<br/>";
echo "Elapsed project time = " . $elapsedTimeDays . "<br/>";
echo "Percent of project complete = " . ($elapsedTimeDays / $totalTimeDays) * 100.0;
?>
$start = new DateTime("<YOUR START DATE>"); // example input "2014/06/30"
$end= new DateTime("<YOUR END DATE>");
$now = new DateTime();
$intervalOBJ = $start->diff($end);
$totalDaysOfProject = $intervalOBJ->format('%a');
$intervalOBJ_2 = $now->diff($end);
$daysRemaining = $intervalOBJ_2->format('%a');
$completedPercentage = round(($daysRemaining/$totalDaysOfProject)*100);
echo $completedPercentage . "% of this project has been completed!";
Description: This calculates the percentage of days remaining. interval = $start to $end. Calculated percentage is in relation to $now.
I'm using the following code to calculate the average time between a start time and an end time for a number of events (from a database):
function getATBData($siteID, $fromDate, $toDate)
{
global $pdo;
$ATBarray = array();
$maxATB;
$minATB;
$avgATB;
$totalATB=new DateTime("#0");
$totalEvents=0;
$timetable;
$query = "SELECT id, siteID, start_time, end_time FROM atb_log WHERE siteID=:siteID AND (start_time BETWEEN :fromDate AND :toDate) AND (end_time BETWEEN :fromDate AND :toDate)";
$stmt = $pdo->prepare($query);
$stmt->bindParam(":siteID", $siteID);
$stmt->bindParam(":fromDate", $fromDate);
$stmt->bindParam(":toDate", $toDate);
$stmt->execute();
foreach ($stmt as $row)
{
$timeDiff = date_diff(new DateTime($row['start_time']),new DateTime($row['end_time']), true); //force absolute
if(!isset($maxATB) OR dateIntervalInSeconds($timeDiff) > dateIntervalInSeconds($maxATB))
$maxATB = $timeDiff;
if(!isset($minATB) OR dateIntervalInSeconds($timeDiff) < dateIntervalInSeconds($minATB))
$minATB = $timeDiff;
$totalATB->add($timeDiff);
$totalEvents++;
}
if($totalEvents!=0)
{
//$avgATB=round($totalATB->getTimestamp() / $totalEvents);
$avgATB = average_time($totalATB->format("H:i:s"),$totalEvents,0);
}
else
{
$avgATB=0;
$maxATB=new DateInterval('PT0S');
$minATB=new DateInterval('PT0S');
}
$avgSeconds = new DateInterval("PT" . $avgATB . "S");
$ATBarray['max'] = $maxATB->format("%H:%I:%S");
$ATBarray['min'] = $minATB->format("%H:%I:%S");
$ATBarray['avg'] = gmdate("H:i:s",$avgATB); //$avgSeconds->format("%H:%i:%s");
$ATBarray['total'] = $totalATB->format("H:i:s");
$ATBarray['events'] = $totalEvents;
return $ATBarray;
}
Unfortunately, I'm getting extremely high averages. As an example, I'm finding that the max time is 3 seconds, the min time is 0 seconds, but the average time is 1 hour and 1 second. This is obviously impossible, so is there something wrong with how I'm calculating total and/or average? The total is rather high too, but I haven't been able to manually add up this data so I'm not sure if that is also incorrect.
This is most probably a DST problem. Is your code running in a U.S. timezone? Rerember that when you're using new DateTime("#0"), this represents 1970-01-01 00:00:00 (non-DST); in the U.S., DST has started now, so the absolute value of your DateTime objects created from the database are one hour off.
EDIT:
I've changed the way I'm calculating averages. I've added the new function all above, and here is the averaging function (found here):
function average_time($total, $count, $rounding = 0) //from SO
{
$total = explode(":", strval($total));
if (count($total) !== 3) return false;
$sum = $total[0]*60*60 + $total[1]*60 + $total[2];
$average = $sum/(float)$count;
$hours = floor($average/3600);
$minutes = floor(fmod($average,3600)/60);
$seconds = number_format(fmod(fmod($average,3600),60),(int)$rounding);
if($hours<10) $hours = "0"+$hours; //add leading 0
if($minutes<10) $minutes = "0"+$minutes;
if($seconds<10) $seconds = "0"+$seconds;
return $hours.":".$minutes.":".$seconds;
}
So now my average numbers have changed and I'm trying to check the totals to see if they're correct. But I've posted that as another question, located here.
I have used numorous date functions in the past, but I need help on a specific function...
Is there a function where I can assign two unknown dates and the system knows where I am within these two dates? Let me explain:
Our monthly salaries run from the 23rd of each month to the 22nd of the next month. Because earnings work on an hourly basis, my boss wants to know anywhere within the month where the accumulative salaries are. For instance the salary period started on the 23rd of September 2012 and we are now on the 29th, I want my query to be able to know where we are in the current salary period.
As you know, months move on, thus my script must automatically know in which period and where whithin that period we are now.
I can do the queries surrounding this, I just need to know the date function (s) to use to assign this array...
Any help will be appreciated - Thanx
You can use PHP's DateTime classes to do this quite easily:-
$periodStart = new DateTime('23rd September');
$now = new DateTime();
$interval = $now->diff($periodStart);
echo "We are {$interval->d} days into the payment period";
Output:
We are 6 days into the payment period.
I prefer to extend the DateTime class for this kind of thing, so everything is in the same place:-
class MyDateTime extends DateTime
{
public function elapsedDays(DateTime $since = null)
{
if ($since === null) {
$since = new DateTime();
}
$interval = $since->diff($this);
return (int) $interval->d;
}
}
$periodStart = new MyDateTime('23rd September');
echo "We are {$periodStart->elapsedDays()} days into the payment period";
Gives the same output.
You can then create periods and intervals and iterate over it to aggregate the sum like:
$datePeriodStart = new DateTime('23rd September');
$datePeriodEnd = clone $datePeriodStart;
$datePeriodEnd->add(new DateInterval('P1M'));
$dateToday = new DateTime();
$interval1 = $dateToday->diff($datePeriodStart);
$interval2 = $dateToday->diff($datePeriodEnd);
echo "We are {$interval1->d} day(s) into the payment period, {$interval2->d} day(s) left.\n";
$period = new DatePeriod($datePeriodStart, new DateInterval('P1D'), $dateToday);
$days = new IteratorIterator($period);
$totalSalary = 0;
$totalDays = 0;
foreach($days as $day)
{
$salary = get_salary_for_day($day);
$totalSalary += $salary;
$totalDays++;
printf("#%d: %s %s\n", $totalDays, $day->format('Y-m-d'), number_format($salary));
}
printf("Total Salary for %d day(s): %s\n", $totalDays, number_format($totalSalary));
Example output:
We are 6 day(s) into the payment period, 23 day(s) left.
#1: 2012-09-23 12,500
#2: 2012-09-24 12,500
#3: 2012-09-25 12,500
#4: 2012-09-26 12,500
#5: 2012-09-27 12,500
#6: 2012-09-28 12,500
#7: 2012-09-29 12,500
Total Salary for 7 day(s): 87,500
You could simply use a TIMESTAMP value (seconds since epoch, also called a "unix timestamp") and just test to see if the current date in unix timestamp is in between the first and last unix timestamp dates.
Essentially, that way you are merely converting the date into a big integer (number of seconds since 1969/70), and arithmetic and testing functions become a LOT easier to handle.
Get FROM and TO date:
$to = new DateTime();
$from = new DateTime($to->format('Y-m-23'));
if ($to->format('j') < 23) {
$from->modify('-1 month');
}
Var_dump:
var_dump($from->format('Y-m-d')); # 2012-09-23
var_dump($to->format('Y-m-d')); # 2012-09-29
SQL
$sql = "
SELECT ...
FROM ...
WHERE some_time BETWEEN '" . $from->format('Y-m-d') . "' AND '" . $to->format('Y-m-d') ."'
";
(fixed and working fine now, but if anyone still wants to refactor, leave a note)
This is a stripped down version of a function I have which iterates over a date range and assigns a unique integer to each...
When working with large datasets, running this several times over different date ranges, I'm getting a fatal error, assigning too much memory to the script and it dies in this loop...
Fatal error: Allowed memory size of 268435456 bytes exhausted
fixed, was an issue with the iteration not taking into account the potential daylight-savings-time
So, I was wondering if someone could recommend a more optimal way of generating this list of months/ints...
It must allow me to start the Int at whatever number I like and
<?php
// updated: 2010.11.04 with Qwerty's recommendations
// for fixing daylight savings time issue
function monthIterate($monthInt, $startDate, $stopDate) {
$epoch = $startMain = strtotime($startDate);
$stopMain = strtotime($stopDate);
while ($epoch <= $stopMain) {
// get the start/stop dates for "this month"
$start = date("Y-m-01", $epoch);
$stop = date("Y-m-t", $epoch);
// uniqueID for the month
$monthKey = "Month#-".str_pad($monthInt, 3, "0", STR_PAD_LEFT);
$months[$monthKey] = compact('start', 'stop');
// move forward in loop, +1 day should get us to the next month
$epoch = strtotime($stop);
$currentMonth = $nextMonth = date('m', $epoch);
while ($currentMonth == $nextMonth) {
$epoch = $epoch + 86400;
$nextMonth = date('m', $epoch);
}
$monthInt++;
}
return $months;
}
?>
Looks like your function goes in endless loop because of extra hour in light saving time.
echo date('Y-m-d H:i:s', strtotime('2010-10-31') + 60*60*2); // adding 2 hours
echo date('Y-m-d H:i:s', strtotime('2010-10-31') + 60*60*3); // adding 3 hours
both will output 2010-10-31 02:00:00. Thus strtotime('2010-10-31') + 86400 is actually 2010-10-31 23:00:00, but not next day.
So you should add more than 86400 seconds to be sure you switched to next day :-)
I think your array index gets too crazy.
ie :
[Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-Month#-123] => Array
(
[start] => 2011-12-01
[stop] => 2011-12-31
)
I would move your "$monthInt = "Month#-".str_pad($monthInt, 3, "0", STR_PAD_LEFT);" lines outside your loop.
<?php
function monthIterate($monthInt, $startDate, $stopDate) {
$monthInt = "Month#-".str_pad($monthInt, 3, "0", STR_PAD_LEFT); <--
$epoch = $startMain = strtotime($startDate);
$stopMain = strtotime($stopDate);
while ($epoch <= $stopMain) {
// get the start/stop dates for "this month"
$start = date("Y-m-01", $epoch);
$stop = date("Y-m-t", $epoch);
// uniqueID for the month
$months[$monthInt] = compact('start', 'stop');
// move forward in loop, +1 day should get us to the next month
$epoch = strtotime($stop) + 86400;
$monthInt++;
}
return $months;
}?>