61
votes

I'm writing an equipment rental application where clients are charged a fee for renting equipment based on the duration (in days) of the rental. So, basically, (daily fee * number of days) = total charge.

For instant feedback on the client side, I'm trying to use Javascript to figure out the difference in two calendar dates. I've searched around, but nothing I've found is quite what I'm looking for. Most solutions I've seen are of the form:

function dateDiff1(startDate, endDate) {
    return ((endDate.getTime() - startDate.getTime()) / 1000*60*60*24);
}

My problem is that equipment can be checked out and returned at any time of day during those two dates with no additional charge. The above code is calculating the number of 24 hour periods between the two dates, when I'm really interested in the number of calendar days.

For example, if someone checked out equipment at 6am on July 6th and returned it at 10pm on July 7th, the above code would calculate that more than one 24 hour period had passed, and would return 2. The desired result is 1, since only one calendar date has elapsed (i.e. the 6th to the 7th).

The closest solution I've found is this function:

function dateDiff2(startDate, endDate) {
    return endDate.getDate() - startDate.getDate();
}

which does exactly what I want, as long as the two dates are within the same month. However, since getDate() only returns the day of month (i.e. 1-31), it doesn't work when the dates span multiple months (e.g. July 31 to August 1 is 1 day, but the above calcuates 1 - 31, or -29).

On the backend, in PHP, I'm using gregoriantojd(), which seems to work just fine (see this post for an example). I just can't find an equivalent solution in Javascript.

Anyone have any ideas?

15
Every single one of these solutions is incorrect if equipment is checked out and returned on the same day. You probably want a diff = Math.max(1, diff) somewhere in your code if you're not offering free same-day rentals! :]Noyo

15 Answers

52
votes

If you want whole days for your student camera rental example ...

function daysBetween(first, second) {

    // Copy date parts of the timestamps, discarding the time parts.
    var one = new Date(first.getFullYear(), first.getMonth(), first.getDate());
    var two = new Date(second.getFullYear(), second.getMonth(), second.getDate());

    // Do the math.
    var millisecondsPerDay = 1000 * 60 * 60 * 24;
    var millisBetween = two.getTime() - one.getTime();
    var days = millisBetween / millisecondsPerDay;

    // Round down.
    return Math.floor(days);
}
21
votes

I just had this problem and solved it after finding this question, so I came back to post this. This will get the total days regardless of time. And DST doesn't mess it up:

date1 = Date.UTC(date1.getFullYear(), date1.getMonth(), date1.getDate());
date2 = Date.UTC(date2.getFullYear(), date2.getMonth(), date2.getDate());
var ms = Math.abs(date1-date2);
return Math.floor(ms/1000/60/60/24); //floor should be unnecessary, but just in case

The trick is converting to a UTC date that doesn't even know about the times of the original dates.

11
votes

What I would do is set the two date's times to the same time. For example, set endDate's time to 12:00am and startDate's time to 12:00 am also. Then get the difference between them.

On a side note, since I too am in the rental equipment software industry, it seems like you're losing rental revenue by not counting the hours. Per your example if someone picked up the equipment on July 6th at 6am and returned it on july 7th at 10pm. They had two full days to use the equipment and possibly incur an excess meter charge too...

11
votes

There is a bug in the given solutions!

This applies to date differences where the time is disregarded AND you want an integer result, that is, whole number of days.

In many of the examples above we see Math.floor other instances I've seen Math.ceil other places as well. These are done to round the result to an integer number of days. The problem is daylight savings time will give a wrong result in the fall using Math.ceil--Your result will be one day too large or in the spring if you use Math.floor you will be off by one day too few. Just use Math.round because 1 hour either way is not going to skew the result.

function dateDiff(dateEarlier, dateLater) {
    var one_day=1000*60*60*24
    return (  Math.round((dateLater.getTime()-dateEarlier.getTime())/one_day)  );
}
8
votes

I would also suggest having a look at the incredible moment.js library. It has a versatile diff function, which you can use to solve the above example as follows:

