5

I basically want to have two dates, i.e:

 var startDate = new Date("01/01/2001 01:00:00");
 var currentDate = new Date();

Then I want to get the duration by subtracting startDate from currentDate and display it in the form of: a years b month(s) c day(s) d hour(s) e second(s).

My problem so far has been that if you take the shortcut, i.e:

var duration = currentDate.getTime() - startDate.getTime();
var seconds = duration / 1000;
var minutes = duration / 1000 / 60;

etc...

Then you get a false duration because, for example, months aren't always 30 days, years aren't always 365 days (leap years), etc. Not to mention that somehow when I did this it was off by roughly a year.

I've looked around the web and through various posts on StackOverflow but I couldn't find a working example of a JavaScript method that accurately subtracts one date from another and allows you to get the details (year, month, day, hour, minute, second) from the result date.

There is a website that can already do this exactly how I'd like to do it: http://www.timeanddate.com

  • you can look into this post http://stackoverflow.com/questions/7095991/javascript-i-have-subtracted-two-dates-to-get-the-difference-between-them-how – Srinivas B Oct 29 '12 at 12:22
  • You may be looking to write this yourself and not use a library, but I highly recommend [Moment.js](http://momentjs.com/) – jonvuri Oct 29 '12 at 12:23
  • From the minute before a summertime hour change to the minute after, should that be 2 minutes, or 2 minutes plus/minus an hour? – Phil H Oct 30 '12 at 11:34
  • Simply speaking - all I'm trying to do is this: `http://www.timeanddate.com/date/timeduration.html?d1=&m1=&y1=&d2=&m2=&y2=` –  Nov 16 '12 at 11:48

6 Answers6

2

There is no obvious simple solution to the problem if you want a "correct" result because months have different numbers of days.

But at the same time, the human brain gets ever more sloppy when it comes to large date ranges. So "1 year, 11 months" is almost as good as "almost two years". So before you start with a 100% correct solution, ask yourself if the result is good enough when you fake it and simply use 30 day months.

If that really isn't an option: Get rid of the hour and minutes; they'll just be confusing. Create two dates which both start at midnight (i.e. HH:MM:SS is 00:00:00).

Now the question is how many days, months and years is between them? That depends. The time between June, 1. and August, 31. is almost two months but there is no month between the two dates. It's 61 days, 0 months. Or 1 month, 30 days? Which is "correct"?

The mathematical correct solution will give you results with as much as 61 days difference which users will find irritating.

The human solution isn't mathematically correct, so you will end up having to code heuristics that fake human feeling of time.

Which is why so many sites say "more than one month" if some dates differ for more than 30 days and continue with "half a year", "a year" (for anything between 330 to 380 days) and "more than a year", etc. They use the sloppy human brain to their advantage instead of trying to come up with an "exact" result (which a) isn't really helpful either and b) no one really agrees what "exact" is supposed to mean).

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • Do you have an example to demonstrate what you're talking about here? –  Oct 30 '12 at 09:24
  • Sure: What result do you expect when the two dates are `01.07.2012` and `31.08.2012`? – Aaron Digulla Nov 05 '12 at 11:29
  • Off the top of my head it would be `31 days 1 month 0 years ?? hours ?? minutes ?? seconds` –  Nov 13 '12 at 09:16
  • There is no month between July and August. So why `1 month`? – Aaron Digulla Nov 13 '12 at 12:59
  • If it were 1 more day then it would be `0 days 2 months...` because it starts from the 1st and ends on the 31st that's why. –  Nov 15 '12 at 12:28
  • If you use that approach, you need to be careful when you calculate `31.7.2012` to `03.08.2012`. If you simply do 08-07 == 1 month, you'd get `4 days 1 month` which is obviously wrong. That's why I said that there "correct" solution is hard (apart from things like that 1 month isn't always 30 days). – Aaron Digulla Nov 15 '12 at 13:55
  • Simply speaking - all I'm trying to do is this: `http://www.timeanddate.com/date/timeduration.html?d1=&m1=&y1=&d2=&m2=&y2=` –  Nov 16 '12 at 11:47
  • No, you're not. You try to come up with an algorithm that gives you the parameters for this web service. Formatting the parameters is trivial. But there is no 100% consistent/correct/logical way to come up with said parameters. – Aaron Digulla Nov 16 '12 at 13:37
  • That said, the `timeanddate.com` gives `61 days` for `01.07.2012` and `31.08.2012`. In the small print, it gives "1 month, 30 days". When I use `02.07.2012 - 03.08.2012`, I get 32 days or 1 month, 1 day which seems odd because neither month is completely in the range. – Aaron Digulla Nov 16 '12 at 13:40
2

If you wanna go vanilla and without any dependencies you can do it like this:

https://gist.github.com/4705863

// Time difference function
function timeDiff(start, end) {
    //today, now!
    //Get the diff
    var diff = end - start;
    //Create numbers for dividing to get hour, minute and second diff
    var units = [
    1000 * 60 * 60 *24,
    1000 * 60 * 60,
    1000 * 60,
    1000
    ];

    var rv = []; // h, m, s array
    //loop through d, h, m, s. we are not gonna use days, its just there to subtract it from the time
    for (var i = 0; i < units.length; ++i) {
        rv.push(Math.floor(diff / units[i]));
        diff = diff % units[i];
    }

    //Get the year of this year
    var thisFullYear = end.getFullYear();
    //Check how many days there where in last month
    var daysInLastMonth = new Date(thisFullYear, end.getMonth(), 0).getDate();
    //Get this month
    var thisMonth = end.getMonth(); 
    //Subtract to get differense between years
    thisFullYear = thisFullYear - start.getFullYear();
    //Subtract to get differense between months
    thisMonth = thisMonth - start.getMonth();
    //If month is less than 0 it means that we are some moths before the start date in the year.
    // So we subtract one year, and add the negative number (month) to 12. (12 + -1 = 11)
    subAddDays = daysInLastMonth - start.getDate();
    thisDay = end.getDate();
    thisMonth = thisMonth - 1;
    if(thisMonth < 0){
        thisFullYear = thisFullYear - 1;
        thisMonth = 12 + thisMonth;
        //Get ends day of the month
    }
    //Subtract the start date from the number of days in the last month, add add the result to todays day of the month
    subAddDays = daysInLastMonth - start.getDate();
    subAddDays = thisDay + subAddDays;

    
    if(subAddDays >= daysInLastMonth){
        subAddDays = subAddDays - daysInLastMonth;
        thisMonth++;
        if (thisMonth > 11){
            thisFullYear++;
            thisMonth = 0;
        }
    }
    return {
        years: thisFullYear,
        months: thisMonth,
        days: subAddDays,
        hours: rv[1],
        minutes: rv[2],
        seconds: rv[3]
    };
}
Spoeken
  • 2,549
  • 2
  • 28
  • 40
1

http://www.w3schools.com/jsref/jsref_obj_date.asp

myDate.UTC() // get the number of milliseconds since the Unix epoch

So

delta_ms = laterDate.UTC() - earlierDate.UTC();
delta_days = Math.round(delta_ms / (1000 * 60 * 60 * 24));

If you want months and years, you need to specify whether you want calendar difference (e.g. 1st May to 1st June is 1 month, but 28th Feb to 30th March is...?), or against a 'standard' year like 365 days and 30 day months, etc.

This is not straightforward, even if it seems like it should be. For example in finance there are at least 5 ways to count the gap between two dates, using standard months or not, and using standard dates or not. Generating the date after some date is easy (1 calendar month after 1st Feb is 1st March) but going backward is not, as the mapping is not 1:1.

Also, note that this is an absolute value which does not need adjusting for leap years, leap seconds, summer time, timezones and all the other ephemera of time formats.

Phil H
  • 19,928
  • 7
  • 68
  • 105
1

Leaving the answer for myself in future

Simply use moment and its duration plugin

and use like

moment.duration(moment('then').diff(moment('now'))).format('d [Days] hh:mm:ss')

it works

beejei
  • 11
  • 2
0

You can consider those cases too. Consider 2 dates 2nd Mar 2008 and 5th Jun 2013.

             Mar
2008  ________###########################
2009  ###################################
2010  ###################################
2011  ###################################
2012  ####################################  (leap)
2013  ###############--------------------
                  Jun

You can subtract the years in between (ie 2009, 2010, 2011, 2012). Now here 2012 is a leap year but it doesn't matter because it will be counted in years not months.

Years = 4

Now, you are left with this

            2 
Mar 08  ____################################
Apr 08 ####################################
May 08 #####################################
Jun 08 ####################################
Jul 08 #####################################
Aug 08 #####################################
Sep 08 ####################################
Oct 08 #####################################
Nov 08 ####################################
Dec 08 #####################################
Jan 12 #####################################
Feb 12 ###################################
Mar 12 #####################################
Apr 12 ####################################
May 12 #####################################
Jun 12 #########----------------------------
                5

Just count the full months. It doesn't matter how many days they contains. They are being counted in months not days.

Months = 14

           2 
Mar 08  ____################################
Jun 12  #########----------------------------
                5

Now count the remaining days in Mar2008 and Jun2012 (excluding the end dates). Here the count comes out to be 29(Mar) + 4(Jun) = 33.

Days = 33

But 33 days seems odd as it can be converted into 1month + some days. If this happens, the question arises which month to choose - either Mar(31 days), Jun(30 days) or just reduce 30days to add a month. I think while considering huge differences, this would hardly matter. If we consider March, then the difference would be

4 Years 15 Months 2 Days or simply 5 Years 3 Months 2 Days

The same approach can be continued for time.

EDITED

Let `date1` be greater than `date2`
Let max(month) gives the total days in the month

years = date1.year - date2.year - 1;
months = date1.month - date2.month - 1;
days = (max(date2.month) - date1.day) + (date2.day - 1);
if(days >= 30)       //Here you can take totals days in date1 or date2
{
    month++;
    days-=30;
}
while(month >= 12)      //Here month can reach a value of 24 because it can be incremented above too
{
    years++;
    month-=12;
}
print("Difference is " + years + " years " + month + " months " + days + " days");
Shashwat
  • 2,538
  • 7
  • 37
  • 56
  • It's probably correct in theory. I think that there's an easier way to do it though. I'm sure that it's possible to do it with currently existing JavaScript methods like that of the `Date` object's. Otherwise, if you could provide some example code for what you're talking about then that would be great too! –  Oct 30 '12 at 09:27
  • It's still very far off though... `if(days >= 30)` minding the syntax errors I think that 500 days is also more than 30. Not to mention that in my question I stated that not all months are 30 days... –  Oct 30 '12 at 10:46
  • It can never reach 500. Value of date1.day is restricted between 1 and 31. – Shashwat Oct 30 '12 at 10:54
  • @ThreaT: There is no built in easy way. There are too many questions about implementation details that matter for a sensible library implementation to be provided that worked for everyone. So you have to define the rules yourself (which is equivalent to writing the code). – Phil H Oct 30 '12 at 11:32
  • Simply speaking - all I'm trying to do is this: `http://www.timeanddate.com/date/timeduration.html?d1=&m1=&y1=&d2=&m2=&y2=` –  Nov 16 '12 at 11:47
  • My solution and your link are doing the same thing. Can you make it specific what exactly do you want? Calculating the total number days, then dividing by 365 for years, then remaining days by 12 for month; is not going to work. You better check the difference between `2 Mar 2008` and `1 Mar 2013` there. Total days = 1825 exactly divisible by 365 (by 5). So difference should be 5 years but is it so? – Shashwat Nov 16 '12 at 15:43
-2
var years = currentDate.getYear() - startDate.getYear();
var months = currentDate.getMonth() - startDate.getMonth();
...
  • No! That's not going to work! Today it is October, 11 months ago it was November. Currentdate.getMonth - startDate.getMonth = -1. You have to use the difference in absolute terms, not using getYear etc. This is all the pain that the questioner is trying to avoid, and it's wrong! – Phil H Oct 29 '12 at 12:56
  • This is the simple solution, but of course not the greatest. A small fix and logics that could be applied for negatives: "if (months < 0) months += 12;" – wqefsbdgfhreq Oct 29 '12 at 13:01
  • 1
    What fixes do you intend to apply for 28th Feb minus 31 Jan? Your solution will give 1 month and -3 days? Or are you going to look up the days in these two target months, and apply different adjustments accordingly? And, I suppose, the same thing for leap years? And did you intend to do this UTC, or local time, for which it will matter whether the two dates are in summer time or not. – Phil H Oct 29 '12 at 13:16
  • 1
    Turns out there are leap seconds, like the one in June this year. Further down the rabbit hole. The point is that this looks simple until you try to get it properly right, or until it fails embarrassingly. – Phil H Oct 29 '12 at 13:18
  • It was working great up until now when I realized that `hours` = -2. I think it would get really complicated to try and "carry the one" each time if you know what I mean –  Oct 30 '12 at 06:02