Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Display relative time in hour, day, month and year

Tags:

algorithm

time

I wrote a function

toBeautyString(epoch) : String

which given a epoch, return a string which will display the relative time from now in hour and minute

For instance:

// epoch: 1346140800 -> Tue, 28 Aug 2012 05:00:00 GMT 
// and now: 1346313600 -> Thu, 30 Aug 2012 08:00:00 GMT
toBeautyString(1346140800) 
-> "2 days and 3 hours ago"

I want now to extend this function to month and year, so it will be able to print:

2 years, 1 month, 3 days and 1 hour ago

Only with epoch without any external libraries. The purpose of this function is to give to the user a better way to visualize the time in the past.

I found this: Calculate relative time in C# but the granularity is not enough.

function toBeautyString(epochNow, epochNow){
    var secDiff = Math.abs(epochNow - epochNow);
    var milliInDay = 1000 * 60 * 60 * 24;
    var milliInHour = 1000 * 60 * 60;

    var nbDays = Math.round(secDiff/milliInDay);
    var nbHour = Math.round(secDiff/milliInHour);

    var relativeHour = (nbDays === 0) ? nbHour : nbHour-(nbDays*24);
    relativeHour %= 24;

    if(nbHour === 0){
        nbDays += 1;
    }else if(nbHour === (nbDays-1)*24){
        nbDays -= 1;
    }

    var dayS = (nbDays > 1) ? "days" : "day";
    var hourS = (relativeHour > 1) ? "hours" : "hour";

    var fullString = "";

    if(nbDays > 0){
        fullString += nbDays + " " + dayS;
        if(relativeHour > 0)
            fullString += " ";
    }

    if(relativeHour > 0){
        fullString += relativeHour + " " + hourS;
    }

    if(epochDate > epochNow){
        return "Will be in " + fullString;
    }else if ((epochDate === epochNow) 
            || (relativeHour === 0 && nbDays === 0)){
        return "Now";
    }else{
        return fullString + " ago";         
    }
}
like image 537
JohnJohnGa Avatar asked Aug 31 '12 15:08

JohnJohnGa


1 Answers

It's helpful to recognize this as two distinct problems: 1) slicing the time into individual chunks of varying units; 2) formatting the chunks and joining them together with your choice of commas, conjunctions, etc. That way, you keep your text formatting logic separate from your time calculation logic.

#converts a time amount into a collection of time amounts of varying size.
#`increments` is a list that expresses the ratio of successive time units
#ex. If you want to split a time into days, hours, minutes, and seconds,
#increments should be [24,60,60]
#because there are 24 hours in a day, 60 minutes in an hour, etc.
#as an example, divideTime(100000, [24,60,60]) returns [1,3,46,40], 
#which is equivalent to 1 day, 3 hours, 46 minutes, 40 seconds
def divideTime(amount, increments):
    #base case: there's no increments, so no conversion is necessary
    if len(increments) == 0:
        return [amount]
    #in all other cases, we slice a bit off of `amount`,
    #give it to the smallest increment,
    #convert the rest of `amount` into the next largest unit, 
    #and solve the rest with a recursive call.
    else:
        conversionRate = increments[-1]
        smallestIncrement = amount % conversionRate
        rest = divideTime(amount / conversionRate, increments[:-1])
        return rest + [smallestIncrement]

def beautifulTime(amount):
    names      = ["year", "month", "day", "hour", "minute", "second"]
    increments = [12,     30,      24,    60,     60]
    ret = []
    times = divideTime(amount, increments)
    for i in range(len(names)):
        time = times[i]
        name = names[i]
        #don't display the unit if the time is zero
        #e.g. we prefer "1 year 1 second" to 
        #"1 year 0 months 0 days 0 hours 0 minutes 1 second"
        if time == 0:
            continue
        #pluralize name if appropriate
        if time != 1:
            name = name + "s"
        ret.append(str(time) + " " + name)
    #there's only one unit worth mentioning, so just return it
    if len(ret) == 1:
        return ret[0]
    #when there are two units, we don't need a comma
    if len(ret) == 2:
        return "{0} and {1}".format(ret[0], ret[1])
    #for all other cases, we want a comma and an "and" before the last unit
    ret[-1] = "and " + ret[-1]
    return ", ".join(ret)

print beautifulTime(100000000)
#output: 3 years, 2 months, 17 days, 9 hours, 46 minutes, and 40 seconds

This solution is somewhat inaccurate with regards to real-life years because it assumes a year is made up of 12 months, each 30 days long. This is a necessary abstraction, or otherwise you'd have to factor in varying month lengths and leap days and daylight savings time, etc etc etc. With this method, you'll lose about 3.75 days per year, which isn't so bad if you're only using it to visualize the magnitude of time spans.

like image 101
Kevin Avatar answered Sep 18 '22 13:09

Kevin