32

Ok so I am working on a calendar application within my CRM system and I need to find the upper and lower bounds of the half an hour surrorunding the timestamp at which somebody entered an event in the calendar in order to run some SQL on the DB to determine if they already have something booked in within that timeslot.

For example I have the timestamp of 1330518155 = 29 February 2012 16:22:35 GMT+4 so I need to get 1330516800 and 1330518600 which equal 16:00 and 16:30.

If anyone has any ideas or think I am approaching developing the calendar in a stupid way let me know! Its my first time on such a task involving so much work with times and dates so any advice appreciated!

Ash
  • 397
  • 1
  • 3
  • 10
  • try to use the function in manual - http://php.net/manual/en/function.strtotime.php - should be helpful, you can add or remove any amount of time from timestamp very easy. – Kamil Mar 09 '12 at 19:13
  • what does this have to do with times and dates? it's simple math – miki Mar 09 '12 at 19:14

11 Answers11

91

Use modulo.

$prev = 1330518155 - (1330518155 % 1800);
$next = $prev + 1800;

The modulo operator gives the remainder part of division.

SenorAmor
  • 3,351
  • 16
  • 27
  • That will find seconds since the hour, but that's only part of the problem, because if the minutes are >= 30, the time bracket is H:30 to H+1:00 – dj_segfault Mar 09 '12 at 20:31
  • 1
    Since we're dividing by 1800, it will give seconds since the last half hour increment (1:00, 1:30, 2:00, 2:30, etc...). If it's 1:30:01, the modulo operation will return 1 and $prev will be 1:30. Adding 1800 seconds will make $next equal to 2:00. Similarly, if it's 1:29:59, the modulo will be 1799 and $prev will be 1:00. Again, adding 1800 will make $next 1:30. – SenorAmor Mar 09 '12 at 20:56
  • 2
    So simple sometimes you just try to over think things and look for a function to do some basic arithmetic! – Ash Mar 10 '12 at 11:44
  • I really like how elegant this is. Not only is it done with only arithmetic, it will also work regardless of time zone (except in the Chatham Islands and Nepal, which are the only two time zones off by 15 minutes instead of 30). – Jazz Aug 24 '12 at 17:55
  • Modulneto! hahahahaha – Adam Fowler Sep 03 '13 at 17:09
  • For other users coming across this via Google, beware that Unix timestamps are seconds since Midnight Jan 1 1970, UTC. If your application is in a timezone that is not a multiple of 30mins different from UTC, this won't work on its own. There are not many, but they do exist. – Dezza Aug 04 '16 at 15:27
  • Hour is not equal to 3600 seconds. It usually is, but not always. An hour can have between 3599-3601 seconds. Welcome to Hell. – matt Dec 04 '20 at 10:04
6

I didn't read the questions clearly, but this code will round to the nearest half hour, for those who don't need the range between the two. Uses some of SenorAmor's code. Props and his mad elegant solution to the correct question.

$time = 1330518155; //Or whatever your time is in unix timestamp

//Store how many seconds long our rounding interval is
//1800 equals one half hour
//Change this to whatever interval to round by
$INTERVAL_SECONDS = 1800;  //30*60

//Find how far off the prior interval we are
$offset = ($time % $INTERVAL_SECONDS); 

//Removing this offset takes us to the "round down" half hour
$rounded = $time - $offset; 

//Now add the full interval if we should have rounded up
if($offset > ($INTERVAL_SECONDS/2)){
  $nearestInterval = $rounded + $INTERVAL_SECONDS;
}
else{
  $nearestInterval = $rounded 
}
Eric G
  • 3,427
  • 5
  • 28
  • 52
4

You could use the modulo operator.

$time -= $time % 3600; // nearest hour (always rounds down)

Hopefully this is enough to point you in the right direction, if not please add a comment and I'll try to craft a more specific example.

Chris Browne
  • 1,582
  • 3
  • 15
  • 33
3

PHP does have a DateTime class and a whole slough of methods that it provides. You could use these if you like, but I find it easier to use the built-in date() and strtotime() functions.

Here's my solution:

// Assume $timestamp has the original timestamp, i.e. 2012-03-09 16:23:41

$day = date( 'Y-m-d', $timestamp ); // $day is now "2012-03-09"
$hour = (int)date( 'H', $timestamp ); // $hour is now (int)16
$minute = (int)date( 'i', $timestamp ); // $minute is now (int)23

if( $minute < 30 ){
  $windowStart = strtotime( "$day $hour:00:00" );
  $windowEnd   = strtotime( "$day $hour:30:00" );
} else {
  $windowStart = strtotime( "$day $hour:30:00" );
  if( ++$hour > 23 ){
    // if we crossed midnight, fix the date and set the hour to 00
    $day = date( 'Y-m-d', $timestamp + (24*60*60) );
    $hour = '00';
  }
  $windowEnd   = strtotime( "$day $hour:00:00" );
}

