Work with a time span in Javascript

后端 未结 7 759
感动是毒
感动是毒 2020-11-30 05:53

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 tim

7条回答
  •  日久生厌
    2020-11-30 06:12

    a simple timestamp formatter in pure JS with custom patterns support and locale-aware, using Intl.RelativeTimeFormat

    some formatting examples

    /** delta: 1234567890, @locale: 'en-US', @style: 'long' */
    
    /* D~ h~ m~ s~ */
    14 days 6 hours 56 minutes 7 seconds
    
    /* 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,06:56:07.890
    
    /* D~, h:m:s.f */
    14 days, 06:56:07.890
    
    /* h~ m~ s~ */
    342 hours 56 minutes 7 seconds
    
    /* s~ m~ h~ D~ */
    7 seconds 56 minutes 6 hours 14 days
    
    /* up D~, h:m */
    up 14 days, 06:56
    

    the code & test

    /**
        Init locale formatter:
        
            timespan.locale(@locale, @style)
        
        Example:
    
            timespan.locale('en-US', 'long');
            timespan.locale('es', 'narrow');
    
        Format time delta:
        
            timespan.format(@pattern, @milliseconds)
    
            @pattern tokens:
                D: days, h: hours, m: minutes, s: seconds, f: millis
    
            @pattern token extension:
                h  => '0'-padded value, 
                h# => raw value,
                h~ => locale formatted value
    
        Example:
    
            timespan.format('D~ h~ m~ s~ f "millis"', 1234567890);
            
            output: 14 days 6 hours 56 minutes 7 seconds 890 millis
    
        NOTES:
    
        * milliseconds unit have no locale translation
        * may encounter declension issues for some locales
        * use quoted text for raw inserts
                
    */
    
    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};
    })();
    
    
    /************************** test below *************************/
    
    const
    cfg = {
      locale: 'en,de,nl,fr,it,es,pt,ro,ru,ja,kor,zh,th,hi',
      style: 'long,narrow'
    },
    el = id => document.getElementById(id),
    locale = el('locale'), loc = el('loc'), style = el('style'),
    fd = new Date(), td = el('td'), fmt = el('fmt'),
    run = el('run'), out = el('out'),
    test = () => {
      try {
          const tv = new Date(td.value);
          if (isNaN(tv)) throw Error('invalid "datetime2" value');
          timespan.locale(loc.value || locale.value, style.value);
          const delta = fd.getTime() - tv.getTime();
          out.innerHTML = timespan.format(fmt.value, delta);
      } catch (e) { out.innerHTML = e.message; }
    };
    el('fd').innerText = el('td').value = fd.toISOString();
    el('fmt').value = 'D~ h~ m~ s~ f~ "ms"';
    for (const [id, value] of Object.entries(cfg)) {
      const elm = el(id);
      value.split(',').forEach(i => elm.innerHTML += ``);
    }
    i {color:green}
    locale: 
    custom: 
    style:
    datetime1:
    datetime2:
    pattern:

提交回复
热议问题