Display relative time in hours, day, month and year

I wrote a function

toBeautyString(epoch) : String 

which a epoch , returns a string that will display the relative time from now on in hour and minute

For example:

 // 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" 

Now I want to extend this function to a month and a year so that it can print:

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

Only with an era without any external libraries. The purpose of this feature is to provide the user with a better way to visualize time in the past.

I found this: Calculate relative time in C # , but 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"; } } 
+7
source share
5 answers

It is useful to recognize this as two different problems: 1) cut the time into separate pieces of different units; 2) formatting pieces and combining them together with your choice of commas, unions, etc. Thus, you keep the text formatting logic separate from the 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 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 #eg 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 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 decision is somewhat inaccurate with real years, since it assumes that the year is 12 months, every 30 days. This is a necessary abstraction, otherwise you will have to consider different months and jumping days, summer time, etc. Etc. Using this method, you will lose about 3.75 days a year, which is not so bad if you use it only to visualize the size of time intervals.

+2
source

You can use the DateDiff class from the Time Period Library for .NET to display relative time:

 // ---------------------------------------------------------------------- public void DateDiffSample( DateTime epoch ) { DateDiff dateDiff = new DateDiff( DateTime.Now, epoch ); Console.WriteLine( "{0} ago", dateDiff.GetDescription( 4 ) ); // > 1 Year 4 Months 12 Days 12 Hours ago } // DateDiffSample 
+2
source

As detailed in other answers, your code cannot be easily extended due to the variable month length. Thus, it is simply impossible to assume that the month will be 30 days.

To have a readable difference, you must subtract from human readable dates.

