Take the 2-minute tour ×
Stack Apps is a question and answer site for apps, scripts, and development with the Stack Exchange API. It's 100% free, no registration required.
function timeSince(date) {

    var seconds = Math.floor((new Date() - date) / 1000);

    var interval = Math.floor(seconds / 31536000);

    if (interval > 1) {
        return interval + " years";
    }
    interval = Math.floor(seconds / 2592000);
    if (interval > 1) {
        return interval + " months";
    }
    interval = Math.floor(seconds / 86400);
    if (interval > 1) {
        return interval + " days";
    }
    interval = Math.floor(seconds / 3600);
    if (interval > 1) {
        return interval + " hours";
    }
    interval = Math.floor(seconds / 60);
    if (interval > 1) {
        return interval + " minutes";
    }
    return Math.floor(seconds) + " seconds";
}

Post an implementation in your language of choice.

share|improve this question
    
Is there a question? This is more suitable for Stack Overflow. –  Dennis Williamson Jul 5 '10 at 3:34
    
It's a dev-tip... maybe we should play Jepoardy here as well? –  Mark Henderson Jul 5 '10 at 3:48
1  
no, there is no question. it is a tip on how to present dates in a friendly format, similar to that used by the stack exchange sites, whose data we are consuming via the api. If you wanted to format a list of items that have dates, as most do, this might be useful information. I know I had to spend some time figuring it out, no need for others. I am not sure why or how I would post this on stack overflow and am sure that it would not be discoverable. –  Sky Sanders Jul 5 '10 at 3:48
1  
@code poet: I'd still post on Stack Overflow and try to give it a good title. This is something lots of people will find useful at some point. –  Edan Maor Jul 5 '10 at 7:31
    
@Edan - you are probably right. I will, although I want this post to remain in the relative quiet and more contextually appropriate stackapps as well, so hopefully it won't suffer 'exact duplicate' fate. dupe cops miss the point sometimes. –  Sky Sanders Jul 5 '10 at 7:52
    
Follow-up question: There are some slight errors on the elapsed times displayed on the site due to treating all months uniformly as 30 days and ignoring leap years. Do you think it's a good idea to keep these errors to match the site (see my answer) or should we try to fix them? –  Bill the Lizard Jul 7 '10 at 4:19
1  
@bill - month is pretty low resolution and the effort doesn't seem worth it. In any case, as george has pointed out, anything past 2 days is a formatted date. I just have not gotten around to porting his php to javascript, which I plan to do. –  Sky Sanders Jul 7 '10 at 4:29
add comment

7 Answers 7

For all the jQuery developers, there is - as always - a decent plugin available: timeago...

Bonus: comes with auto-refresh, supports a semantic approach and is kind of progressive enhancement-ish, since it will turn something like

<abbr class="timeago" title="2008-07-17T09:24:17Z">July 17, 2008</abbr>

into timestamps like

about a year ago

As ususal, one line of jQuery does the job...

jQuery(document).ready(function() {
    jQuery("abbr.timeago").timeago();
});

Having that said, I want to emphasize that I don't know the author of this plugin in any way and don't get paid for making propaganda for it =)

share|improve this answer
1  
+ thanks for that. –  Sky Sanders Jul 5 '10 at 8:20
    
I use this as the primary method, and I use my method below for server side calculations... for users without JS enabled. –  Chase Florell Nov 15 '10 at 19:33
add comment

Here's a Java implementation that was heavily influenced by the answers on the SO question How do I calculate relative time?, particularly @Jeff's and the accepted answer by @Vincent Robert.

/**
 * Formats the time elapsed between fromDate and now.
 * See http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time/
 * 
 * @param fromDate the start time, a unix epoch datetime stamp.
 * @return the formatted time elapsed.
 */
