33

I'm basically trying to convert a Unix timestamp (the time() function) to a relative date/time that's both compatible with past and future date. So outputs could be:

2 weeks ago

1 hour and 60 minutes ago

15 minutes and 54 seconds ago

after 10 minutes and 15 seconds

First I tried to code this, but made a huge unmaintainable function, and then I searched the internet for a couple of hours, yet all I can find are scripts that produce only one part of the time (e.h: "1 hour ago" without the minutes).

Do you have a script that already does this?

Community
  • 1
  • 1
KeyStroke
  • 1,455
  • 5
  • 19
  • 24
  • Perhaps this can help you: http://stackoverflow.com/questions/2643113/convert-2010-04-16-163000-to-tomorrow-afternoon/2643137 – nikc.org Apr 22 '10 at 11:57
  • 1
    possible duplicate of [How do I calculate relative time?](http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time) – hakre Nov 05 '11 at 22:42

10 Answers10

62

This function gives you "1 hour ago" or "Tomorrow" like results between 'now' and 'specific timestamp'.

function time2str($ts)
{
    if(!ctype_digit($ts))
        $ts = strtotime($ts);

    $diff = time() - $ts;
    if($diff == 0)
        return 'now';
    elseif($diff > 0)
    {
        $day_diff = floor($diff / 86400);
        if($day_diff == 0)
        {
            if($diff < 60) return 'just now';
            if($diff < 120) return '1 minute ago';
            if($diff < 3600) return floor($diff / 60) . ' minutes ago';
            if($diff < 7200) return '1 hour ago';
            if($diff < 86400) return floor($diff / 3600) . ' hours ago';
        }
        if($day_diff == 1) return 'Yesterday';
        if($day_diff < 7) return $day_diff . ' days ago';
        if($day_diff < 31) return ceil($day_diff / 7) . ' weeks ago';
        if($day_diff < 60) return 'last month';
        return date('F Y', $ts);
    }
    else
    {
        $diff = abs($diff);
        $day_diff = floor($diff / 86400);
        if($day_diff == 0)
        {
            if($diff < 120) return 'in a minute';
            if($diff < 3600) return 'in ' . floor($diff / 60) . ' minutes';
            if($diff < 7200) return 'in an hour';
            if($diff < 86400) return 'in ' . floor($diff / 3600) . ' hours';
        }
        if($day_diff == 1) return 'Tomorrow';
        if($day_diff < 4) return date('l', $ts);
        if($day_diff < 7 + (7 - date('w'))) return 'next week';
        if(ceil($day_diff / 7) < 4) return 'in ' . ceil($day_diff / 7) . ' weeks';
        if(date('n', $ts) == date('n') + 1) return 'next month';
        return date('F Y', $ts);
    }
}
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
osm
  • 4,186
  • 3
  • 23
  • 24
15
function relativeTime($time) {

    $d[0] = array(1,"second");
    $d[1] = array(60,"minute");
    $d[2] = array(3600,"hour");
    $d[3] = array(86400,"day");
    $d[4] = array(604800,"week");
    $d[5] = array(2592000,"month");
    $d[6] = array(31104000,"year");

    $w = array();

    $return = "";
    $now = time();
    $diff = ($now-$time);
    $secondsLeft = $diff;

    for($i=6;$i>-1;$i--)
    {
         $w[$i] = intval($secondsLeft/$d[$i][0]);
         $secondsLeft -= ($w[$i]*$d[$i][0]);
         if($w[$i]!=0)
         {
            $return.= abs($w[$i]) . " " . $d[$i][1] . (($w[$i]>1)?'s':'') ." ";
         }

    }

    $return .= ($diff>0)?"ago":"left";
    return $return;
}

Usage:

echo relativeTime((time()-256));
4 minutes 16 seconds ago
xdebug
  • 1,178
  • 12
  • 16
11

Here is what I've written. Displays a past date relative to today's date.

/**
 * @param $date integer of unixtimestamp format, not actual date type
 * @return string
 */
function zdateRelative($date)
{
    $now = time();
    $diff = $now - $date;

    if ($diff < 60){
        return sprintf($diff > 1 ? '%s seconds ago' : 'a second ago', $diff);
    }

    $diff = floor($diff/60);

    if ($diff < 60){
        return sprintf($diff > 1 ? '%s minutes ago' : 'one minute ago', $diff);
    }

    $diff = floor($diff/60);

    if ($diff < 24){
        return sprintf($diff > 1 ? '%s hours ago' : 'an hour ago', $diff);
    }

    $diff = floor($diff/24);

    if ($diff < 7){
        return sprintf($diff > 1 ? '%s days ago' : 'yesterday', $diff);
    }

    if ($diff < 30)
    {
        $diff = floor($diff / 7);

        return sprintf($diff > 1 ? '%s weeks ago' : 'one week ago', $diff);
    }

    $diff = floor($diff/30);

    if ($diff < 12){
        return sprintf($diff > 1 ? '%s months ago' : 'last month', $diff);
    }

    $diff = date('Y', $now) - date('Y', $date);

    return sprintf($diff > 1 ? '%s years ago' : 'last year', $diff);
}
esengineer
  • 9,514
  • 7
  • 45
  • 69
