Round minute down to nearest quarter hour
Asked Answered
P

20

65

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

Perichondrium answered 19/3, 2010 at 21:15 Comment(4)
If you were rounding to the nearest quarter hour wouldn't 1:12 become 1:15 and 3:28 become 3:30? Rather, aren't you rounding down to the quarter hour? The former is a little more challenging than the latter...Delamare
Sorry, yes that was the goal. Rounding DOWN to the nearest quarter hour.Perichondrium
Function for arbitrarily rounding PHP DateTimes: https://mcmap.net/q/297896/-php-datetime-round-up-to-nearest-10-minutesScleroprotein
Relevant advice about handling this task before PHP gets a chance: How to round a time to the nearest 15 minute segment and Java, MySQL or PHP - rounding down minutes to the next 10 minutesNobility
R
36

Your full function would be something like this...

function roundToQuarterHour($timestring) {
    $minutes = date('i', strtotime($timestring));
    return $minutes - ($minutes % 15);
}
Redemption answered 19/3, 2010 at 21:27 Comment(4)
removed the function and went this route: $start_minutes = date('i', strtotime($row['start'])); $minutes_floor = $start_minutes - ($start_minutes % 15);Perichondrium
if $minutes = 29 it will return 15 - must return 30Trundle
how to do this rounding to upwards. Like 9.10 rounded to 9.15 and 9.20 to 9.30 like wiseZachariahzacharias
@Trundle That's correct. We are rounding down, not to the nearest. So, 15 is correct for 29Brannon
W
82
$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().

Wommera answered 19/3, 2010 at 21:26 Comment(1)
You can supplement ceil()/floor() for round() if you want to force your time up/down, respectively.Ovate
R
36

Your full function would be something like this...

function roundToQuarterHour($timestring) {
    $minutes = date('i', strtotime($timestring));
    return $minutes - ($minutes % 15);
}
Redemption answered 19/3, 2010 at 21:27 Comment(4)
removed the function and went this route: $start_minutes = date('i', strtotime($row['start'])); $minutes_floor = $start_minutes - ($start_minutes % 15);Perichondrium
if $minutes = 29 it will return 15 - must return 30Trundle
how to do this rounding to upwards. Like 9.10 rounded to 9.15 and 9.20 to 9.30 like wiseZachariahzacharias
@Trundle That's correct. We are rounding down, not to the nearest. So, 15 is correct for 29Brannon
D
12
$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;
Delamare answered 19/3, 2010 at 21:23 Comment(1)
I found that if the time is something like 10:59 it's rounded to 10:60. To solve this I added: if ($minutes == "60") { $rounded = $date_split['hour']+1; $rounded .= ":00"; } else { $minutes = str_pad($minutes, 2, '0', STR_PAD_LEFT); $rounded = $date_split['hour'].":".$minutes; } also str_pad to ensure the leading zero everywhere. This solution might be optimal but works fine.Selfregulated
D
9

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

Definitely answered 23/8, 2013 at 5:8 Comment(0)
F
6
$minutes = ($minutes - ($minutes % 15));
Frampton answered 19/3, 2010 at 21:17 Comment(1)
So 1:19 becomes (19%15)*15 = 4*15 = 60!?Delamare
U
6

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)
Ultramontanism answered 19/3, 2010 at 22:36 Comment(0)
A
6

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));
}
Asphyxiant answered 12/4, 2019 at 20:44 Comment(4)
Actually, you can just do $now->floorMinute(15) or similar when using CarbonByrne
use now()->floorMinutes(15) in Laravel. To round up use now()->roundMinutes(15) like @Wesley-Synio was getting at, this exists for other time intervals such as hours and days.Housebound
Because you don't need the Carbon lib. See the other answer from @TrentRenshawMartellato
Sooner or later if you're dealing with date manipulation, Carbon is a mustPlenteous
E
4

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'));

}
Expunction answered 18/10, 2016 at 23:23 Comment(1)
It does floor – not round up/down though.Martellato
G
2

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>';
Glasser answered 29/5, 2011 at 15:41 Comment(0)
D
2

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
Demonography answered 19/10, 2020 at 14:16 Comment(0)
C
1

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.

Competitive answered 8/12, 2011 at 14:10 Comment(1)
Or this could be easier. $expireNowDate = date_create(date("Y-m-d"));Alber
P
1
// 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 );
}
Phenanthrene answered 3/1, 2016 at 21:43 Comment(1)
This worked as required. 11:29 goes to 11:30, 11:22 goes to 11:15. This is better then Wickethewok's solution. I did change the default of round to minutes to 15 as the requester asked for rounding to the quarter hour. I also changed the output to g:i:s which suits my particular need.Bordiuk
E
1

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.

Epner answered 27/7, 2016 at 9:34 Comment(0)
C
0

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.

Craving answered 5/12, 2012 at 13:55 Comment(0)
C
0

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

Clue answered 18/6, 2013 at 15:58 Comment(0)
C
0

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)
Cervine answered 3/10, 2015 at 18:37 Comment(0)
N
0

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)

Nobility answered 19/9, 2019 at 12:48 Comment(0)
S
0

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

SELECT FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(date_col)/900)*900) FROM mytable;
Sloven answered 2/7, 2022 at 11:42 Comment(0)
B
0

Solved by one line code.

protected function roundToQuarterHour($timestring)
    {
        return Carbon::parse($timestring)->roundMinute(15);
    }
Betake answered 12/9, 2022 at 12:29 Comment(0)
H
0

Most of the answers here make it more complicated than it needs to be.

PHP 7+ has a built-in intdiv function for integer division (floored division). Since the OP wants to round down to the nearest 15-minute mark, you can simply use:

$t = time();
// Or from string:
$t = strtotime('2010-03-18 10:50:00');
$t = intdiv($t, 900) * 900;
$output = date('Y-m-d H:i:s', $t);

Run it online here

900 is the number of seconds in 15 minutes (60 * 15).

This code should also be faster if performance is extremely critical, as it would be fewer underlying instructions, since you're not using float division anymore.

Hippodrome answered 16/11, 2023 at 22:22 Comment(4)
Yes, this is less complicated than the accepted answer, but your input value is a unix timestamp. However, the original question offers a formatted date string. To fully satisfy the question and boast of performance benefits, you will need to parse the string to unix, calculate, then return the integer to the original date time format.Nobility
@Nobility The OP didn't provide any code and the ask was pretty vague. Still, I updated my answer. The core the of the question is how to round down a date. If the user is selecting from the DB, then it depends on whether they used UNIX_TIMESTAMP in the select, or how they get the data into PHP.Hippodrome
@Nobility In addition, the highest-voted answer used time() as well, which shows that the answer is still useful as it was.Hippodrome
It is relatively common for old pages on Stack Overflow to suffer from "task creep" / requirement deviation because new answers become swayed in direction based on earlier posted answers. I only mean to keep answers focused on answering the originally asked question.Nobility

© 2022 - 2024 — McMap. All rights reserved.