开发者

Round minute down to nearest quarter hour

开发者 https://www.devze.com 2022-12-23 18:46 出处:网络
I need to round times down to the nearest quarter hour in PHP.The times are being pulled from a MySQL database from a datetime column and formatted like 2010开发者_开发技巧-03-18 10:50:00.

I need to round times down to the nearest quarter hour in PHP. The times are being pulled from a MySQL database from a datetime column and formatted like 2010开发者_开发技巧-03-18 10:50:00.

Example:

  • 10:50 needs to be 10:45
  • 1:12 needs to be 1:00
  • 3:28 needs to be 3:15
  • etc.

I'm assuming floor() is involved but not sure how to go about it.

Thanks


$seconds = time();
$rounded_seconds = round($seconds / (15 * 60)) * (15 * 60);

echo "Original: " . date('H:i', $seconds) . "\n";
echo "Rounded: " . date('H:i', $rounded_seconds) . "\n";

This example gets the current time and rounds it to the nearest quarter and prints both the original and the rounded time.

PS: If you want to round it down replace round() with floor().


Your full function would be something like this...

function roundToQuarterHour($timestring) {
    $minutes = date('i', strtotime($timestring));
    return $minutes - ($minutes % 15);
}


$now = getdate();
$minutes = $now['minutes'] - $now['minutes']%15;

 //Can add this to go to the nearest 15min interval (up or down)
  $rmin  = $now['minutes']%15;
  if ($rmin > 7){
    $minutes = $now['minutes'] + (15-$rmin);
   }else{
      $minutes = $now['minutes'] - $rmin;
  }

$rounded = $now['hours'].":".$minutes;
echo $rounded;


To round nearest quarter hour use below code

<?php
$time = strtotime("01:08");
echo $time.'<br />';
$round = 15*60;
$rounded = round($time / $round) * $round;
echo date("H:i", $rounded);
?>

01:08 become 01:15


$minutes = ($minutes - ($minutes % 15));


Lately I like tackling a problem the TDD/unit testing way. I am not programming much PHP anymore lately, but this is what I came up with. To be honest I actually looked at the code examples here, and picked the one I thought was already correct. Next I wanted to verify this by unit testing using the tests you provided above.

class TimeTest

require_once 'PHPUnit/Framework.php';
require_once 'Time.php';

class TimeTest extends PHPUnit_Framework_TestCase 
{
    protected $time;

    protected function setUp() {
        $this->time = new Time(10, 50);
    }

    public function testConstructingTime() {
        $this->assertEquals("10:50", $this->time->getTime());
        $this->assertEquals("10", $this->time->getHours());
        $this->assertEquals("50", $this->time->getMinutes());        
    }

    public function testCreatingTimeFromString() {
        $myTime = Time::create("10:50");
        $this->assertEquals("10", $myTime->getHours());
        $this->assertEquals("50", $myTime->getMinutes());
    }

    public function testComparingTimes() {
        $timeEquals     = new Time(10, 50);
        $this->assertTrue($this->time->equals($timeEquals));
        $timeNotEquals  = new Time(10, 44);
        $this->assertFalse($this->time->equals($timeNotEquals));
    }


    public function testRoundingTimes()
    {
        // Round test time.
        $roundedTime = $this->time->round();
        $this->assertEquals("10", $roundedTime->getHours());
        $this->assertEquals("45", $roundedTime->getMinutes());

        // Test some more times.
        $timesToTest = array(
            array(new Time(1,00), new Time(1,12)),
            array(new Time(3,15), new Time(3,28)),
            array(new Time(1,00), new Time(1,12)),
        );

        foreach($timesToTest as $timeToTest) {
            $this->assertTrue($timeToTest[0]->equals($timeToTest[0]->round()));
        }        
    }
}

class Time

<?php

class Time
{
    private $hours;
    private $minutes;

    public static function create($timestr) {
        $hours      = date('g', strtotime($timestr));
        $minutes    = date('i', strtotime($timestr));
        return new Time($hours, $minutes);
    }

    public function __construct($hours, $minutes) {
        $this->hours    = $hours;
        $this->minutes  = $minutes;
    }

    public function equals(Time $time) {
        return  $this->hours == $time->getHours() &&
                 $this->minutes == $time->getMinutes();
    }

    public function round() {
        $roundedMinutes = $this->minutes - ($this->minutes % 15);
        return new Time($this->hours, $roundedMinutes);
    }

    public function getTime() {
        return $this->hours . ":" . $this->minutes;
    }

    public function getHours() {
        return $this->hours;
    }

    public function getMinutes() {
        return $this->minutes;
    }
}

Running Test

alfred@alfred-laptop:~/htdocs/time$ phpunit TimeTest.php 
PHPUnit 3.3.17 by Sebastian Bergmann.

....

Time: 0 seconds

OK (4 tests, 12 assertions)


It's an old question but having recently implemented myself I'll share my solution:-

public function roundToQuarterHour($datetime) {

    $datetime = ($datetime instanceof DateTime) ? $datetime : new DateTime($datetime);

    return $datetime->setTime($datetime->format('H'), ($i = $datetime->format('i')) - ($i % 15));

}