6

I love the relativeTime function by xdebug. Problem is I needed it to have some granularity.

In other words stop at seconds or minutes if I decide. So now,

echo fTime(strtotime('-23 hours 5 minutes 55 seconds'),0); 

would show,

23 hours, 5 minutes ago

Instead of

23 hours, 5 minutes, 55 seconds ago

I also wanted it to NOT go lower in the array if it reached one of the higher time amounts. So if it shows years, I only want to show years and months. So now,

echo fTime(strtotime('-1 year 2 months 3 weeks 4 days 16 hours 15 minutes 22 seconds'),0); 

Would show

1 year, 2 months ago

Instead of

1 year, 2 months, 3 weeks, 4 days, 16 hours, 15 minutes, 22 seconds ago

The following code change did what I needed. Props go to xdebug first of course. Hopefully someone else might find it useful:

function fTime($time, $gran=-1) {

    $d[0] = array(1,"second");
    $d[1] = array(60,"minute");
    $d[2] = array(3600,"hour");
    $d[3] = array(86400,"day");
    $d[4] = array(604800,"week");
    $d[5] = array(2592000,"month");
    $d[6] = array(31104000,"year");

    $w = array();

    $return = "";
    $now = time();
    $diff = ($now-$time);
    $secondsLeft = $diff;
    $stopat = 0;
    for($i=6;$i>$gran;$i--)
    {
         $w[$i] = intval($secondsLeft/$d[$i][0]);
         $secondsLeft -= ($w[$i]*$d[$i][0]);
         if($w[$i]!=0)
         {
            $return.= abs($w[$i]) . " " . $d[$i][1] . (($w[$i]>1)?'s':'') ." ";
             switch ($i) {
                case 6: // shows years and months
                    if ($stopat==0) { $stopat=5; }
                    break;
                case 5: // shows months and weeks
                    if ($stopat==0) { $stopat=4; }
                    break;
                case 4: // shows weeks and days
                    if ($stopat==0) { $stopat=3; }
                    break;
                case 3: // shows days and hours
                    if ($stopat==0) { $stopat=2; }
                    break;
                case 2: // shows hours and minutes
                    if ($stopat==0) { $stopat=1; }
                    break;
                case 1: // shows minutes and seconds if granularity is not set higher
                    break;
             }
             if ($i===$stopat) { break 0; }
         }
    }

    $return .= ($diff>0)?"ago":"left";
    return $return;
}

Marcus

Marcus
  • 61
  • 1
  • 3
5

Here is what I use for past times:

function zdateRelative($date)
{
  $diff = time() - $date;
  $periods[] = [60, 1, '%s seconds ago', 'a second ago'];
  $periods[] = [60*100, 60, '%s minutes ago', 'one minute ago'];
  $periods[] = [3600*70, 3600, '%s hours ago', 'an hour ago'];
  $periods[] = [3600*24*10, 3600*24, '%s days ago', 'yesterday'];
  $periods[] = [3600*24*30, 3600*24*7, '%s weeks ago', 'one week ago'];
  $periods[] = [3600*24*30*30, 3600*24*30, '%s months ago', 'last month'];
  $periods[] = [INF, 3600*24*265, '%s years ago', 'last year'];
  foreach ($periods as $period) {
    if ($diff > $period[0]) continue;
    $diff = floor($diff / $period[1]);
    return sprintf($diff > 1 ? $period[2] : $period[3], $diff);
  }
}
William Entriken
  • 37,208
  • 23
  • 149
  • 195
3

I needed one to give me results as below, so I wrote my own. Hopefully, this will help somebody.

Example usage:

$datetime = "2014-08-13 12:52:48";  
echo getRelativeTime($datetime);    //10 hours ago  
echo getRelativeTime($datetime, 1); //10 hours ago  
echo getRelativeTime($datetime, 2); //10 hours and 50 minutes ago  
echo getRelativeTime($datetime, 3); //10 hours, 50 minutes and 50 seconds ago  
echo getRelativeTime($datetime, 4); //10 hours, 50 minutes and 50 seconds ago  

Code:

