Work with a time span in Javascript
Asked Answered
M

7

40

Using Date.js already, but can also use another library if necessary.

Not sure what is the best way to work with time deltas. Specifically, I want to display the time that has elapsed between now and a past date-time.

So I need to do something like this:

var elapsed_time = new Date() - pastDate;
pastDate.toString('days-hours-minutes-seconds');

Gotten it to mostly work using Date.js, but the problem is now I'm working with a Date object and not a timespan, so what should be an 23 hour time span is instead 23 hours after the Date's very first time:

var result = (new Date()) - past_date;
"result" is the number (probably milliseconds): 15452732
var result = (new Date() - past_date
"result" is a date from 1969: Wed Dec 31 1969 23:17:32

What I need is:

0 days 23 hours 17 minutes and 32 seconds

Any ideas?

Matrimonial answered 12/1, 2013 at 20:24 Comment(0)
L
80

Sounds like you need moment.js

e.g.

moment().subtract('days', 6).calendar();

=> last Sunday at 8:23 PM

moment().startOf('hour').fromNow();

=> 26 minutes ago

Edit:

Pure JS date diff calculation:

var date1 = new Date("7/Nov/2012 20:30:00");
var date2 = new Date("20/Nov/2012 19:15:00");

var diff = date2.getTime() - date1.getTime();

var days = Math.floor(diff / (1000 * 60 * 60 * 24));
diff -=  days * (1000 * 60 * 60 * 24);

var hours = Math.floor(diff / (1000 * 60 * 60));
diff -= hours * (1000 * 60 * 60);

var mins = Math.floor(diff / (1000 * 60));
diff -= mins * (1000 * 60);

var seconds = Math.floor(diff / (1000));
diff -= seconds * (1000);

document.write(days + " days, " + hours + " hours, " + mins + " minutes, " + seconds + " seconds");
Lute answered 12/1, 2013 at 20:29 Comment(5)
I'm not sure this supports printing out the elapsed time in days, hours minutes, seconds?Matrimonial
Yeah, had another guy look into it as well. Even with moment.js, there are not enough "format" options on the time span. The way we had to do it was manually like you have above.Matrimonial
ok to mention moment.js, but how does the given moment.js code answer the OP's question?Microsurgery
UPDATE 2020.10 #1: from moment.js docs: "In most cases, you should not choose Moment for new projects" - momentjs.com/docsImprecation
UPDATE 2020.10 #2: an interesting overview of alternatives - inventi.studio/en/blog/why-you-shouldnt-use-moment-jsImprecation
P
11

If you're not too worried in accuracy after days, you can simply do the maths

function timeSince(when) { // this ignores months
    var obj = {};
    obj._milliseconds = (new Date()).valueOf() - when.valueOf();
    obj.milliseconds = obj._milliseconds % 1000;
    obj._seconds = (obj._milliseconds - obj.milliseconds) / 1000;
    obj.seconds = obj._seconds % 60;
    obj._minutes = (obj._seconds - obj.seconds) / 60;
    obj.minutes = obj._minutes % 60;
    obj._hours = (obj._minutes - obj.minutes) / 60;
    obj.hours = obj._hours % 24;
    obj._days = (obj._hours - obj.hours) / 24;
    obj.days = obj._days % 365;
    // finally
    obj.years = (obj._days - obj.days) / 365;
    return obj;
}

then timeSince(pastDate); and use the properties as you like.

Otherwise you can use .getUTC* to calculate it, but note it may be slightly slower to calculate

function timeSince(then) {
    var now = new Date(), obj = {};
    obj.milliseconds = now.getUTCMilliseconds() - then.getUTCMilliseconds();
    obj.seconds = now.getUTCSeconds() - then.getUTCSeconds();
    obj.minutes = now.getUTCMinutes() - then.getUTCMinutes();
    obj.hours = now.getUTCHours() - then.getUTCHours();
    obj.days = now.getUTCDate() - then.getUTCDate();
    obj.months = now.getUTCMonth() - then.getUTCMonth();
    obj.years = now.getUTCFullYear() - then.getUTCFullYear();
    // fix negatives
    if (obj.milliseconds < 0) --obj.seconds, obj.milliseconds = (obj.milliseconds + 1000) % 1000;
    if (obj.seconds < 0) --obj.minutes, obj.seconds = (obj.seconds + 60) % 60;
    if (obj.minutes < 0) --obj.hours, obj.minutes = (obj.minutes + 60) % 60;
    if (obj.hours < 0) --obj.days, obj.hours = (obj.hours + 24) % 24;
    if (obj.days < 0) { // months have different lengths
        --obj.months;
        now.setUTCMonth(now.getUTCMonth() + 1);
        now.setUTCDate(0);
        obj.days = (obj.days + now.getUTCDate()) % now.getUTCDate();
    }
    if (obj.months < 0)  --obj.years, obj.months = (obj.months + 12) % 12;
    return obj;
}
Pantile answered 12/1, 2013 at 21:36 Comment(0)
T
9

Here is a simple timestamp formatter with custom patterns support and locale-aware, using Intl.RelativeTimeFormat.

Formatting examples:

/** delta: 1234567890, @locale: 'en-US', @style: 'long' */

/* D~ h~ m~ s~ f~ */
14 days 6 hours 56 minutes 7 seconds 890

/* D#"d" h#"h" m#"m" s#"s" f#"ms" */
14d 6h 56m 7s 890ms

/* D~, h:m:s.f */
14 days, 06:56:07.890

/* h~ m~ s~ */
342 hours 56 minutes 7 seconds
   
/* up D~, h:m */
up 14 days, 06:56

Usage description, source code and live examples are below:

/************************** README *************************

Initialize formatter:

    timespan.locale(@locale, @style)
    
      @locale format:
          
          'en-US', 'de-AT', ...
          
              reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locales_argument
      
      @style format:
      
          'long': e.g., 1 month
          'short': e.g., 1 mo.

      Examples:

          timespan.locale('en-US', 'long');
          timespan.locale('de-AT', 'short');

Get formatted timespan:

    timespan.format(@pattern, @milliseconds)

        @pattern tokens:
        
            D: days, h: hours, m: minutes, s: seconds, f: milliseconds

        @pattern token extension:
        
            h  => '0'-padded value, 
            h# => raw value,
            h~ => locale formatted value

        Examples:

            timespan.format('D~ h~ m~ s~', 1234567890);
            timespan.format('up D~, h:m', 1234567890);

NOTES:

* milliseconds unit have no locale translation
* may encounter declension issues for some locales
* use quoted text for raw inserts

*/


/************************** SOURCE *************************/

const timespan = (() => {
  let rtf, tokensRtf;
  const
  tokens = /[Dhmsf][#~]?|"[^"]*"|'[^']*'/g,
  map = [
      {t: [['D', 1], ['D#'], ['D~', 'day']], u: 86400000},
      {t: [['h', 2], ['h#'], ['h~', 'hour']], u: 3600000},
      {t: [['m', 2], ['m#'], ['m~', 'minute']], u: 60000},
      {t: [['s', 2], ['s#'], ['s~', 'second']], u: 1000},
      {t: [['f', 3], ['f#'], ['f~']], u: 1}
  ],
  locale = (value, style = 'long') => {
      try {
          rtf = new Intl.RelativeTimeFormat(value, {style});
      } catch (e) {
          if (rtf) throw e;
          return;
      }
      const h = rtf.format(1, 'hour').split(' ');
      tokensRtf = new Set(rtf.format(1, 'day').split(' ')
          .filter(t => t != 1 && h.indexOf(t) > -1));
      return true;
  },
  fallback = (t, u) => u + ' ' + t.fmt + (u == 1 ? '' : 's'),
  mapper = {
      number: (t, u) => (u + '').padStart(t.fmt, '0'),
      string: (t, u) => rtf ? rtf.format(u, t.fmt).split(' ')
          .filter(t => !tokensRtf.has(t)).join(' ')
          .trim().replace(/[+-]/g, '') : fallback(t, u),
  },
  replace = (out, t) => out[t] || t.slice(1, t.length - 1),
  format = (pattern, value) => {
      if (typeof pattern !== 'string')
          throw Error('invalid pattern');
      if (!Number.isFinite(value))
          throw Error('invalid value');
      if (!pattern)
          return '';
      const out = {};
      value = Math.abs(value);
      pattern.match(tokens)?.forEach(t => out[t] = null);
      map.forEach(m => {
          let u = null;
          m.t.forEach(t => {
              if (out[t.token] !== null)
                  return;
              if (u === null) {
                  u = Math.floor(value / m.u);
                  value %= m.u;
              }
              out[t.token] = '' + (t.fn ? t.fn(t, u) : u);
          })
      });
      return pattern.replace(tokens, replace.bind(null, out));
  };
  map.forEach(m => m.t = m.t.map(t => ({
      token: t[0], fmt: t[1], fn: mapper[typeof t[1]]
  })));
  locale('en');
  return {format, locale};
})();


/************************** EXAMPLES *************************/

const delta = 1234567890;

console.log(timespan.format('D~ h~ m~ s~ f~', delta));
console.log(timespan.format('s~ m~ h~ D~', delta));
console.log(timespan.format('D#"d" h#"h" m#"m" s#"s" f#"ms"', delta));
console.log(timespan.format('h~ m~ s~', delta));
console.log(timespan.format('D,h:m:s.f', delta));
console.log(timespan.format('D~, h:m:s.f', delta));
console.log(timespan.format('up D~, h:m', delta));

timespan.locale('de-DE', 'long');

console.log(timespan.format('D~ h~ m~ s~', delta));
Teece answered 25/7, 2020 at 16:33 Comment(5)
Too bad Safari won't support it. EDIT looks like they plan to implement it and it already works with their TP versions! Great!Tekla
This solution appears to be what I'm looking for, but for "Uncaught TypeError: el(...) is null". Can you explain? I suspect "el = id => document.getElementById(id)" needs to be customized for other elements on page, or what?Grillroom
@Grillroom It is a general purpose function, not tied to DOM elements. The code below comment "test below" is not a part of solution. I guess the error you got is because you just copy/pasted the whole JavaScript part without the HTML part and tried to run that.Teece
Somehow we're not connecting here. I did paste the entire script part, yes, placing it between script tagging. I skipped the CSS part but did paste the HTML part and do see those elements. (FF) I've already got my need met, however, so no need for you to be concerned about me, and I'll not worry about any more about getting the snippet working. Thank you!!!!Grillroom
The formatting examples on this are in dire need of minimal examples of how they are used, the block of... code pasted below is not that.Islander
B
6

You can use momentjs duration object

Example:

const diff = moment.duration(Date.now() - new Date(2010, 1, 1))
console.log(`${diff.years()} years ${diff.months()} months ${diff.days()} days ${diff.hours()} hours ${diff.minutes()} minutes and ${diff.seconds()} seconds`)
Biffin answered 26/8, 2016 at 12:54 Comment(0)
A
0

Moment.js provides such functionality:

http://momentjs.com/

It's well documented and nice library.

It should go along the lines "Duration" and "Humanize of API http://momentjs.com/docs/#/displaying/from/

 var d1, d2; // Timepoints
 var differenceInPlainText = moment(a).from(moment(b), true); // Add true for suffixless text
Alenaalene answered 12/1, 2013 at 21:57 Comment(4)
I looked over the entire documentation as well as did some trial-and-error. I could not figure out how to do it with moment.js If you can show how to do it with moment.js, that would be awesome. The other answers show how to do it manually, which is what I was trying to avoid.Matrimonial
Clairified the answer... hopefully gives more clues. Never done this myself.Alenaalene
I'm pretty sure that does not work. It seems to print out something like this "a few seconds ago". Can't tell it to print out "HH:MM:SS" type stuff.Matrimonial
Ah so you want to print out zero days, hours and minutes too?Alenaalene
C
0
/**
 * 计算时间对象与当前时间的差距,并显示友好的文本 
 * English: Calculating the difference between the given time and the current time and then showing the results.
 */
function date2Text(date) {
    var milliseconds = new Date() - date;
    var timespan = new TimeSpan(milliseconds);
    if (milliseconds < 0) {
        return timespan.toString() + "之后";
    }else{
        return timespan.toString() + "前";
    }
}

/**
 * 用于计算时间间隔的对象
 * English: Using a function to calculate the time interval
 * @param milliseconds 毫秒数
 */
var TimeSpan = function (milliseconds) {
    milliseconds = Math.abs(milliseconds);
    var days = Math.floor(milliseconds / (1000 * 60 * 60 * 24));
    milliseconds -= days * (1000 * 60 * 60 * 24);

    var hours = Math.floor(milliseconds / (1000 * 60 * 60));
    milliseconds -= hours * (1000 * 60 * 60);

    var mins = Math.floor(milliseconds / (1000 * 60));
    milliseconds -= mins * (1000 * 60);

    var seconds = Math.floor(milliseconds / (1000));
    milliseconds -= seconds * (1000);
    return {
        getDays: function () {
            return days;
        },
        getHours: function () {
            return hours;
        },
        getMinuts: function () {
            return mins;
        },
        getSeconds: function () {
            return seconds;
        },
        toString: function () {
            var str = "";
            if (days > 0 || str.length > 0) {
                str += days + "天";
            }
            if (hours > 0 || str.length > 0) {
                str += hours + "小时";
            }
            if (mins > 0 || str.length > 0) {
                str += mins + "分钟";
            }
            if (days == 0 && (seconds > 0 || str.length > 0)) {
                str += seconds + "秒";
            }
            return str;
        }
    }
}
Continuous answered 5/1, 2018 at 3:32 Comment(1)
Could you please change the comments to English so people can add understand.Gery
A
-4

Here a .NET C# similar implementation of a timespan class that supports days, hours, minutes and seconds. This implementation also supports negative timespans.

const MILLIS_PER_SECOND = 1000;
const MILLIS_PER_MINUTE = MILLIS_PER_SECOND * 60;   //     60,000
const MILLIS_PER_HOUR = MILLIS_PER_MINUTE * 60;     //  3,600,000
const MILLIS_PER_DAY = MILLIS_PER_HOUR * 24;        // 86,400,000

export class TimeSpan {
    private _millis: number;

    private static interval(value: number, scale: number): TimeSpan {
        if (Number.isNaN(value)) {
            throw new Error("value can't be NaN");
        }

        const tmp = value * scale;
        const millis = TimeSpan.round(tmp + (value >= 0 ? 0.5 : -0.5));
        if ((millis > TimeSpan.maxValue.totalMilliseconds) || (millis < TimeSpan.minValue.totalMilliseconds)) {
            throw new TimeSpanOverflowError("TimeSpanTooLong");
        }

        return new TimeSpan(millis);
    }

    private static round(n: number): number {
        if (n < 0) {
            return Math.ceil(n);
        } else if (n > 0) {
            return Math.floor(n);
        }

        return 0;
    }

    private static timeToMilliseconds(hour: number, minute: number, second: number): number {
        const totalSeconds = (hour * 3600) + (minute * 60) + second;
        if (totalSeconds > TimeSpan.maxValue.totalSeconds || totalSeconds < TimeSpan.minValue.totalSeconds) {
            throw new TimeSpanOverflowError("TimeSpanTooLong");
        }

        return totalSeconds * MILLIS_PER_SECOND;
    }

    public static get zero(): TimeSpan {
        return new TimeSpan(0);
    }

    public static get maxValue(): TimeSpan {
        return new TimeSpan(Number.MAX_SAFE_INTEGER);
    }

    public static get minValue(): TimeSpan {
        return new TimeSpan(Number.MIN_SAFE_INTEGER);
    }

    public static fromDays(value: number): TimeSpan {
        return TimeSpan.interval(value, MILLIS_PER_DAY);
    }

    public static fromHours(value: number): TimeSpan {
        return TimeSpan.interval(value, MILLIS_PER_HOUR);
    }

    public static fromMilliseconds(value: number): TimeSpan {
        return TimeSpan.interval(value, 1);
    }

    public static fromMinutes(value: number): TimeSpan {
        return TimeSpan.interval(value, MILLIS_PER_MINUTE);
    }

    public static fromSeconds(value: number): TimeSpan {
        return TimeSpan.interval(value, MILLIS_PER_SECOND);
    }

    public static fromTime(hours: number, minutes: number, seconds: number): TimeSpan;
    public static fromTime(days: number, hours: number, minutes: number, seconds: number, milliseconds: number): TimeSpan;
    public static fromTime(daysOrHours: number, hoursOrMinutes: number, minutesOrSeconds: number, seconds?: number, milliseconds?: number): TimeSpan {
        if (milliseconds != undefined) {
            return this.fromTimeStartingFromDays(daysOrHours, hoursOrMinutes, minutesOrSeconds, seconds, milliseconds);
        } else {
            return this.fromTimeStartingFromHours(daysOrHours, hoursOrMinutes, minutesOrSeconds);
        }
    }

    private static fromTimeStartingFromHours(hours: number, minutes: number, seconds: number): TimeSpan {
        const millis = TimeSpan.timeToMilliseconds(hours, minutes, seconds);
        return new TimeSpan(millis);
    }

    private static fromTimeStartingFromDays(days: number, hours: number, minutes: number, seconds: number, milliseconds: number): TimeSpan {
        const totalMilliSeconds = (days * MILLIS_PER_DAY) +
            (hours * MILLIS_PER_HOUR) +
            (minutes * MILLIS_PER_MINUTE) +
            (seconds * MILLIS_PER_SECOND) +
            milliseconds;

        if (totalMilliSeconds > TimeSpan.maxValue.totalMilliseconds || totalMilliSeconds < TimeSpan.minValue.totalMilliseconds) {
            throw new TimeSpanOverflowError("TimeSpanTooLong");
        }
        return new TimeSpan(totalMilliSeconds);
    }

    constructor(millis: number) {
        this._millis = millis;
    }

    public get days(): number {
        return TimeSpan.round(this._millis / MILLIS_PER_DAY);
    }

    public get hours(): number {
        return TimeSpan.round((this._millis / MILLIS_PER_HOUR) % 24);
    }

    public get minutes(): number {
        return TimeSpan.round((this._millis / MILLIS_PER_MINUTE) % 60);
    }

    public get seconds(): number {
        return TimeSpan.round((this._millis / MILLIS_PER_SECOND) % 60);
    }

    public get milliseconds(): number {
        return TimeSpan.round(this._millis % 1000);
    }

    public get totalDays(): number {
        return this._millis / MILLIS_PER_DAY;
    }

    public get totalHours(): number {
        return this._millis / MILLIS_PER_HOUR;
    }

    public get totalMinutes(): number {
        return this._millis / MILLIS_PER_MINUTE;
    }

    public get totalSeconds(): number {
        return this._millis / MILLIS_PER_SECOND;
    }

    public get totalMilliseconds(): number {
        return this._millis;
    }

    public add(ts: TimeSpan): TimeSpan {
        const result = this._millis + ts.totalMilliseconds;
        return new TimeSpan(result);
    }

    public subtract(ts: TimeSpan): TimeSpan {
        const result = this._millis - ts.totalMilliseconds;
        return new TimeSpan(result);
    }
}

How to use

Create a new TimeSpan object

From zero

    const ts = TimeSpan.zero;

From milliseconds

    const milliseconds = 10000; // 1 second

    // by using the constructor
    const ts1 = new TimeSpan(milliseconds);

    // or as an alternative you can use the static factory method
    const ts2 = TimeSpan.fromMilliseconds(milliseconds);

From seconds

    const seconds = 86400; // 1 day
    const ts = TimeSpan.fromSeconds(seconds);

From minutes

    const minutes = 1440; // 1 day
    const ts = TimeSpan.fromMinutes(minutes);

From hours

    const hours = 24; // 1 day
    const ts = TimeSpan.fromHours(hours);

From days

    const days = 1; // 1 day
    const ts = TimeSpan.fromDays(days);

From time with given hours, minutes and seconds

    const hours = 1;
    const minutes = 1;
    const seconds = 1;
    const ts = TimeSpan.fromTime(hours, minutes, seconds);

From time2 with given days, hours, minutes, seconds and milliseconds

    const days = 1;
    const hours = 1;
    const minutes = 1;
    const seconds = 1;
    const milliseconds = 1;
    const ts = TimeSpan.fromTime(days, hours, minutes, seconds, milliseconds);

From maximal safe integer

    const ts = TimeSpan.maxValue;

From minimal safe integer

    const ts = TimeSpan.minValue;

From minimal safe integer

    const ts = TimeSpan.minValue;

Add

    const ts1 = TimeSpan.fromDays(1);
    const ts2 = TimeSpan.fromHours(1);
    const ts = ts1.add(ts2);

    console.log(ts.days);               // 1
    console.log(ts.hours);              // 1
    console.log(ts.minutes);            // 0
    console.log(ts.seconds);            // 0
    console.log(ts.milliseconds);           // 0

Subtract

    const ts1 = TimeSpan.fromDays(1);
    const ts2 = TimeSpan.fromHours(1);
    const ts = ts1.subtract(ts2);

    console.log(ts.days);               // 0
    console.log(ts.hours);              // 23
    console.log(ts.minutes);            // 0
    console.log(ts.seconds);            // 0
    console.log(ts.milliseconds);           // 0

Getting the intervals

    const days = 1;
    const hours = 1;
    const minutes = 1;
    const seconds = 1;
    const milliseconds = 1;
    const ts = TimeSpan.fromTime2(days, hours, minutes, seconds, milliseconds);

    console.log(ts.days);               // 1
    console.log(ts.hours);              // 1
    console.log(ts.minutes);            // 1
    console.log(ts.seconds);            // 1
    console.log(ts.milliseconds);           // 1

    console.log(ts.totalDays)           // 1.0423726967592593;
    console.log(ts.totalHours)          // 25.016944722222224;
    console.log(ts.totalMinutes)            // 1501.0166833333333;
    console.log(ts.totalSeconds)            // 90061.001;
    console.log(ts.totalMilliseconds);      // 90061001;

See also here: https://github.com/erdas/timespan

Adamandeve answered 16/1, 2019 at 23:45 Comment(3)
A link to a solution is welcome, but please make sure the answer makes sense without the link.Ideation
Did the changes fix your suggestion?Adamandeve
Yes, they did. Thank you.Ideation

© 2022 - 2024 — McMap. All rights reserved.