public function someQuarterHourEvent() {

    print_r($this->roundToQuarterHour(new DateTime()));
    print_r($this->roundToQuarterHour('2016-10-19 10:50:00'));
    print_r($this->roundToQuarterHour('2016-10-19 13:12:00'));
    print_r($this->roundToQuarterHour('2016-10-19 15:28:00'));

}


I was surprised that nobody has mentioned the amazing Carbon library (often used in Laravel).

/**
 * 
 * @param \Carbon\Carbon $now
 * @param int $minutesChunk
 * @return \Carbon\Carbon
 */
public static function getNearestTimeRoundedDown($now, $minutesChunk = 30) {
    $newMinute = $now->minute - ($now->minute % $minutesChunk); 
    return $now->minute($newMinute)->startOfMinute(); //https://carbon.nesbot.com/docs/
}

Test cases:

public function testGetNearestTimeRoundedDown() {
    $this->assertEquals('2018-07-06 14:00:00', TT::getNearestTimeRoundedDown(Carbon::parse('2018-07-06 14:12:59'))->format(TT::MYSQL_DATETIME_FORMAT));
    $this->assertEquals('14:00:00', TT::getNearestTimeRoundedDown(Carbon::parse('2018-07-06 14:29:25'))->format(TT::HOUR_MIN_SEC_FORMAT));
    $this->assertEquals('14:30:00', TT::getNearestTimeRoundedDown(Carbon::parse('2018-07-06 14:30:01'))->format(TT::HOUR_MIN_SEC_FORMAT));
    $this->assertEquals('18:00:00', TT::getNearestTimeRoundedDown(Carbon::parse('2019-07-06 18:05:00'))->format(TT::HOUR_MIN_SEC_FORMAT));
    $this->assertEquals('18:45:00', TT::getNearestTimeRoundedDown(Carbon::parse('2019-07-06 18:50:59'), 15)->format(TT::HOUR_MIN_SEC_FORMAT));
    $this->assertEquals('18:45:00', TT::getNearestTimeRoundedDown(Carbon::parse('2019-07-06 18:49:59'), 15)->format(TT::HOUR_MIN_SEC_FORMAT));
    $this->assertEquals('10:15:00', TT::getNearestTimeRoundedDown(Carbon::parse('1999-12-30 10:16:58'), 15)->format(TT::HOUR_MIN_SEC_FORMAT));
    $this->assertEquals('10:10:00', TT::getNearestTimeRoundedDown(Carbon::parse('1999-12-30 10:16:58'), 10)->format(TT::HOUR_MIN_SEC_FORMAT));
}


For my system I wanted to add jobs which are scheduled to run every 5th minute on my server, and I want the same job to run in the next 5th minute block, then 15, 30, 60, 120, 240 minutes, 1 day and 2 days after, so that's what this function calculates

function calculateJobTimes() {
    $now = time();
    IF($now %300) {
        $lastTime = $now - ($now % 300);
    }
    ELSE {
        $lastTime = $now;
    }
    $next[] = $lastTime + 300;
    $next[] = $lastTime + 900;
    $next[] = $lastTime + 1800;
    $next[] = $lastTime + 3600;
    $next[] = $lastTime + 7200;
    $next[] = $lastTime + 14400;
    $next[] = $lastTime + 86400;
    $next[] = $lastTime + 172800;
    return $next;
}

echo "The time now is ".date("Y-m-d H:i:s")."<br />
Jobs will be scheduled to run at the following times:<br /><br />
<ul>";
foreach(calculateJobTimes() as $jTime) {
    echo "<li>".date("Y-m-d H:i:s", $jTime).'</li>';
}
echo '</ul>';


It's important you use a built-in PHP function for rounding times to take into account the date as well as the time. For example 2020-10-09 23:37:35 needs to become 2020-10-10 00:00:00 when rounding up to nearest hour.

Round time to nearest hour:

$time = '2020-10-09 23:37:35';

$time = date("Y-m-d H:i:s", round(strtotime($time) / 3600) * 3600); // 2020-10-10 00:00:00

$time = '2020-10-09 23:15:35';

$time = date("Y-m-d H:i:s", round(strtotime($time) / 3600) * 3600); // 2020-10-09 23:00:00

Round time down to nearest 15 minute increment:

$time = '2020-10-09 23:15:35';

$time = date("Y-m-d H:i:s", floor(strtotime($time) / (60*15))*(60*15)); // 2020-10-09 23:15:00

$time = '2020-10-09 23:41:35';

$time = date("Y-m-d H:i:s", floor(strtotime($time) / (60*15))*(60*15)); // 2020-10-09 23:30:00

If you need to round up to nearest 15 minute increment, change floor to ceil e.g

$time = date("Y-m-d H:i:s", ceil(strtotime($time) / (60*15))*(60*15)); // 2020-10-09 23:45:00

If you need to round time to another minute increment you can simply do:

$time = date("Y-m-d H:i:s", ceil(strtotime($time) / (60*20))*(60*20)); // 2020-10-10 00:00:00