public static String formatElapsedTime(long fromDate) {
    int SECOND = 1;
    int MINUTE = 60 * SECOND;
    int HOUR = 60 * MINUTE;
    int DAY = 24 * HOUR;
    int MONTH = 30 * DAY;

    long now = (new Date()).getTime();
    long delta = (now / 1000) - fromDate;

    if (delta < 1 * MINUTE) {
        return delta == 1 ? "one second ago" : delta + " seconds ago";
    }
    if (delta < 2 * MINUTE) {
        return "a minute ago";
    }
    if (delta < 45 * MINUTE) {
        return (delta / MINUTE) + " minutes ago";
    }
    if (delta < 90 * MINUTE) {
        return "an hour ago";
    }
    if (delta < 24 * HOUR) {
        return (delta / HOUR) + " hours ago";
    }
    if (delta < 48 * HOUR) {
        return "yesterday";
    }
    if (delta < 30 * DAY) {
        return (delta / DAY) + " days ago";
    }
    if (delta < 12 * MONTH) {
        int months = (int)(Math.floor((double) ((delta / DAY) / 30)));
        return months <= 1 ? "one month ago" : months + " months ago";
    } else {
        int years = (int)(Math.floor((double) (delta / DAY) / 365));
        return years <= 1 ? "one year ago" : years + " years ago";
    }
}

Here are some passing JUnit tests that show what kind of results you can expect.

@Test
public void testFormatElapsedTime() {
    long now = (new Date()).getTime() / 1000;

    assertEquals( "one second ago", formatElapsedTime(now - 1) );
    assertEquals( "2 seconds ago", formatElapsedTime(now - 2) );
    assertEquals( "30 seconds ago", formatElapsedTime(now - 30) );
    assertEquals( "59 seconds ago", formatElapsedTime(now - 59) );

    assertEquals( "a minute ago", formatElapsedTime(now - 60) );
    assertEquals( "a minute ago", formatElapsedTime(now - 61) );
    assertEquals( "a minute ago", formatElapsedTime(now - 119) );

    assertEquals( "2 minutes ago", formatElapsedTime(now - 120) );
    assertEquals( "2 minutes ago", formatElapsedTime(now - 121) );
    assertEquals( "2 minutes ago", formatElapsedTime(now - 179) );
    assertEquals( "3 minutes ago", formatElapsedTime(now - 180) );
    assertEquals( "44 minutes ago", formatElapsedTime(now - (44 * 60)) );

    assertEquals( "an hour ago", formatElapsedTime(now - (45 * 60)) );
    assertEquals( "an hour ago", formatElapsedTime(now - (89 * 60)) );
    assertEquals( "1 hours ago", formatElapsedTime(now - (90 * 60)) );
    assertEquals( "1 hours ago", formatElapsedTime(now - (119 * 60)) );
    assertEquals( "2 hours ago", formatElapsedTime(now - (120 * 60)) );
    assertEquals( "2 hours ago", formatElapsedTime(now - (179 * 60)) );
    assertEquals( "3 hours ago", formatElapsedTime(now - (180 * 60)) );
    assertEquals( "23 hours ago", formatElapsedTime(now - (23 * 60 * 60)) );

    assertEquals( "yesterday", formatElapsedTime(now - (24 * 60 * 60)) );
    assertEquals( "yesterday", formatElapsedTime(now - (47 * 60 * 60)) );
    assertEquals( "2 days ago", formatElapsedTime(now - (48 * 60 * 60)) );
    assertEquals( "3 days ago", formatElapsedTime(now - (3 * 24 * 60 * 60)) );
    assertEquals( "14 days ago", formatElapsedTime(now - (14 * 24 * 60 * 60)) );
    assertEquals( "29 days ago", formatElapsedTime(now - (29 * 24 * 60 * 60)) );

    assertEquals( "one month ago", formatElapsedTime(now - (30 * 24 * 60 * 60)) );
    assertEquals( "one month ago", formatElapsedTime(now - (59 * 24 * 60 * 60)) );
    assertEquals( "2 months ago", formatElapsedTime(now - (60 * 24 * 60 * 60)) );
    assertEquals( "2 months ago", formatElapsedTime(now - (89 * 24 * 60 * 60)));
    assertEquals( "3 months ago", formatElapsedTime(now - (3 * 30 * 24 * 60 * 60)) );
    assertEquals( "11 months ago", formatElapsedTime(now - (11 * 30 * 24 * 60 * 60)) );

    assertEquals( "one year ago", formatElapsedTime(now - (12 * 30 * 24 * 60 * 60)) );
    assertEquals( "one year ago", formatElapsedTime(now - (18 * 30 * 24 * 60 * 60)) );
    assertEquals( "one year ago", formatElapsedTime(now - (23 * 30 * 24 * 60 * 60)) );

    // a little bit of error in the calculation shows up here.
    assertEquals( "one year ago", formatElapsedTime(now - (24 * 30 * 24 * 60 * 60)) );
    assertEquals( "2 years ago", formatElapsedTime(now - (25 * 30 * 24 * 60 * 60)) );
}
share|improve this answer
    
