PHP- Carbon Datetime DiffInSeconds and How to Group By Hour - php

I'm calculating difference between 2 datetimes in seconds but I want to group them by hours. It should be like that.
This is how I'm getting difference between 2 dates.
$date_diff = $first->created_at->diffInSeconds($second->created_at);
And For example first time is 09:47:20 and next one is 11:47:50 I need the group them by hour like this.
In 09:00 = 12.40 Minutes
In 10:00 = 60.00 Minutes
In 11:00 = 47.50 Minutes
How can I do that ? And what you'll suggest for this ?
Thanks

You can check out the code below.
Keep in mind that it works as expected if the start time <= the end time.
Also, there's room for improvement, like extracting logic to separate functions or adding trailing/leading zeros where you'd need them, but it should get you started.
$start = '09:47:20';
$end = '11:47:50';
$startTime = Carbon::createFromFormat('H:i:s', $start);
$endTime = Carbon::createFromFormat('H:i:s', $end);
$startHour = $startTime->hour; // 9
$endHour = $endTime->hour; // 11
$hourRange = range($startHour, $endHour); // array(9, 10, 11)
if (count($hourRange) === 1) {
// start and end time share the same hour
$hourRange = array($startHour, $endHour);
}
$lastIndex = count($hourRange) - 1;
$diffGrouped = array();
foreach ($hourRange as $index => $hour) {
if ($index === 0) {
// start hour has the next full hour - calculate the difference
$nextFullHour = $startHour + 1;
$nextFullHourTime = Carbon::createFromFormat('H', $nextFullHour);
$minutes = $startTime->diff($nextFullHourTime)->format('%i.%s');
} else if ($index === $lastIndex) {
// end hour is its own "previous full hour"
$previousFullHourTime = Carbon::createFromFormat('H', $endHour);
$minutes = $endTime->diff($previousFullHourTime)->format('%i.%s');
} else {
// everything between the start and the end hour
$minutes = '60.00';
}
$diffGrouped[] = array(
'hour' => $hour,
'minutes' => $minutes
);
}
dd($diffGrouped);
/*
array:3 [
0 => array:2 [
"hour" => 9
"minutes" => "12.40"
]
1 => array:2 [
"hour" => 10
"minutes" => "60.00"
]
2 => array:2 [
"hour" => 11
"minutes" => "47.50"
]
]
*/

Related

Make 5 minute interval