I needed a way to round down to the day, and cut off everything beyond that:

$explodedDate = explode("T", gmdate("c",strtotime("now")));
$expireNowDate =  date_create($explodedDate[0]);

The strtotime gives me a timestamp for "now", which gmdate converts to ISO format (something like "2012-06-05T04:00:00+00:00"), then I use explode at the "T", giving me "2012-06-05" in the zeroth index of $explodedDate, which is then passed into date_create to get a date object.

Not sure if all of that is necessary, but it seems like a lot less work than going through and subtracting the seconds, minutes, hours etc.


// time = '16:58'
// type = auto, up, down
function round_time( $time, $round_to_minutes = 5, $type = 'auto' ) {
    $round = array( 'auto' => 'round', 'up' => 'ceil', 'down' => 'floor' );
    $round = @$round[ $type ] ? $round[ $type ] : 'round';
    $seconds = $round_to_minutes * 60;
    return date( 'H:i', $round( strtotime( $time ) / $seconds ) * $seconds );
}


Simple solution:

$oldDate = "2010-03-18 10:50:00";
$date = date("Y-m-d H:i:s", floor(strtotime($oldDate) / 15 / 60) * 15 * 60);

You can change floor to ceil if you want to round up.


I wrote a function that does the trick to round time stamps to seconds or minutes.

I might not be the most performant way, but I think PHP doens't care about a few simple loops.

In your case, you just pass your MySQL datetime like this:

<?php echo date('d/m/Y - H:i:s', roundTime(strtotime($MysqlDateTime), 'i', 15)); ?>

Returns: the closests rounded value (looks both up and down!)

The function:

<?php
function roundTime($time, $entity = 'i', $value = 15){

    // prevent big loops
    if(strpos('is', $entity) === false){
        return $time;
    }

    // up down counters
    $loopsUp = $loopsDown = 0;

    // loop up
    $loop = $time;
    while(date($entity, $loop) % $value != 0){
        $loopsUp++;
        $loop++;
    }
    $return = $loop;    


    // loop down
    $loop = $time;
    while(date($entity, $loop) % $value != 0){
        $loopsDown++;
        $loop--;
        if($loopsDown > $loopsUp){
            $loop = $return;
            break;  
        }
    }
    $return = $loop;

    // round seconds down
    if($entity == 'i' && date('s', $return) != 0){
        while(intval(date('s', $return)) != 0){
            $return--;
        }
    }
    return $return;
}
?>

You simple replace $entity by 's' if you want to round up or down to seconds and replace 15 by the amount of seconds or minutes you want to roud up or down to.


Here's a function I'm currently using:

/**
 * Rounds a timestamp
 *
 * @param int $input current timestamp
 * @param int $round_to_minutes rounds to this minute
 * @param string $type auto, ceil, floor
 * @return int rounded timestamp
 */
static function roundToClosestMinute($input = 0, $round_to_minutes = 5, $type = 'auto')
{
    $now = !$input ? time() : (int)$input;

    $seconds = $round_to_minutes * 60;
    $floored = $seconds * floor($now / $seconds);
    $ceiled = $seconds * ceil($now / $seconds);

    switch ($type) {
        default:
            $rounded = ($now - $floored < $ceiled - $now) ? $floored : $ceiled;
            break;

        case 'ceil':
            $rounded = $ceiled;
            break;

        case 'floor':
            $rounded = $floored;
            break;
    }

    return $rounded ? $rounded : $input;
}

Hope it helps someone :)


Might help others. For any language.

roundedMinutes = yourRoundFun(Minutes / interval) * interval.

E.g. The interval could be 5 minutes , 10 minutes, 15 minutes, 30 minutes. Then rounded minutes can be reset to the respective date.

yourDateObj.setMinutes(0) 
yourDateObj.setMinutes(roundedMinutes)


While it is typically most appropriate to use datetime-based functions to manipulate a datetime, the requirement of this task does not involve any special time-related treatment -- it is a simple task of executing a calculation on a specific substring and using the mathematical outcome to replace the substring.

Not everyone is a fan of regex, but it does provide a single-function technique to mutate the input string.

Code: (Demo)

$timeString = "2010-03-18 10:50:57";
// PHP7.4+ arrow syntax
echo preg_replace_callback(
        '~:\K(\d{2}).*~',
        fn($m) => $m[1] - $m[1] % 15 . ':00',
        $timeString
     );

echo "\n---\n";
// below PHP7.3
echo preg_replace_callback(
        '~:\K(\d{2}).*~',
        function($m) {return $m[1] - $m[1] % 15 . ':00';},
        $timeString
     );

Output:

2010-03-18 10:45:00
---
2010-03-18 10:45:00

Note, this regex pattern will work just as well if dealing with a time-only (colon-delimited) string. (Demo)


Or you could MySQL let do the work (for dates from 1970):

SELECT FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(date_col)/900)*900) FROM mytable;


Solved by one line code.

protected function roundToQuarterHour($timestring)
    {
        return Carbon::parse($timestring)->roundMinute(15);
    }
0

精彩评论

暂无评论...
验证码 换一张
取 消