your name in lights: stackapps.com/questions/1009/… –  Sky Sanders Aug 10 '10 at 7:44
    
"an hour ago" vs. "1 hours ago" ?? –  Juha Syrjälä Aug 13 '10 at 20:10
    
@Juha: Yeah, I'm not crazy about that either. It's an artifact of the way that "an hour ago" is defined as 45 - 90 minutes ago and anything more than that (within the last 24 hours) is displayed as an integer number of hours. I wanted to stay true to the original implementation though. –  Bill the Lizard Aug 13 '10 at 20:28
    
Actually SO does not currently use this algorithm. It says "1 hour ago" for 60-120 minutes ago. –  Juha Syrjälä Aug 13 '10 at 20:51
add comment

C# and Javascript port of Bill's answer

Works for me - adding it to both Soapi libraries. See the JS version in action here

One thing to keep in mind is that dates coming from the API are UTC. In order to get a viable diff, you need to convert your Now to UTC.

c#

    /// <summary>
    /// Formats the time elapsed between fromDate and now.
    /// See http://stackoverflow.com/questions/11/how-do-i-calculate-relative-time/
    /// and http://stackapps.com/questions/1009/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site/1034#1034
    /// </summary>
    /// <param name="fromDate">the start time</param>
    /// <returns>the formatted time elapsed.</returns>
    /// <remarks>A direct port of Bill the Lizards answer</remarks>
    public static string FormatElapsedTime(DateTime fromDate)
    {
        const int second = 1;
        const int minute = 60 * second;
        const int hour = 60 * minute;
        const int day = 24 * hour;
        const int month = 30 * day;


        int delta = (int) (DateTime.Now.ToUniversalTime() - fromDate).TotalSeconds;

        if (delta < 1 * minute)
        {
            return delta == 1 ? "one second ago" : delta + " seconds ago";
        }
        if (delta < 2 * minute)
        {
            return "a minute ago";
        }
        if (delta < 45 * minute)
        {
            return (delta / minute) + " minutes ago";
        }
        if (delta < 90 * minute)
        {
            return "an hour ago";
        }
        if (delta < 24 * hour)
        {
            return (delta / hour) + " hours ago";
        }
        if (delta < 48 * hour)
        {
            return "yesterday";
        }
        if (delta < 30 * day)
        {
            return (delta / day) + " days ago";
        }
        if (delta < 12 * month)
        {
            int months = (int) (Math.Floor(((delta / day) / 30)));
            return months <= 1 ? "one month ago" : months + " months ago";
        }

        int years = (int)(Math.Floor((delta / day) / 365));
        return years <= 1 ? "one year ago" : years + " years ago";
    }
}

