JonnyRobbie
JonnyRobbie

Reputation: 614

JavaScript year,month,day,hour,minute,second difference

I was trying to search for the answer, but I couldn't find any. I'm trying to create a JavaScript function, that takes two dates and returns a string in the format of "x years, y months, z days, a hours, b minutes, c seconds". The usual iterated division fails, because months can have different number of days. I found several sources, but they either go only up to days/hours/minutes omitting the months problem, or they just erroneously average days in month.

function dateDifference(date) {
    now = new Date();
    ??;
    return difference; //returns something like "x years, y months, z days, a hours, b minutes, c seconds"
}

Thanks for the help

Upvotes: 1

Views: 4265

Answers (4)

Martin
Martin

Reputation: 604

Here is my more accurate function that calculates elapsed time between dates and returns an object containing years, months, days, hours, minutes, seconds and milliseconds properties.

/**
 * Get elapsed time ~ difference between two date/time values (ordered automatically)
 * 
 * @param {*} start_time - start time
 * @param {*} end_time - end time
 * @throws {TypeError} on invalid start/end time value
 * @returns {{
 *   start: Date,
 *   end: Date,
 *   years: number,
 *   months: number,
 *   days: number,
 *   hours: number,
 *   minutes: number,
 *   seconds: number,
 *   milliseconds: number,
 *   total_days: number,
 *   total_time: number,
 *   toString: (()=>string),
 * }} `{[key: string]: any}`
 */
function elapsedTime(start_time, end_time){

    /**
     * Parse date value ~ accepts valid Date instance, integer timestamp or date string
     *  
     * @param {*} val 
     * @returns {Date|undefined}
     */
    const _parse_date = val => {
        if (val instanceof Date) return !isNaN(val = val.getTime()) ? new Date(val) : undefined;
        else if ('string' === typeof val) return !isNaN(val = Date.parse(val)) ? new Date(val) : undefined;
        return Number.isInteger(val) ? new Date(val) : undefined;
    };

    //-- parse arguments
    if (!(start_time = _parse_date(start_time))) throw new TypeError('Invalid elapsed start time value! Pass a valid Date instance, integer timestamp or date string value.');
    if (!(end_time = _parse_date(end_time))) throw new TypeError('Invalid elapsed end time value! Pass a valid Date instance, integer timestamp or date string value.');
    const min_max = start_time > end_time ? [end_time, start_time] : [start_time, end_time];
    const start = new Date(min_max[0].getTime());
    const end = new Date(min_max[1].getTime());

    //-- parse elapsed time
    let years = 0;
    let months = 0;
    let days = 0;
    let hours = 0;
    let minutes = 0;
    let seconds = 0;
    let milliseconds = 0
    const total_time = end.getTime() - start.getTime();
    const total_days = Math.floor(total_time / (24 * 60 * 60 * 1000));
    if ((milliseconds += (end.getMilliseconds() - start.getMilliseconds())) < 0){
        seconds --;
        milliseconds += 1000;
    }
    if ((seconds += (end.getSeconds() - start.getSeconds())) < 0){
        minutes --;
        seconds += 60;
    }
    if ((minutes += (end.getMinutes() - start.getMinutes())) < 0){
        hours --;
        minutes += 60;
    }
    if ((hours += (end.getHours() - start.getHours())) < 0){
        days --;
        hours += 24;
    }
    const start_year = start.getFullYear();
    let start_month = start.getMonth();
    years = end.getFullYear() - start_year;
    if ((months = end.getMonth() - start_month) < 0){
        years --;
        months += 12;
    }
    if ((days += (end.getDate() - start.getDate())) < 0){
        if (end.getMonth() === start.getMonth()) start_month ++;
        if (months <= 0){
            years --;
            months = 11;
        }
        else months --;
        days += new Date(start_year, start_month + 1, 0).getDate();
    }

    //<< result
    return {
        start,
        end,
        years,
        months,
        days,
        hours,
        minutes,
        seconds,
        milliseconds,
        total_days,
        total_time,
        toString: function(){
            const values = [];
            const _add = (val, singular) => void (val ? values.push(val + ' ' + (val === 1 ? singular : singular + 's')) : null);
            _add(years, 'year');
            _add(months, 'month');
            _add(days, 'day');
            _add(hours, 'hour');
            _add(minutes, 'minute');
            _add(seconds, 'second');
            _add(milliseconds, 'millisecond');
            if (!values.length) values.push('0 milliseconds');
            return values.length > 1 ? values.slice(0, -1).join(', ') + ' and ' + values[values.length - 1] : values.join('');
        },
    }
}