// Now $windowStart and $windowEnd are the unix timestamps of your endpoints

There are a few improvements that can be made on this, but that's the basic core.

[Edit: corrected my variable names!]

[Edit: I've revisited this answer because, to my embarrassment, I realized that it didn't handle the last half-hour of a day correctly. I've fixed that issue. Note that $day is fixed by adding a day's worth of seconds to the timestamp -- doing it this way means we don't have to worry about crossing month boundaries, leap days, etc. because PHP will format it correctly for us regardless.]

Jazz
  • 1,435
  • 1
  • 15
  • 23
1

If you need to get the current time and then apply the rounding (down) of the time, I would do the following:

$now = date('U');
$offset = ($now % 1800);
$now = $now-$offset;
for ($i = 0;$i < 24; $i++)
{
    echo date('g:i',$now);
    $now += 1800;
}

Or you could round up by adding the offset, and do something more than just echo the time. The for loop then displays the 12 hours of increments. I used the above in a recent project.

royjm
  • 107
  • 14
0

Far from my best work... but here's some functions for working with string or unix time stamp.

/**
 * Takes a timestamp like "2016-10-01 17:59:01" and returns "2016-10-01 18:00"
 * Note: assumes timestamp is in UTC
 * 
 * @param $timestampString - a valid string which will be converted to unix with time()
 * @param int $mins - interval to round to (ex: 15, 30, 60);
 * @param string $format - the format to return the timestamp default is Y-m-d H:i
 * @return bool|string
 */
function roundTimeString( $timestampString, $mins = 30, $format="Y-m-d H:i") {
    return gmdate( $format, roundTimeUnix( time($timestampString), $mins ));
}

/**
 * Rounds the time to the nearest minute interval, example: 15 would round times to 0, 15, 30,45
 * if $mins = 60, 1:00, 2:00
 * @param $unixTimestamp
 * @param int $mins
 * @return mixed
 */
function roundTimeUnix( $unixTimestamp, $mins = 30 ) {
    $roundSecs = $mins*60;
    $offset = $unixTimestamp % $roundSecs;
    $prev = $unixTimestamp - $offset;
    if( $offset > $roundSecs/2 ) {
        return $prev + $roundSecs;
    }
    return $prev;
}
blak3r
  • 16,066
  • 16
  • 78
  • 98
0

This is a solution using DateTimeInterface and keeping timezone information etc. Will also handle timezones that are not a multiple of 30 minutes offset from GMT (e.g. Asia/Kathmandu).

/**
 * Return a DateTimeInterface object that is rounded down to the nearest half hour.
 * @param \DateTimeInterface $dateTime
 * @return \DateTimeInterface
 * @throws \UnexpectedValueException if the $dateTime object is an unknown type
 */
function roundToHalfHour(\DateTimeInterface $dateTime)
{
    $hours = (int)$dateTime->format('H');
    $minutes = $dateTime->format('i');

    # Round down to the last half hour period
    $minutes = $minutes >= 30 ? 30 : 0;

    if ($dateTime instanceof \DateTimeImmutable) {
        return $dateTime->setTime($hours, $minutes);
    } elseif ($dateTime instanceof \DateTime) {
        // Don't change the object that was passed in, but return a new object
        $dateTime = clone $dateTime;
        $dateTime->setTime($hours, $minutes);
        return $dateTime;
    }
    throw new UnexpectedValueException('Unexpected DateTimeInterface object');
}

You'll need to have created the DateTime object first though - perhaps with something like $dateTime = new DateTimeImmutable('@' . $timestamp). You can also set the timezone in the constructor.

Dezza
  • 1,094
  • 4
  • 22
  • 25
0

Math.round(timestamp/1800)*1800

Henry G
  • 13
  • 3
0

I'd use the localtime and the mktime function.

$localtime = localtime($time, true);
$localtime['tm_sec'] = 0;
$localtime['tm_min'] = 30;
$time = mktime($localtime);
Sylvain Defresne
  • 42,429
  • 12
  • 75
  • 85
-1

Here's a more semantic method for those that have to make a few of these, perhaps at certain times of the day.

$time = strtotime(date('Y-m-d H:00:00'));

You can change that H to any 0-23 number, so you can round to that hour of that day.

Vael Victus
  • 3,966
  • 7
  • 34
  • 55
-1

As you probably know, a UNIX timestamp is a number of seconds, so substract/add 1800 (number of seconds in 30 minutes) and you will get the desired result.

Pateman
  • 2,727
  • 3
  • 28
  • 43