I'm trying to separate the dates for example:
$arr=array(
"2018-06-27 20:30:20",
"2018-06-27 20:31:20",
"2018-06-27 20:37:20",
"2018-06-27 20:45:20",
"2018-06-27 20:48:20"
);
As you can see there are minutes with difference only of 1 minute or even seconds.
What I'm trying to accomplish is to force the dates to be 5 mins interval.
example output
2018-06-27 20:30:00
2018-06-27 20:35:00
2018-06-27 20:40:00
2018-06-27 20:45:00
2018-06-27 20:50:00
Here's my code
function roundToNearestMinuteInterval($time)
{
$time = (round(strtotime($time) / 300)) * 300;
return date('Y-m-d H:i:s', $time);
}
$temp="";
$wave=1;
foreach($arr as $a) {
if(empty($temp)) {
$temp= roundToNearestMinuteInterval($a);
}
$date= roundToNearestMinuteInterval($a);
if($temp==$date && $wave!=1){
$new=date('Y-m-d H:i:s',strtotime('+3 minutes',strtotime($a)));
$date= roundToNearestMinuteInterval($date);
$temp= $date;
}
$wave++;
echo $date."<br/>";
}
If you want to have an output array with all the 5 minute (or other interval) times between the earliest and latest times in the input array, you can just iterate between them, adding the interval in each loop:
$arr=array("2018-06-27 20:30:20","2018-06-27 20:31:20","2018-06-27 20:37:20","2018-06-27 20:45:20","2018-06-27 20:48:20");
function roundToNearestMinuteInterval($time, $interval) {
$timestamp = strtotime($time);
$rounded = round($timestamp / ($interval * 60), 0) * $interval * 60;
return $rounded;
}
$interval = 5; // minutes
$start = roundToNearestMinuteInterval(min($arr), $interval);
$end = roundToNearestMinuteInterval(max($arr), $interval);
for (; $start <= $end; $start += $interval * 60) {
$results[] = date('Y-m-d H:i:s', $start);
}
print_r($results);
Output:
Array
(
[0] => 2018-06-27 20:30:00
[1] => 2018-06-27 20:35:00
[2] => 2018-06-27 20:40:00
[3] => 2018-06-27 20:45:00
[4] => 2018-06-27 20:50:00
)
Demo on 3v4l.org
Solution with a DateTime extension dt (https://github.com/jspit-de/dt) returns an array with the date as a key. The value supplied is the number of rounded values ​​from the input array. The algorithm can be implemented even without class extension with a few more commands.
$inputArr = array(
"2018-06-27 20:30:20",
"2018-06-27 20:31:20",
"2018-06-27 20:37:20",
"2018-06-27 20:45:20",
"2018-06-27 20:48:20"
);
$interval = "5 Minutes";
//create basis
$resArr = [];
$dt = dt::create(min($inputArr))->round($interval); //start
$endDate = dt::create(max($inputArr))->round($interval);
for(;$dt <= $endDate; $dt->modify($interval)){
$key = $dt->format("Y-m-d H:i:s");
$resArr[$key] = 0;
}
foreach($inputArr as $strDate){
$key = $dt::create($strDate)
->round($interval)
->format("Y-m-d H:i:s");
$resArr[$key]++;
}
The result $resArr
array (
'2018-06-27 20:30:00' => 2,
'2018-06-27 20:35:00' => 1,
'2018-06-27 20:40:00' => 0,
'2018-06-27 20:45:00' => 1,
'2018-06-27 20:50:00' => 1,
)

PHP: 3 time periods with different pricing

It's all about booking...
I've got 3 time periods:
Period 3 from 01.01.2014 to 29.04.2014 - Price 3 (per day)
Period 2 from 30.04.2014 to 14.06.2014 - Price 2 (per day)
Period 1 from 15.06.2014 to 21.09.2014 - Price 1 (per day)
Period 4 from 22.09.2014 to 29.04.2015 - Price 3 (per day)
I've already made a php calculation that calculates the booked days and pricing for each period. But I can't figure it out how to calculate between two periods.
For example:
Somebody books from 26.01.2014 to 25.04.2014 = 89 days * Price 1
But it gets really hard when somebody books from period 3 to period 1 for... I tried to separate the calculations:
if ($check_in >= '2013-04-30' && $check_out <= '2014-09-21')
{
//Days and price calcs here
// contains Period 2 and Period 1
}
But it doesn't work well...
Do you have any ideas how to make the whole calculation to work perfectly?
I missed something really important.
Here is the structure:
Period 1
if($numberDays == 1)
{
$price = $price1_period1
}
if($numberDays >= 2 && $numberDays <= 3)
{
$price = $price2_period1 * $numberDays;
}
if($numberDays >= 4 && $numberDays <= 6)
{
$price = $price3_period1 * $numberDays;
}
if($numberDays >= 7 && $numberDays <= 14)
{
$price = $price4_period1 * $numberDays;
}
if($numberDays >= 15 && $numberDays <= 29)
{
$price = $price5_period1 * $numberDays;
}
if($numberDays >= 30)
{
$price = $price6_period1 * $numberDays;
}
It's the same for the other periods. Ex.: for period 2 the price for 6 days is $price3_period2.
You could generate a price for each day. Then loop from start date to end date and sum the dayprice to get the total price:
<?php
$oneDay = 24*3600;
$configs = array(
array(
'startTime' => strtotime('2014-01-01'),
'endTime' => strtotime('2014-04-29'),
'price' => 10
),
array(
'startTime' => strtotime('2014-04-30'),
'endTime' => strtotime('2014-06-14'),
'price' => 20
),
array(
'startTime' => strtotime('2014-06-15'),
'endTime' => strtotime('2014-09-21'),
'price' => 30
),
array(
'startTime' => strtotime('2014-09-22'),
'endTime' => strtotime('2015-04-29'),
'price' => 40
),
);
$prices = array();
foreach ($configs as $config)
{
$time1 = $config['startTime'];
$time2 = $config['endTime'];
$price = $config['price'];
while ($time1 <= $time2)
{
$prices[date('Y-m-d', $time1)] = $price;
$time1 += $oneDay;
}
}
/**
* #param $checkIn in format YYYY-mm-dd
* #param $checkOut in format YYYY-mm-dd
*/
function getTotalPrice($checkIn, $checkOut, $prices)
{
$time1 = strtotime($checkIn);
$time2 = strtotime($checkOut);
$price = 0;
while ($time1 <= $time2)
{
$time1 += 24 * 3600;
$price += $prices[date('Y-m-d', $time1)];
}
return $price;
}
echo getTotalPrice('2014-01-04', '2014-01-09', $prices);
First things first, I assume $check_in and $check_out are strings that you get from some form, then you are comparing them with another string, any of them are dates.
What you can do is convert both $check_in and $check_out to Datetime and then do the comparison, example:
// Check In Date
$check_in_date = new Datetime($check_in);
$date_compare_in = new Datetime('2013-04-30');
$diff_in = $check_in_date->diff($date_compare_in);
// Check Out Date
$check_out_date = new Datetime($check_out);
$date_compare_out = new Datetime('2014-09-21');
$diff_out = $check_out_date->diff($date_compare_out);
Now $diff_in is a DateInterval object that you can check for the quantity of days, example, if the hours are greater than 0, the $check_in was later than the compare date, if is less than 0 the $check_in was before.
if($diff_in->h >= 0 and $diff_out->h <= 0){
// We are within this date range.
}
a DateInterval Object has the following structure:
DateInterval Object
(
[y] => 0
[m] => 0
[d] => 0
[h] => 0
[i] => 0
[s] => 0
[invert] => 0
[days] => 0
)

Days, hours, minutes since timestamp

So I have this function, which displays how long it's been since a timestamp.
Right now if it's been let's say 60 minutes, it will go over and show "1 hour ago". If it's been 24 hours it goes over to "1 day ago" and so on. How could I do so that if it's been for example 1 hour, show minutes also?
Like
Event occured 58 minutes ago
3 minutes later
Event occured 1 hour 1 minute ago
26 hours later
Event occured 1 day 2 hour ago
function humanTiming($time)
{
$time = time() - $time; // to get the time since that moment
$tokens = array (
31536000 => 'year',
2592000 => 'month',
604800 => 'week',
86400 => 'day',
3600 => 'hour',
60 => 'minute',
1 => 'second'
);
foreach ($tokens as $unit => $text) {
if ($time < $unit) continue;
$numberOfUnits = floor($time / $unit);
return $numberOfUnits.$text.' ago';
}
}
You need to modify your function like this:
function humanTiming($time)
{
$time = time() - $time; // to get the time since that moment
$tokens = array (
31536000 => 'year',
2592000 => 'month',
604800 => 'week',
86400 => 'day',
3600 => 'hour',
60 => 'minute',
1 => 'second'
);
$result = '';
$counter = 1;
foreach ($tokens as $unit => $text) {
if ($time < $unit) continue;
if ($counter > 2) break;
$numberOfUnits = floor($time / $unit);
$result .= "$numberOfUnits $text ";
$time -= $numberOfUnits * $unit;
++$counter;
}
return "{$result}ago";
}
Check result on codepad.org.
PHP 5.2.0 introduced DateTime object, which will provide a better solution for the problem.
I am going to use my answer on another question to show how easy it is, though will slightly edit the code:
$create_time = "2016-08-02 12:35:04";
$current_time="2016-08-02 16:16:02";
$dtCurrent = DateTime::createFromFormat('Y-m-d H:i:s', $current_time);
// to use current timestamp, use the following:
//$dtCurrent = new DateTime();
$dtCreate = DateTime::createFromFormat('Y-m-d H:i:s', $create_time);
$diff = $dtCurrent->diff($dtCreate);
$interval = $diff->format("%y years %m months %d days %h hours %i minutes %s seconds");
$interval will return 0 years 0 months 0 days 3 hours 40 minutes 58 seconds
Let's get rid of those zero values now. Good old regex is pretty much useful here:
$interval = preg_replace('/(^0| 0) (years|months|days|hours|minutes|seconds)/', '', $interval);
In the end we get exactly what we need: 3 hours 40 minutes 58 seconds
You can use a progressive calculation method such as:
$tokens = array (
31536000 => 'year',
2592000 => 'month',
604800 => 'week',
86400 => 'day',
3600 => 'hour',
60 => 'minute',
1 => 'second'
);
$tokenValues = array();
foreach($tokens as $tokenValue => $tokenName)
{
if($time > $tokenValue)
{
$tokenValues[$tokenName] = floor($time \ $tokenValue);
$time -= $tikenValue;
}
if(count($tokenValues) == 2)
{
break;
}
}
You should then have only 2 items in tokenValues, adapt as you see fit!
You can find Event occurred time and current time difference using diff. For example see the following code
$time1 = new DateTime('2013-11-21 12:59:00'); // Event occurred time
$time2 = new DateTime(date('Y-m-d H:i:s')); // Current time
$interval = $time1->diff($time2);
echo $interval->y . " Year " .$interval->m . " Month " .$interval->d ." Days ". $interval->h . " Hours, " . $interval->i." Mintues, ".$interval->s." seconds <br/>";
I don't have time to explain, but this function works as I tested it. In addition, I add a feature to handle time from the current time and future. If you supply this function with exact current time, it will return 'just now'. If you supply it with time from the past, it will return a string ended with 'ago'. If you supply it with time from the future, it will return a string ended with 'later'. Another feature is automatic singular/plural form for the time unit. Try it yourself!
function timeEllapsedFrom($from) {
$time = time() - $from;
$diff = abs($time);
$tokens = array (
'year' => 31536000,
'month' => 2592000,
'week' => 604800,
'day' => 86400,
'hour' => 3600,
'minute' => 60,
'second' => 1
);
$result = array();
foreach ($tokens as $id => $length) {
$value = floor($diff/$length);
if ($value) $result[] = "$value $id" . ($value > 1 ? 's' : '');
$diff -= $length*$value;
}
if (!count($result)) return 'just now';
return join(', ', $result) . ($time < 0 ? ' later' : ' ago');
}
You can test it with this code:
echo timeEllapsedFrom(time() - 604800 - 7200 - 480 - 24);
and it will output:
1 week, 2 hours, 8 minutes, 24 seconds ago
Hope it helps!
Using DateTime class. Works with both past and future timestamps, and does correct pluralization when needed:
function elapsedTime($time)
{
date_default_timezone_set('UTC');
$given_time = new DateTime("#$time");
$current_time = new DateTime();
$diff = $given_time->diff($current_time);
$timemap = array('y' => 'year',
'm' => 'month',
'd' => 'day',
'h' => 'hour',
'i' => 'minute',
's' => 'second');
$timefmt = array();
foreach ($timemap as $prop => $desc) {
if ($diff->$prop > 0) {
$timefmt[] = ($diff->$prop > 1)
? "{$diff->$prop} {$desc}s"
: "{$diff->$prop} $desc";
}
}
return (!$timefmt)
? 'just now'
: ((count($timefmt) > 1)
? $diff->format(sprintf('%s and %s',
implode(', ', array_slice($timefmt, 0, -1)), end($timefmt)))
: end($timefmt))
. (($given_time < $current_time) ? ' ago' : ' later');
}
Testing:
var_dump(elapsedTime(strtotime('1987-11-25 11:40')));
# string(69) "25 years, 11 months, 29 days, 17 hours, 50 minutes and 42 seconds ago"
var_dump(elapsedTime(time()));
# string(8) "just now"
var_dump(elapsedTime(strtotime('2013-11-25')));
# string(41) "18 hours, 29 minutes and 18 seconds later"

How to differentiate between hours?

I'm developing a simple sistem so our employees could save their overtime so, at the end of the month, they're payed for those extra hours they made.
Normal hours are counted as 1 but nightly ones (through 23:00 to 07:00) should be 1,25 hours each.
That said, we're requesting to introduce the day, and the start and end hours. So, I thought I could have an array like this:
$hours= array(
'0' => true, '1' => true, '2' => true, '3' => true, '4' => true, '5' => true, '6' => true,
'7' => false, '8' => false, '9' => false, '10' => false, '11' => false, '12' => false, '13' => false,
'14' => false, '15' => false, '16' => false, '17' => false, '18' => false, '19' => false, '20' => false,
'21' => false, '22' => false, '23' => true
);
So, basically I test if an hour is special or it isn't with a loop like this:
$normals = 0;
$specials = 0;
$hour_since = '23:00:00';
$hour_since_expl = explode(':',$hour_since);
$hour_to = '23:15:00';
$hour_to_expl= explode(':',$hour_to );
$date = $fecha = '2012-03-14';
$datetime_since= strtotime($date ." ".$hour_since );
$datetime_to= ((int) $hour_to_expl[0] > (int) $hour_to_expl[0]) ?
strtotime(date("Y-m-d h:i:s", strtotime($date ." ".$hour_to)) . " +1 day") :
strtotime($date." ".$hour_to);
$difference = $datetime_to - $datetime_since;
$hours_difference = $difference / SECONDS_PER_HOUR; //60*60
for ($i = 0; $i <= $difference; $i++){
$hour = $i + (int) $hour_since_expl [0];
if ($hours[$hour]) //Special hour here... Pay more!
$specials++;
else
$normals++;
}
But the problem is when hours are not exact and you have started somewhere like 22:30 and ended 00:30 where you have 0,5 hours being not special. I've been struggling my mind but I can't find any solution.
Do someone have any ideas?
Edit: More code given.
Let's assume that start time is 15:15 and endtime is 22:45. First, convert start time to nearest HOUR on or after it and endtime to nearest HOUR on or before it. So we get 15:00 and 22:00. Use your loop for these hours and use the differences as follows: if the difference time (15:15-15:00 or 22:45-22:00) is in overtime, then do overtime*numMins/60 else do standard*numMins/60. I'm not familiar with PHP, so had to do this in pseudo code
edit: took a bit of time, but i love intresting chalanges ;D i hope it helps!
$specialStart = 23;
$specialEnd = 7;
$normals = 0;
$specials = 0;
$hour_since = '22:00:00';
$hour_since_expl = explode(':',$hour_since);
$start_time = mktime($hour_since_expl[0], $hour_since_expl[1], $hour_since_expl[2]);
$hour_to = '07:30:00';
$hour_to_expl= explode(':',$hour_to );
$end_time = mktime($hour_to_expl[0], $hour_to_expl[1], $hour_to_expl[2]);
if($end_time < $start_time){
//worked passed minight, add a day
$end_time = $end_time + 86400;
}
$work_time = ( $end_time - $start_time ) / 60;
for($i = 0; $i < $work_time; $i++){
$time = $start_time + ( $i * 60 );
$hour_of_day = date("H", $time);
if($hour_of_day >= $specialStart || $hour_of_day < $specialEnd){
$specials += 60;
}else{
$normals += 60;
}
}
$specials = $specials / 3600;
$normals = $normals / 3600;
echo "specials: ".$specials;
echo "<br/>normals: ".$normals;
I would suggest you to calculate total time in two separate fields.Firstly get normal hours i.e. before 23:00 and then special hours i.e. after 23:00 and before 07:00.
Then you can calculate pay easily.

Get value of previous array key in PHP

I am trying to do a Drug Half life calculator with PHP. I want to pass in the amount of the drug taken per day in MG's and pass in the Half-life hours, then it will calculate how much of the drug is left after X amount of time and how much is still left from previous doses.
So far this is what I have...
function calcHalfLife( $mgTaken , $drugHalfLifeHours , $day = 1 ) {
//total number of half-lifes elapsed
$total_half_lifes = ($day * 24) / $drugHalfLifeHours;
//total reduction in dosage
$reductionFactor = pow( 0.5 , $total_half_lifes );
//return the current dosage in the person's system
return round( $mgTaken * $reductionFactor , 8 );
}
Then I am working on this function below which will let me pass in an Array of Days and the MG taken for each day, the function should then iterate the array and run the function above on each day's value.
function HalfLifeChart(array $days, $drugHalfLifeHours ) {
$out = array();
foreach ($days as $day => $dosage) {
$out[$day] = calcHalfLife( $dosage , $drugHalfLifeHours , 1 );
}
return $out;
}
Example usage...
$day = array(1 => 30,
2 => 0,
3 => 0,
4 => 40,
5 => 30,
6 => 10,
7 => 60);
echo '<br><pre>';
print_r(HalfLifeChart( $day, 4.5));
echo '</pre><br><br>';
Now I have a pretty good start but the HalfLifeChart function is where I need to do more work, right now it will run the Half-life calculations on the number passed for each day which is good, but I need to get the result from the previous day and add that to the MG taken on the current day and then run the Calculations on that number.
So for example, if I have 0.8043mg left from the previous day and I took 30mg today, then the calculation should be ran on 0.8043 + 30 and then pass that result through my Half life calculator function.
I am not sure how to grab the result from the previous day though, any help please?
Why don't you store the result of the previous day on another variable?
Something like:
function HalfLifeChart(array $days, $drugHalfLifeHours ) {
$out = array();
$prevDay = 0;
foreach ($days as $k => $v) {
$out[$k] = calcHalfLife( $v , $drugHalfLifeHours , 1 ); //change this
$prevDay = $out[$k];
}
return $out;
}
function HalfLifeChart(array $days, $drugHalfLifeHours ) {
$out=array();
$remains=0;
foreach ($days as $day => $dosage) {
$total=$remains+$dosage;
$out[$day]=$total;
$remains=calcHalfLife( $total , $drugHalfLifeHours , 1 );
}
return $out;
}
gives you
print_r(HalfLifeChart( $day, 4.5));
Array
(
[1] => 30
[2] => 0.74409424
[3] => 0.01845587
[4] => 40.00045776
[5] => 30.99213701
[6] => 10.76870236
[7] => 60.26709765
)
Just store it.
function HalfLifeChart(array $days, $drugHalfLifeHours ) {
$out = array();
$yesterday = 0;
foreach ($days as $k => $v) {
$out[$k] = calcHalfLife($v + $yesterday, $drugHalfLifeHours, 1);
$yesterday = $out[$k];
}
return $out;
}

Categories