//============================ example usage ================================

let a = '2023-11-20T17:30:29.069Z',
b = '2012-01-19T05:00:00.420Z',
result = elapsedTime(a, b);

console.log(result.toString());
// 11 years, 10 months, 1 day, 12 hours, 30 minutes, 28 seconds and 649 milliseconds

console.log(result);
// {
//     start: 2012-01-19T05:00:00.420Z,
//     end: 2023-11-20T17:30:29.069Z,
//     years: 11,
//     months: 10,
//     days: 1,
//     hours: 12,
//     minutes: 30,
//     seconds: 28,
//     milliseconds: 649,
//     total_days: 4323,
//     total_time: 373552228649,
//     toString: [Function: toString]
// }

Upvotes: 0

Rob Johnstone
Rob Johnstone

Reputation: 1734

Use .getTime to convert the dates into milliseconds:

var date1 = new Date(),
    date2 = new Date(someDate),
    diff = Math.abs(date1.getTime() - date2.getTime());

// then convert into years, months, etc

EDIT: You can't get away from averaging the length of months as the problem will often be ambiguous. e.g if calculating the difference between 3rd Feb and 10th Mar is that 1 month and 7 days (assuming a month is 28 days as in Feb) or 1 month and 4 days (assuming a month is 31 days as in March)?

EDIT2: corrected my truely appalling maths.

EDIT3: Actually, thinking about it, any normal human being transposes the day from the first month into the last month and uses that to calculate the difference in days. So:

var date1 = new Date(),
    date2 = new Date(1981, 10, 18);
    switch = (date2.getTime() - date1.getTime()) < 0 ? false : true, // work out which is the later date
    laterDate = switch ? date2 : date1;
    earlierDate = switch ? date1 : date2;
    dayDiff = laterDate.getDate() - earlierDate.getDate(),
    monthDiff = laterDate.getMonth() - earlierDate.getMonth(), // javascript uses zero-indexed months (0-11 rather than the more conventional 1-12)
    yearDiff = laterDate.getFullYear() - earlierDate.getFullYear(),
    months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 

if (dayDiff < 0) {
    monthDiff--;
    dayDiff += months[laterDate.getMonth()-1]; // -1 because we want the previous month
}

if (monthDiff < 0) {
    yearDiff--;
    monthDiff += 12;
}

console.log(yearDiff+' years, '+monthDiff+' months, '+dayDiff+' days');

I think this gets us most of the way there but we still have to consider leap years

EDIT 4: Corrected a couple of (sometimes but, unfortunately, not always cancelling out) off by 1 errors

Upvotes: 2

Salem
Salem

Reputation: 12996

You can just subtract two dates to get the milliseconds, and then work from there:

var date1 = new Date("2012/06/12 12:00:30");
var date2 = new Date("2013/06/15 12:00:40");
var diff = Math.abs(date2 - date1);
var years = Math.floor(diff/(1000*60*60*24*365)); // 1
var months = Math.floor((diff - years) / (1000*60*60*24*30)) % 12; // 0
(...)

Upvotes: 0

theshadowmonkey
theshadowmonkey

Reputation: 679

Use moment.js which is a really good function and you can have time returned in plain text form.

http://momentjs.com/

Read the API documentation and with some clever hacking on the function, you can get the required output string format. And btw there is no specific library that can output dates in your required format I am aware of. So, let me know if you get somewhere and need help with moment.js

Upvotes: 2

Related Questions