I would do it like this (JavaScript to answer the question):

 function toBeautyString(then) { var nowdate = new Date(); var thendate = new Date(then * 1000); //finding the human-readable components of the date. var y = nowdate.getFullYear() - thendate.getFullYear(); var m = nowdate.getMonth() - thendate.getMonth(); var d = nowdate.getDate() - thendate.getDate(); var h = nowdate.getHours() - thendate.getHours(); var mm = nowdate.getMinutes() - thendate.getMinutes(); var s = nowdate.getSeconds() - thendate.getSeconds(); //back to second grade math, now we must now 'borrow'. if(s < 0) { s += 60; mm--; } if(mm < 0) { mm += 60; h--; } if(h < 0) { h += 24; d--; } if(d < 0) { //here where we take into account variable month lengths. var a = thendate.getMonth(); var b; if(a <= 6) { if(a == 1) b = 28; else if(a % 2 == 0) b = 31; else b = 30; } else if(b % 2 == 0) b = 30; else b = 31; d += b; m--; } if(m < 0) { m += 12; y--; } //return "y years, m months, d days, h hours, mm minutes and s seconds ago." } 

The code works by subtracting dates from human-readable dates (obtained using the built-in javascript commands). The only work left before this is to ensure that any borrowing continues smoothly. This is easy unless you borrow from months because the months are of variable length.

Say you subtract February 25th from April 12th.

Before borrowing a place, m = 2 and d = -13 . Now that you are borrowing from m , m = 1 , but you need to make sure that d increases by 28, as you borrow in February. The end result is 1 month, 15 days ago.

If you subtracted July 25th from September 12th, the result would be 1 month, 18 days ago.

The only thing that is not indicated above is leap years. It expands easily: you just need to take into account the year and set up the one you need if you borrow in February.

+2
source

Two functions: one for calculating the difference, and the other for displaying it (inspired by Kevin's answer). It works on all my tests, takes into account monthly durations, is easily translated, and also works with time-saving time.

 /** * Calculates difference from 'now' to a timestamp, using pretty units * (years, months, days, hours, minutes and seconds). * Timestamps in ms, second argument is optional (assumes "now"). */ function abstractDifference(thenTimestamp, nowTimestamp) { var now = nowTimestamp ? new Date(nowTimestamp) : new Date(); var then = new Date(thenTimestamp); var nowTimestamp = Math.round(now.getTime()); console.log(nowTimestamp, thenTimestamp); // -- part 1, in which we figure out the difference in days var deltaSeconds = Math.round((nowTimestamp - thenTimestamp)/1000); // adjust offset for daylight savings time: 2012/01/14 to 2012/04/14 // is '3 months', not 2 months 23 hours (for most earth-bound humans) var offsetNow = now.getTimezoneOffset(); var offsetThen = then.getTimezoneOffset(); deltaSeconds -= (offsetNow - offsetThen) * 60; // positive integers are easier to work with; and months are sensiteive to +/- var inTheFuture = false; if (deltaSeconds < 0) { inTheFuture = true; deltaSeconds = -deltaSeconds; } var seconds = deltaSeconds % 60; var deltaMinutes = Math.floor(deltaSeconds / 60); var minutes = deltaMinutes % 60; var deltaHours = Math.floor(deltaMinutes / 60); var hours = deltaHours % 24; var deltaDays = Math.floor(deltaHours / 24); console.log("delta days: ", deltaDays); // -- part 2, in which months figure prominently function daysInMonth(year, month) { // excess days automagically wrapped around; see details at // http://www.ecma-international.org/publications/standards/Ecma-262.htm return 32 - new Date(year, month, 32).getDate(); } var months = 0; var currentMonth = now.getMonth(); var currentYear = now.getFullYear(); if ( ! inTheFuture) { // 1 month ago means "same day-of-month, last month" // it is the length of *last* month that is relevant currentMonth --; while (true) { if (currentMonth < 0) { currentMonth = 11; currentYear--; } var toSubstract = daysInMonth(currentYear, currentMonth); if (deltaDays >= toSubstract) { deltaDays -= toSubstract; months ++; currentMonth --; } else { break; } } } else { // in 1 month means "same day-of-month, next month" // it is the length of *this* month that is relevant while (true) { if (currentMonth > 11) { currentMonth = 0; currentYear++; } var toSubstract = daysInMonth(currentYear, currentMonth); if (deltaDays >= toSubstract) { deltaDays -= toSubstract; months ++; currentMonth ++; } else { break; } } } var years = Math.floor(months / 12); var months = months % 12; return {future: inTheFuture, years: years, months: months, days: deltaDays, hours: hours, minutes: minutes, seconds: seconds}; } /** * Returns something like "1 year, 4 days and 1 second ago", or * "in 1 month, 3 hours, 45 minutes and 59 seconds". * Second argument is optional. */ function prettyDifference(thenTimestamp, nowTimestamp) { var o = abstractDifference(thenTimestamp, nowTimestamp); var parts = []; function pushPart(property, singular, plural) { var value = o[property]; if (value) parts.push("" + value + " " + (value==1?singular:plural)); } // to internationalize, change things here var lastSeparator = " and "; var futurePrefix = "in "; var pastSuffix = " ago"; var nameOfNow = "now"; pushPart("years", "year", "years"); pushPart("months", "month", "months"); pushPart("days", "day", "days"); pushPart("hours", "hour", "hours"); pushPart("minutes", "minute", "minutes"); pushPart("seconds", "second", "seconds"); if (parts.length == 0) { return nameOfNow; } var beforeLast = parts.slice(0, -1).join(", "); var pendingRelative = parts.length > 1 ? [beforeLast , parts.slice(-1) ].join(lastSeparator) : parts[0]; return o.future ? futurePrefix + pendingRelative : pendingRelative + pastSuffix; } 
+1
source

There can be no such algorithm!

The fractions of the day (hours, minutes, seconds), even days, are not a problem. The problem is that the length of the "month" varies from 28 to 31 days.

I will give you an example:

Suppose today is 28 Feb 2013 and you want to calculate toBeautyString(28 Jan 2013) :

 today: 28 Feb 2013 toBeautyString(28 Jan 2013) expected answer: 1 month ago 

Well, that is really not a problem. On the same day, in the same year, only the month changed.

Now calculate toBeautyString(27 Jan 2013) on the same day:

 today: 28 Feb 2013 toBeautyString(27 Jan 2013) expected answer: 1 month and 1 day ago 

It's easy too, isn't it? We wanted to get the value the day before, and the output indicated that the duration is one day longer.

Now let's go to bed and continue to work the next day (March 1, 2013).
Try the following:

 today: 1 Mar 2013 toBeautyString(1 Feb 2013) expected answer: 1 month ago 

Well, so simple! The same logic as your first calculation. Only the month has changed by 1, so the duration cannot be anything but 1 month.
So, let's calculate the value the day before:

 today: 1 Mar 2013 toBeautyString(31 Jan 2013) expected answer: 1 month and 1 day ago 

Again: the result of the previous day should be a duration that is 1 day longer.
Try making a duration of up to 1 day:

 today: 1 Mar 2013 toBeautyString(30 Jan 2013) expected answer: 1 month and 2 days ago 

and longer:

 today: 1 Mar 2013 toBeautyString(29 Jan 2013) expected answer: 1 month and 3 days ago 

and finally:

 today: 1 Mar 2013 toBeautyString(28 Jan 2013) expected answer: 1 month and 4 days ago 

Remember this!


No, we repeat the very first calculation that we did yesterday. Yesterday we counted toBeautyString(28 Jan 2013) and the result was 1 month ago . Today is the next day. If we calculate toBeautyString(28 Jan 2013) today, the result should show us a duration that is one day longer:

 today: 1 Mar 2013 toBeautyString(28 Jan 2013) expected answer 1 month and 1 days ago 

Compare this to the previous calculation. We did both calculations on March 1, 2013. And in both cases, we calculated the same thing: toBeautyString(28 Jan 2013) . But we expect two different results. Surprisingly, both expectations are true.

So, in order to deliver a result that truly meets our expectations, the algorithm must be able to read our mind. But this is not possible for the algorithm, therefore, there cannot be an algorithm that perfectly performs what you expect.

-3
source

All Articles