JavaScript

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title></title>
    <script type="text/javascript">

        function formatElapsedTime(fromDate)
        {
            var second = 1;
            var minute = 60 * second;
            var hour = 60 * minute;
            var day = 24 * hour;
            var month = 30 * day;

            var delta = (new Date() - fromDate) / 1000;

            if (delta < 1 * minute)
            {
                return delta == 1 ? "one second ago" : Math.floor(delta) + " seconds ago";
            }
            if (delta < 2 * minute)
            {
                return "a minute ago";
            }
            if (delta < 45 * minute)
            {
                return Math.floor(delta / minute) + " minutes ago";
            }
            if (delta < 90 * minute)
            {
                return "an hour ago";
            }
            if (delta < 24 * hour)
            {
                return Math.floor(delta / hour) + " hours ago";
            }
            if (delta < 48 * hour)
            {
                return "yesterday";
            }
            if (delta < 30 * day)
            {
                return Math.floor(delta / day) + " days ago";
            }
            if (delta < 12 * month)
            {
                var months = Math.floor((delta / day) / 30);
                return months <= 1 ? "one month ago" : months + " months ago";
            }
            var years = Math.floor((delta / day) / 365);
            return years <= 1 ? "one year ago" : years + " years ago";
        }


        function tests()
        {

            function assertEquals(expected, actual)
            {
                var equals = expected == actual;
                var output = document.getElementById("output");
                var div = document.createElement("div");
                div.innerHTML = (equals ? "passed" : "FAILED") + " : expected " + expected + " and got " + actual;
                output.appendChild(div)
            }

            var now = (new Date()).getTime();

            assertEquals("one second ago", formatElapsedTime(now - 1000));
            assertEquals("2 seconds ago", formatElapsedTime(now - 2000));
            assertEquals("30 seconds ago", formatElapsedTime(now - 30000));
            assertEquals("59 seconds ago", formatElapsedTime(now - 59000));

            assertEquals("a minute ago", formatElapsedTime(now - 60000));
            assertEquals("a minute ago", formatElapsedTime(now - 61000));
            assertEquals("a minute ago", formatElapsedTime(now - 119000));

            assertEquals("2 minutes ago", formatElapsedTime(now - 120000));
            assertEquals("2 minutes ago", formatElapsedTime(now - 121000));
            assertEquals("2 minutes ago", formatElapsedTime(now - 179000));
            assertEquals("3 minutes ago", formatElapsedTime(now - 180000));
            assertEquals("44 minutes ago", formatElapsedTime(now - (44 * 60000)));

            assertEquals("an hour ago", formatElapsedTime(now - (45 * 60000)));
            assertEquals("an hour ago", formatElapsedTime(now - (89 * 60000)));
            assertEquals("1 hours ago", formatElapsedTime(now - (90 * 60000)));
            assertEquals("1 hours ago", formatElapsedTime(now - (119 * 60000)));
            assertEquals("2 hours ago", formatElapsedTime(now - (120 * 60000)));
            assertEquals("2 hours ago", formatElapsedTime(now - (179 * 60000)));
            assertEquals("3 hours ago", formatElapsedTime(now - (180 * 60000)));
            assertEquals("23 hours ago", formatElapsedTime(now - (23 * 60 * 60000)));

            assertEquals("yesterday", formatElapsedTime(now - (24 * 60 * 60000)));
            assertEquals("yesterday", formatElapsedTime(now - (47 * 60 * 60000)));
            assertEquals("2 days ago", formatElapsedTime(now - (48 * 60 * 60000)));
            assertEquals("3 days ago", formatElapsedTime(now - (3 * 24 * 60 * 60000)));
            assertEquals("14 days ago", formatElapsedTime(now - (14 * 24 * 60 * 60000)));
            assertEquals("29 days ago", formatElapsedTime(now - (29 * 24 * 60 * 60000)));

            assertEquals("one month ago", formatElapsedTime(now - (30 * 24 * 60 * 60000)));
            assertEquals("one month ago", formatElapsedTime(now - (59 * 24 * 60 * 60000)));
            assertEquals("2 months ago", formatElapsedTime(now - (60 * 24 * 60 * 60000)));
            assertEquals("2 months ago", formatElapsedTime(now - (89 * 24 * 60 * 60000)));
            assertEquals("3 months ago", formatElapsedTime(now - (3 * 30 * 24 * 60 * 60000)));
            assertEquals("11 months ago", formatElapsedTime(now - (11 * 30 * 24 * 60 * 60000)));

            assertEquals("one year ago", formatElapsedTime(now - (12 * 30 * 24 * 60 * 60000)));
            assertEquals("one year ago", formatElapsedTime(now - (18 * 30 * 24 * 60 * 60000)));
            assertEquals("one year ago", formatElapsedTime(now - (23 * 30 * 24 * 60 * 60000)));

            // a little bit of error in the calculation shows up here.
            assertEquals("one year ago", formatElapsedTime(now - (24 * 30 * 24 * 60 * 60000)));
            assertEquals("2 years ago", formatElapsedTime(now - (25 * 30 * 24 * 60 * 60000)));
        }
    </script>