public function getRelativeTime($datetime, $depth=1) {

    $units = array(
        "year"=>31104000,
        "month"=>2592000,
        "week"=>604800,
        "day"=>86400,
        "hour"=>3600,
        "minute"=>60,
        "second"=>1
    );

    $plural = "s";
    $conjugator = " and ";
    $separator = ", ";
    $suffix1 = " ago";
    $suffix2 = " left";
    $now = "now";
    $empty = "";

    # DO NOT EDIT BELOW

    $timediff = time()-strtotime($datetime);
    if ($timediff == 0) return $now;
    if ($depth < 1) return $empty;

    $max_depth = count($units);
    $remainder = abs($timediff);
    $output = "";
    $count_depth = 0;
    $fix_depth = true;

    foreach ($units as $unit=>$value) {
        if ($remainder>$value && $depth-->0) {
            if ($fix_depth) {
                $max_depth -= ++$count_depth;
                if ($depth>=$max_depth) $depth=$max_depth;
                $fix_depth = false;
            }
            $u = (int)($remainder/$value);
            $remainder %= $value;
            $pluralise = $u>1?$plural:$empty;
            $separate = $remainder==0||$depth==0?$empty:
                            ($depth==1?$conjugator:$separator);
            $output .= "{$u} {$unit}{$pluralise}{$separate}";
        }
        $count_depth++;
    }
    return $output.($timediff<0?$suffix2:$suffix1);
}
Ozzy
  • 8,244
  • 7
  • 55
  • 95
2

You can use Carbon via packagist, just amazing :) https://github.com/briannesbitt/Carbon#api-humandiff

Hasin Hayder
  • 818
  • 7
  • 8
2

PHP 8.0.0 now has a pretty weak implementation of relative dates via IntlDateFormatter::format with the addition of the IntlDateFormatter::RELATIVE_* constants.

This likely isn't super useful at the time of writing as it only outputs the strings "yesterday", "today", and "tomorrow"... It then falls back to the full/long/medium/short dates for anything outside of those bounds.

The big drawcard for this is that it's fully internationalized; so using other locales such as de_DE, ko_KR, or pa_IN will give you translated strings in the relevant scripts... It could be worth the compromise.

$formatter = new IntlDateFormatter(
    'en_US',
    IntlDateFormatter::RELATIVE_FULL,
    IntlDateFormatter::NONE,
    'America/Los_Angeles',
    IntlDateFormatter::GREGORIAN
);

echo $formatter->format( time() - 86400 );
echo $formatter->format( time() );
echo $formatter->format( time() + 86400 );

It's also possible that readers from the future will be using newer version of PHP that have more comprehensive relative time formats too.

Andrew Odri
  • 8,868
  • 5
  • 46
  • 55
0

Why not rip off the way that drupal does it - http://api.drupal.org/api/drupal/includes%21common.inc/function/format_interval/7

<?php
function format_interval($interval, $granularity = 2, $langcode = NULL) {
  $units = array(
    '1 year|@count years' => 31536000, 
    '1 month|@count months' => 2592000, 
    '1 week|@count weeks' => 604800, 
    '1 day|@count days' => 86400, 
    '1 hour|@count hours' => 3600, 
    '1 min|@count min' => 60, 
    '1 sec|@count sec' => 1,
  );
  $output = '';
  foreach ($units as $key => $value) {
    $key = explode('|', $key);
    if ($interval >= $value) {
      $output .= ($output ? ' ' : '') . format_plural(floor($interval / $value), $key[0], $key[1], array(), array('langcode' => $langcode));
      $interval %= $value;
      $granularity--;
    }

    if ($granularity == 0) {
      break;
    }
  }
  return $output ? $output : t('0 sec', array(), array('langcode' => $langcode));
}
?>

You probably don't need a replacement for t() and you could do your own thing for format_plural pretty easily as you (probably) don't have to support multiple languages. http://api.drupal.org/api/drupal/includes%21common.inc/function/format_plural/7

David Meister
  • 3,941
  • 1
  • 26
  • 27
  • 1
    Wouldn't Drupal's function be protected under their copyright/open source license? – Ryan Leonard Mar 20 '13 at 19:29
  • @Rhino, it depends entirely on what you plan to do with your code. If you're not distributing the source code you can do what you like with the GPL code in Drupal. If you're distributing your source code (for money or free) then using a part of Drupal would affect the license you can redistribute under (ie. you'd have to distribute under a GPL license yourself) - http://drupal.org/licensing/faq. – David Meister May 19 '13 at 12:01
  • 1
    Yes, I understand the GPL, just pointing out that you might want to mention the limitation in the answer. – Ryan Leonard May 20 '13 at 09:40
0

Should be easy enough to adapt to different formats. This simple function only works with timestamps in the past.

// return relative date/time string from timestamp
// [n yrs] [n mos] [n days] h:i:s
function relative_time(int $time): string
{
        $dt = new DateTime();
        $dt->setTimestamp($time);
        $diff = (new DateTime())->diff($dt);

        $s = "";
        if ($diff->y) $s .= " {$diff->y} " . (($diff->y > 1) ? "yrs" : "yr");
        if ($diff->m) $s .= " {$diff->m} " . (($diff->m > 1) ? "mos" : "mo");
        if ($diff->d) $s .= " {$diff->d} " . (($diff->d > 1) ? "days" : "day");
        $s .= sprintf(" %02d:%02d:%02d", $diff->h, $diff->i, $diff->s);
        return trim($s);
}
gregjor
  • 21,802
  • 1
  • 23
  • 16