function is_same_date(startDate, endDate) {
   var startMoment = moment(startDate).clone().startOf('day'),
       endMoment = moment(endDate).clone().startOf('day');
   return startMoment.diff(endMoment, 'days') == 0;
}

Here are some examples using moment.js diff:

> d1 = new Date(2012,04,04,0,0)
Fri May 04 2012 00:00:00 GMT-0400 (EDT)

> d2 = new Date(2012,04,03,23,0)
Thu May 03 2012 23:00:00 GMT-0400 (EDT)

> d3 = new Date(2012,04,05,23,0)
Sat May 05 2012 23:00:00 GMT-0400 (EDT)

> moment(d2).diff(d1, 'days')
0

> moment(d1).diff(d2, 'days')
0

> moment(d1).diff(d3, 'days') // uh oh. this is why we use `startOf`
-2

> moment(d2).diff(d3, 'days')
-2

> moment(d3).startOf('day') // this modified d3, sets the time to 00:00:00.0000
> d3
Sat May 05 2012 00:00:00 GMT-0400 (EDT)

If timezones are a concern, you can also use moment.utc() to convert to a normalized timezone.

6
votes

Since the Julian day is effectively the number of days (and in some cases also the fraction of number of days) since a certain date, it is practically the same as a UNIX timestamp, presented differently. You can get the number of whole days since 1970 like so:

Date.prototype.getWholeDays = function () {
    return Math.floor(new Date() / 1000*60*60*24);
};

function dateDiff(startDate, endDate) {
    return endDate.getWholeDays() - startDate.getWholeDays();
}
3
votes

If you only want the difference in days, be sure to use UTC (GMT) or the subtraction won't produce a whole number of days in seconds if Daylight Saving Time starts or end during the interval.

2
votes

I used to check the result of datediff function which uses .getTime() with Excel for the 18th century. The result returns correct. I have another method to calculate different days:

    function DaysOfYear(nYear) {
        if ((nYear % 4) == 0) {
            return 366;
        }
        else {
            return 365;
        }
    }


    function DiffDays(fDate, tDate) {
        var fYear = fDate.getFullYear();
        var tYear = tDate.getFullYear();
        var fMonth = fDate.getMonth();
        var tMonth = tDate.getMonth();
        var fDay = fDate.getDate();
        var tDay = tDate.getDate();
        var nDays = 0;

        if (tYear > fYear) {   // different year
            // remain days of from year
            nDays = DaysOfYear(fYear) - YTD_Days(fDate);
            // days of between fYear to tYear
            for (y = (fYear + 1); y < tYear; y++) {
                nDays = nDays + DaysOfYear(y);
            }
            // days from the beginning of tYear
            nDays = nDays + YTD_Days(tDate);
        }
        else {   // in the same year
            nDays = YTD_Days(tDate) - YTD_Days(fDate);
        };
        return nDays;
    }

    function YTD_Days(dDate) {
        var y = dDate.getFullYear();
        var m = dDate.getMonth();
        var nDays = 0

        for (i = 0; i < m; i++) {
            switch (i) {
                case 0:     // January
                    nDays = nDays + 31;
                    break;
                case 1:     // February
                    if ((y % 4) == 0) {
                        nDays = nDays + 29;
                    }
                    else {
                        nDays = nDays + 28;
                    };
                    break;
                case 2:     // March
                    nDays = nDays + 31;
                    break;
                case 3:     // April
                    nDays = nDays + 30;
                    break;
                case 4:     // May
                    nDays = nDays + 31;
                    break;
                case 5:     // June
                    nDays = nDays + 30;
                    break;
                case 6:     // July
                    nDays = nDays + 31;
                    break;
                case 7:     // August
                    nDays = nDays + 31;
                    break;
                case 8:     // September
                    nDays = nDays + 30;
                    break;
                case 9:     // October
                    nDays = nDays + 31;
                    break;
                case 10:     // November
                    nDays = nDays + 30;
                    break;
                case 11:     // December
                    nDays = nDays + 31;
                    break;
            }
        }
        nDays = nDays + dDate.getDate();
        return nDays;
    }