</head>
<body onload="tests()">
    <div id="output">
    </div>
</body>
</html>

Test Results

passed : expected one second ago and got one second ago
passed : expected 2 seconds ago and got 2 seconds ago
passed : expected 30 seconds ago and got 30 seconds ago
passed : expected 59 seconds ago and got 59 seconds ago
passed : expected a minute ago and got a minute ago
passed : expected a minute ago and got a minute ago
passed : expected a minute ago and got a minute ago
passed : expected 2 minutes ago and got 2 minutes ago
passed : expected 2 minutes ago and got 2 minutes ago
passed : expected 2 minutes ago and got 2 minutes ago
passed : expected 3 minutes ago and got 3 minutes ago
passed : expected 44 minutes ago and got 44 minutes ago
passed : expected an hour ago and got an hour ago
passed : expected an hour ago and got an hour ago
passed : expected 1 hours ago and got 1 hours ago
passed : expected 1 hours ago and got 1 hours ago
passed : expected 2 hours ago and got 2 hours ago
passed : expected 2 hours ago and got 2 hours ago
passed : expected 3 hours ago and got 3 hours ago
passed : expected 23 hours ago and got 23 hours ago
passed : expected yesterday and got yesterday
passed : expected yesterday and got yesterday
passed : expected 2 days ago and got 2 days ago
passed : expected 3 days ago and got 3 days ago
passed : expected 14 days ago and got 14 days ago
passed : expected 29 days ago and got 29 days ago
passed : expected one month ago and got one month ago
passed : expected one month ago and got one month ago
passed : expected 2 months ago and got 2 months ago
passed : expected 2 months ago and got 2 months ago
passed : expected 3 months ago and got 3 months ago
passed : expected 11 months ago and got 11 months ago
passed : expected one year ago and got one year ago
passed : expected one year ago and got one year ago
passed : expected one year ago and got one year ago
passed : expected one year ago and got one year ago
passed : expected 2 years ago and got 2 years ago
share|improve this answer
add comment

Here's my code from Py-StackExchange, in Python:

def format_relative_date(date):
    """Takes a datetime object and returns the date formatted as a string e.g. "3 minutes ago", like the real site.
    This is based roughly on George Edison's code from StackApps:
    http://stackapps.com/questions/1009/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site/1018#1018"""

    now = datetime.now()
    diff = (now - date).seconds

    # Anti-repetition! These simplify the code somewhat.
    plural = lambda d: 's' if d != 1 else ''
    frmt   = lambda d: (diff / float(d), plural(diff / float(d)))

    if diff < 60:
        return '%d second%s ago' % frmt(1)
    elif diff < 3600:
        return '%d minute%s ago' % frmt(60)
    elif diff < 86400:
        return '%d hour%s ago' % frmt(3600)
    elif diff < 172800:
        return 'yesterday'
    else:
        return date.strftime('M j / y - H:i')
share|improve this answer
2  
thanks for that. –  Sky Sanders Aug 12 '10 at 21:22
add comment

Here's a quick and dirty way of how I did mine in .NET

