Округлить минуту до ближайшей четверти часа

Мне нужно округлить время до ближайшей четверти часа в PHP. Время извлекается из базы данных MySQL из столбца даты и времени и форматируется как 2010-03-18 10:50:00.

Пример:

  • 10:50 должно быть 10:45
  • 1:12 должно быть 1:00
  • 3:28 должно быть 3:15
  • и Т. Д.

Я предполагаю, что floor() замешан, но не знаю, как это сделать.

Спасибо


person Rob    schedule 19.03.2010    source источник
comment
Если бы вы округляли до ближайшей четверти часа, разве 1:12 не стало бы 1:15, а 3:28 не стало бы 3:30? Скорее, вы не округляете в меньшую сторону до четверти часа? Первое немного сложнее, чем второе...   -  person Richard JP Le Guen    schedule 20.03.2010
comment
Извините, да, это была цель. Округление в меньшую сторону до четверти часа.   -  person Rob    schedule 23.03.2010
comment
Функция для произвольного округления PHP DateTimes: stackoverflow.com/a/57399274/339440   -  person Stephen R    schedule 08.08.2019


Ответы (17)


Ваша полная функция будет примерно такой...

function roundToQuarterHour($timestring) {
    $minutes = date('i', strtotime($timestring));
    return $minutes - ($minutes % 15);
}
person Wickethewok    schedule 19.03.2010
comment
удалил функцию и пошел по этому пути: $start_minutes = date('i', strtotime($row['start'])); $minutes_floor = $start_minutes - ($start_minutes % 15); - person Rob; 23.03.2010
comment
если $minutes = 29, он вернет 15 - должен вернуть 30 - person FDisk; 07.02.2012
comment
как сделать это округление в большую сторону. Например, 9,10 округляется до 9,15, а 9,20 — до 9,30. - person Yasiru Nilan; 17.04.2020
comment
@FDisk Это правильно. Округляем в меньшую сторону, а не в меньшую сторону. Итак, 15 правильно для 29 - person nice_dev; 17.04.2020

$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";

Этот пример получает текущее время и округляет его до ближайшего квартала и печатает как исходное, так и округленное время.

PS: если вы хотите округлить в меньшую сторону, замените round() на floor().

person Veger    schedule 19.03.2010
comment
Вы можете дополнить ceil()/floor() для round(), если хотите увеличить/уменьшить время соответственно. - person zmonteca; 12.07.2013
comment
Простой, умный и легко настраиваемый. Потрясающий! - person Robin Valk; 13.09.2015

Чтобы округлить ближайшую четверть часа, используйте код ниже

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

01:08 стало 01:15

person Mufaddal    schedule 23.08.2013
comment
На вашем примере расчеты получаются намного точнее, чем на примере в принятом ответе, спасибо! - person Ilia; 06.12.2013

В последнее время мне нравится решать проблему с помощью TDD/модульного тестирования. способ. В последнее время я мало программирую на PHP, но вот что я придумал. Честно говоря, я посмотрел примеры кода здесь и выбрал тот, который, как мне показалось, уже был правильным. Затем я хотел проверить это с помощью модульного тестирования, используя приведенные выше тесты.

класс 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()));
        }        
    }
}

время учебы

<?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;
    }
}

Запуск теста

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

....

Time: 0 seconds

OK (4 tests, 12 assertions)
person Alfred    schedule 19.03.2010

Это старый вопрос, но, недавно реализовав себя, я поделюсь своим решением: -

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

}
person Trent Renshaw    schedule 18.10.2016

Я был удивлен, что никто не упомянул удивительную библиотеку Carbon (часто используемую в 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/
}

Тестовые случаи:

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));
}
person Ryan    schedule 12.04.2019
comment
На самом деле, вы можете просто сделать $now->floorMinute(15) или подобное при использовании Carbon. - person Wesley - Synio; 31.03.2020
comment
используйте now()->floorMinutes(15) в Laravel. Чтобы округлить, используйте now()->roundMinutes(15), как это делал @Wesley-Synio, это существует для других временных интервалов, таких как часы и дни. - person wizardzeb; 05.12.2020

Для моей системы я хотел добавить задания, выполнение которых запланировано на моем сервере каждую 5-ю минуту, и я хочу, чтобы одно и то же задание выполнялось в течение следующего 5-минутного блока, затем 15, 30, 60, 120, 240 минут, 1 день и 2 дня спустя, вот что вычисляет эта функция

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>';
person Sander    schedule 29.05.2011