1
votes

You could adjust the start date to midnight of the same day, then calculate the number of 24-hour periods between the dates. Then, you would take the ceiling or floor of that number depending on whether you want to count any part of a day as a whole day or not.

function dateDiff(startDate, endDate) {
    // set startDate to the beginning of the day
    startDate = new Date(
        startDate.getFullYear(),
        startDate.getMonth(),
        startDate.getDate());

    return Math.floor((endDate - startDate) / 1000*60*60*24);
}
1
votes

use setHours() method, assuming number of days can never be zero :)

function dateDiff1(startDate, endDate) {
    endDate.setHours(0);
    startDate.setHours(0);
    //assuming days cannt be 0.

    var x = ((endDate.getTime() - startDate.getTime()) / 1000*60*60*24);
    if (x <1 && x>=0) {
        return 1;
    }
    return x;
}
1
votes

To get the full days the date interval over daylight saving time changes the floor(days) must be replaced by round. Otherwise it's a very useful function.

0
votes

+up the example above that uses the UTC constructors. Without doing that, you'll be clobbered by Daylight Savings / Summer Hours.

If you take the differences in some of them, it could wind up an hour short when crossing the DST line for your time zone in one direction (spring-forward, meaning in Feb asking for April, for example). When you floor N+23/24, you'll end up with N.

The other direction (in October asking for December) should be fine, you'll end up an hour ahead which the floor() will deal with correctly.

0
votes

Just out of curiosity:

If you can affirm that the outcome will ALWAYS be < 31 days, then the following is a solution too:

var deltaDays = new Date(endDate - startDate).getDate()-1

Why?

Well decomposing the previous line:

var msecs = endDate - startDate; // difference in milliseconds
var dt = new Date(msecs); // will give us a date object pointing to a time 
                          // somewhere in Jan 1970 (given the precondition above)
var days = dt.getDate();  // Take the day part of it
var deltaDays = days - 1; // since the numbering starts from 1, we have to 
                          // substract 1 (Ex. if the diff in msecs is 0. The
                          // Date will point to 1st of Jan 1970)

But obviously you should NOT use it if your results can be > 31.

0
votes

Need date.js, http://code.google.com/p/datejs/

function CalculateDuration(startDate, endDate) {

var sdt = Date.parse(startDate);
var edt = Date.parse(endDate);

var sdtYear = sdt.getYear();
var edtYear = edt.getYear();
var sdtMonth = sdt.getMonth();
var edtMonth = edt.getMonth();
var sdtDay = sdt.getDate();
var edtDay = edt.getDate();

var StartDateMonthDays = Date.getDaysInMonth(sdtYear, sdtMonth);
var EndDateMonthDays = Date.getDaysInMonth(edtYear, edtMonth);

if (edtMonth < sdtMonth) { //Means the ending month is earlier, which invalidates the operation

    alert("Ending month cannot be earlier than the starting month")

}

//Months are equal, if month is the next month of the start date month, 
//the operation fails, thus we need to calculate month difference first
else if (edtMonth > sdtMonth) {

    //Need to count how many days are left to finish the month
    var daysToMonthFinish = StartDateMonthDays - sdtDay;
    var calculatedDuration = daysToMonthFinish + edtDay;
    $("#hdn_duration").val(calculatedDuration);

}
//If months are the same, it is safe to just calculate the end day minus the start day
else {

    var calcDuration = edtDay - sdtDay;
    $("#hdn_duration").val(calcDuration);
}
0
votes

I know this is a partial solution but you may be able to get the days between dates (as long as the dates don't have time set). Then you can work from there.

I'm basing this solution with the problem you presented ((daily fee * number of days) = total charge); not the actual date difference question. This should help you out.

var Main = function() {
    var startDate = new Date('08/20/2014');
    var endDate = new Date('08/22/2014');
    var totalCharge = monthlyFee * daysBetween(startDate, endDate);
}

var daysBetween = function(startDate, endDate) {
    return Math.round(Math.abs((startDate.getTime() - endDate.getTime())/(24*60*60*1000)));
};