Visual Basic

    ''# <summary>
    ''# Return how long in the past something happend
    ''# </summary>
    ''# <param name="input">The date of an event in UTC time</param>
    ''# <param name="append">append characters to the end of the string 
    ''# example: "suffixAgo"</param>
    ''# <returns>a logical string "10 mins ago"</returns>
    ''# <remarks>This function is primarily used in Views to format Model data.</remarks>
    Public Shared Function GetDateRange(ByVal input As DateTime, Optional ByVal suffixAgo As String = Nothing) As String
        Dim output As String
        Dim theDate As DateTime = dt
        Dim DateInterval As TimeSpan = DateTime.UtcNow - theDate
        Dim SecondsAppart As Integer = Math.Floor(DateInterval.TotalSeconds)
        If (SecondsAppart < 86400) Then
            If SecondsAppart < 59 Then
                output = ("less than a minute " + suffixAgo)
            ElseIf SecondsAppart < 119 Then
                output = ("about a minute " + suffixAgo)
            ElseIf SecondsAppart < 3599 Then
                output = String.Format("{0} minutes {1}", Math.Floor(DateInterval.TotalMinutes), suffixAgo)
            ElseIf SecondsAppart < 7199 Then
                output = "about an hour " + suffixAgo
            Else
                output = String.Format("{0} hours {1}", Math.Floor(DateInterval.TotalHours), suffixAgo)
            End If
        Else
            Dim DaysAppart As Integer = Math.Floor(DateInterval.TotalDays)
            If DaysAppart <= 1 Then
                output = "yesterday"
            ElseIf DaysAppart < 30 Then
                output = String.Format("{0} days {1}", Math.Floor(DateInterval.TotalDays), suffixAgo)
            ElseIf DaysAppart < 60 Then
                output = "about a month " + suffixAgo
            ElseIf DaysAppart < 365 Then
                output = String.Format("{0} months {1}", Math.Floor(DateInterval.TotalDays / 30), suffixAgo)
            ElseIf DaysAppart < 730 Then
                output = ("about a year " + suffixAgo)
            Else
                output = String.Format("{0} years {1}", Math.Floor(DateInterval.TotalDays / 365), suffixAgo)
            End If
        End If 

            Return output
    End Function
share|improve this answer
    
+1 thanks for that. –  Sky Sanders Jul 5 '10 at 15:52
add comment

Here is a similar function in PHP:

function Pluralize($amount, $unit)
{
    if($amount == 1)
        return "$amount $unit ago";
    else
        return "$amount {$unit}s ago";
}

function RelativeTime($timestamp)
{
    // Calculate the difference
    $current_time = time();
    $difference = $current_time - $timestamp;

    if($difference == 0)
        return 'just now';
    elseif($difference < 60)
        return Pluralize($difference, 'second');
    elseif($difference < self::Hour)
        return Pluralize(floor($difference / 60), 'minute');
    elseif($difference < self::Day)
        return Pluralize(floor($difference / self::Hour), 'hour');
    elseif($difference < (self::Day * 2))
        return 'yesterday';
    elseif($difference < self::Month)
        return Pluralize(floor($difference / self::Day), 'day');
    elseif($difference < self::Year)
        return Pluralize(floor($difference / self::Month), 'month');
    else
        return Pluralize(floor($difference / self::Year), 'year');
}

Note: this is borrowed from my Stack.PHP wrapper.

share|improve this answer
1  
thats good. is there a link to php date format strings? i would like to refactor the JS version. –  Sky Sanders Jul 5 '10 at 16:53
    
@code: php.net/manual/en/function.date.php –  Nathan Osman Jul 5 '10 at 17:43
add comment

Looks good. I did a very similar thing for stack.PHP, but it wasn't as elagant.

Is this code public domain?

P.S. On SO, it would look something like this:

  • 0 sec. ago - 59 sec. ago
  • 1 min. ago - 59 min. ago
  • 1 hr. ago - 23 hr. ago
  • yesterday
  • 2 days ago
  • MM DD
  • MM DD, YY
share|improve this answer
1  
as per my profile: All code posted is tested and works on my machine. Unless otherwise noted, it is public domain and you are free to do with as you please. –  Sky Sanders Jul 5 '10 at 4:49
    
Ah, well... thank you! –  Nathan Osman Jul 5 '10 at 5:07
    
post your implementation, that is what this post is about. –  Sky Sanders Jul 5 '10 at 16:03
    
yup, let's see the PHP. –  Chase Florell Jul 15 '10 at 19:22
    
@rock: See my answer above. –  Nathan Osman Jul 15 '10 at 20:10
add comment

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .