2
\$\begingroup\$

I use the following code in Javascript to get the difference between two Date objects. I want the result to return the difference in:

  • seconds if the result is less than 60 secs
  • minutes if the result is less than 60 mins
  • hours if the result is less than 24 hours
  • days otherwise

The code is very long and I see lots of code duplication. Isn't there a smarter/shorter way to do this (without using a library)?

function dateDiff(a, b) {
  let utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate(), a.getUTCHours(), a.getUTCMinutes(), a.getUTCSeconds());
  let utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate(), b.getUTCHours(), b.getUTCMinutes(), b.getUTCSeconds());

  let result = (utc2 - utc1) / (1000 * 60 * 60 * 24);
  let floor = Math.floor(result);
  if (floor > 0) return floor + "d";

  result *= 24;
  floor = Math.floor(result);
  if (floor > 0) return floor + "h";

  result *= 60;
  floor = Math.floor(result);
  if (floor > 0) return floor + "min";

  result *= 60;
  floor = Math.floor(result);
  if (floor > 0) return floor + "sec";
}
\$\endgroup\$

migrated from stackoverflow.com Jan 15 '17 at 8:21

This question came from our site for professional and enthusiast programmers.

  • \$\begingroup\$ Can't you just use Math.floor once? You're multiplying by whole numbers. \$\endgroup\$ – David Archibald Jan 14 '17 at 21:29
3
\$\begingroup\$

Use getTime instead of the UTC transformation it'll return the timeStamp.

So your diff would be:

b.getTime() - a.getTime()

This will give you the milliseconds if you want the seconds you would divide it by 1000 so you would get:

var secondsDiff = (b.getTIme() - a.getTime())/1000

Then for the return

if (secondsDiff > 86400)  { 
 return Math.floor(secondsDiff/86400) + ' D'
}
if (secondsDiff > 3600)  { 
 return Math.floor(secondsDiff/3600) + ' h'
}
if (secondsDiff > 60)  { 
 return Math.floor(secondsDiff/60) + ' min'
}
if (secondsDiff > 0) { 
 return secondsDiff + ' sec'
}
\$\endgroup\$
  • \$\begingroup\$ How about some details. I didn't downvote, but a one sentence answer isn't quite long enough. How about comment something like this next time. \$\endgroup\$ – David Archibald Jan 14 '17 at 21:23
  • \$\begingroup\$ Well it's a broad field of make the code more efficient, but from the start the first thing that catchs the eye is the Utc misuse when you could have getTime. Plus I put the link but there isn't really much to say about getTime \$\endgroup\$ – FabioCosta Jan 14 '17 at 21:25
  • \$\begingroup\$ This is kind of the gray zone. Yes, it's an answer, but I feel that it can be improved and you've been downvoted likely because it's not detailed enough. So if it can be improved, why not improve it? Up to you. \$\endgroup\$ – David Archibald Jan 14 '17 at 21:27
  • \$\begingroup\$ Ok expanded the answer then. \$\endgroup\$ – FabioCosta Jan 14 '17 at 21:36
  • \$\begingroup\$ If you combined this with trincot's answer (using a for loop) your code would become even shorter and I'd love to accept it as the right answer codereview.stackexchange.com/a/152683/128754 \$\endgroup\$ – Timo Jan 20 '17 at 13:08
2
\$\begingroup\$

I wanted to play with the fact that 3 out of 4 if tests are comparing to exponent of 60 (0, 60 and 3600) and came up with the code that is maybe a line shorter but much less readble. However, was interested if it's faster. I compared it on jsperf.com with FabioCosta's solution as was cutious. https://jsperf.com/datediff-compare2

Performance test result are maybe not enough to reach a conclusion, but out of 10 tests it turns out to be from -2% to +14% faster on Chrome55/Linux.

  function dateDiff2(d1,d2) {
    var a = (d1.getTime() - d2.getTime())/1000;
  	if (a >= 86400) return Math.floor(a/86400) + ' D';
  	var labels=["sec","min","h"];
  	var p = Math.floor((Math.log(a) / Math.log(60)));
  	return Math.floor(a/Math.pow(60,p)) + labels[p];
  }

/* test data */
var dates=[
    [new Date(1483225200000),new Date(1483225199000)],
    [new Date(1483225200000),new Date(1483225139000)],
    [new Date(1483225200000),new Date(1483221599000)],
    [new Date(1483225200000),new Date(1483138799000)]
  ]

console.log(dates.map(test=>dateDiff2(test[0],test[1])));

\$\endgroup\$
1
\$\begingroup\$

You could put it in a loop if you store the specifics about the time units in an array. This is ES6 code:

function dateDiff(a, b) {
    const units = [{size: 60*60*24, name: 'd'  },
                   {size: 60*60,    name: 'h'  },
                   {size: 60,       name: 'min'},
                   {size: 1,        name: 'sec'}];
    const result = (b.getTime() - a.getTime()) / 1000;
    const unit = units.find( unit => result >= unit.size );
    return Math.floor(result / unit.size) + unit.name;
}

console.log(dateDiff(new Date(2016, 0, 14, 20), new Date(2016, 0, 14, 23, 58, 20)));

\$\endgroup\$
  • \$\begingroup\$ How about a simple hashmap for units, like {d : 60 * 60* 24, … }? \$\endgroup\$ – lxg Jan 14 '17 at 21:50
  • \$\begingroup\$ @lxg, sure, but then you have to specify d, h, min, ... twice, since you need to make sure you iterate in the correct order. \$\endgroup\$ – trincot Jan 15 '17 at 8:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.