PHP: producing relative date/time from timestamps

2019-01-01 04:26发布

问题:

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?

回答1:

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


回答2:

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


回答3:

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


回答4:

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



回答5:

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


回答6:

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



回答7:

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


回答8:

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