Мне нужен был способ округлить до дня и отрезать все, что сверх этого:

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

Strtotime дает мне метку времени для «сейчас», которую gmdate преобразует в формат ISO (что-то вроде «2012-06-05T04:00:00+00:00»), затем я использую взорваться на «T», что дает мне « 2012-06-05" в нулевом индексе $explodedDate, который затем передается в date_create для получения объекта даты.

Не уверен, что все это необходимо, но кажется, что это намного меньше работы, чем прохождение и вычитание секунд, минут, часов и т. д.

person Craig B    schedule 08.12.2011
comment
Или это может быть проще. $expireNowDate = date_create(date("Y-m-d")); - person Andy Gee; 30.10.2012

Простое решение:

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

Вы можете изменить floor на ceil, если хотите округлить.

person Rafał Schefczyk    schedule 27.07.2016

Важно, чтобы вы использовали встроенную функцию PHP для округления времени, чтобы учитывать дату и время. Например, 2020-10-09 23:37:35 должно стать 2020-10-10 00:00:00 при округлении до ближайшего часа.

Округлить время до ближайшего часа:

$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

Округлить время до ближайшего 15-минутного приращения:

$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

Если вам нужно округлить в большую сторону до ближайшего 15-минутного приращения, измените floor на ceil, например

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

Если вам нужно округлить время до еще одной минуты, вы можете просто сделать:

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

Я написал функцию, которая округляет метки времени до секунд или минут.

Возможно, я не самый производительный способ, но я думаю, что PHP не заботится о нескольких простых циклах.

В вашем случае вы просто передаете дату и время MySQL следующим образом:

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

Возвращает: ближайшее округленное значение (выглядит как вверх, так и вниз!)

Функция:

<?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;
}
?>

Вы просто заменяете $entity на 's', если хотите округлить вверх или вниз до секунд, и замените 15 количеством секунд или минут, до которых вы хотите округлить вверх или вниз.

person ibram    schedule 05.12.2012

Вот функция, которую я сейчас использую:

/**
 * 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;
}

Надеюсь, это поможет кому-то :)

person Marcel Drews    schedule 18.06.2013

Может помочь другим. Для любого языка.

roundedMinutes = yourRoundFun(Minutes / interval) * interval.

Например. Интервал может быть 5 минут, 10 минут, 15 минут, 30 минут. Затем округленные минуты можно сбросить до соответствующей даты.

yourDateObj.setMinutes(0) 
yourDateObj.setMinutes(roundedMinutes)
person Sai    schedule 03.10.2015

Хотя обычно наиболее целесообразно использовать функции, основанные на дате и времени, для управления датой и временем, требование этой задачи не связано с какой-либо специальной обработкой, связанной со временем — это простая задача выполнения вычисления для определенной подстроки и использования математических вычислений. результат для замены подстроки.

Не все являются поклонниками регулярных выражений, но они предоставляют технику с одной функцией для изменения входной строки.

Код: (Демо)

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

Вывод:

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

Обратите внимание, что этот шаблон регулярного выражения будет работать так же хорошо, если вы имеете дело со строкой, состоящей только из времени (с разделителями-двоеточиями). (Демо)

person mickmackusa    schedule 19.09.2019

person    schedule
comment
Я обнаружил, что если время равно 10:59, оно округляется до 10:60. Чтобы решить эту проблему, я добавил: if ($minutes == 60) { $rounded = $date_split['hour']+1; $округлено .= :00; } else { $minutes = str_pad($minutes, 2, '0', STR_PAD_LEFT); $rounded = $date_split['час'].:.$минуты; } также str_pad, чтобы везде начальный нуль. Это решение может быть оптимальным, но работает нормально. - person TCB13; 02.05.2012

person    schedule
comment
Итак, 1:19 становится (19%15)*15 = 4*15 = 60!? - person Richard JP Le Guen; 20.03.2010
comment
Спасибо, Ричард. Моя голова где-то в другом месте. - person Scott Saunders; 20.03.2010

person    schedule
comment
Это сработало, как и требовалось. 11:29 переходит в 11:30, 11:22 переходит в 11:15. Это лучше, чем решение Wickethewok. Я изменил значение по умолчанию для округления до минут на 15, поскольку заказчик попросил округлить до четверти часа. Я также изменил вывод на g:i:s, что соответствует моей конкретной потребности. - person Johnish; 26.08.2016