|
| 1 | +(function(){ |
| 2 | + function formatDate(epoch, fmt){ |
| 3 | + const d = new Date(Number(epoch) * 1000); |
| 4 | + switch(fmt){ |
| 5 | + case 't': return d.toLocaleTimeString(undefined, {hour: 'numeric', minute: '2-digit'}); |
| 6 | + case 'T': return d.toLocaleTimeString(undefined, {hour: 'numeric', minute: '2-digit', second: '2-digit'}); |
| 7 | + case 'd': return d.toLocaleDateString(); |
| 8 | + case 'D': return d.toLocaleDateString(undefined, {year:'numeric', month:'long', day:'numeric'}); |
| 9 | + case 'f': return d.toLocaleString(undefined, {year:'numeric', month:'short', day:'numeric', hour:'numeric', minute:'2-digit'}); |
| 10 | + case 'F': return d.toLocaleString(undefined, {weekday:'long', year:'numeric', month:'long', day:'numeric', hour:'numeric', minute:'2-digit'}); |
| 11 | + case 'R': return relativeTime(new Date(Number(epoch) * 1000)); |
| 12 | + default: return d.toString(); |
| 13 | + } |
| 14 | + } |
| 15 | + |
| 16 | + function relativeTime(date){ |
| 17 | + const now = Date.now(); |
| 18 | + let diff = Math.floor((now - date.getTime()) / 1000); |
| 19 | + if (Math.abs(diff) < 5) return 'just now'; |
| 20 | + const intervals = [ |
| 21 | + [60, 'second', 1], |
| 22 | + [3600, 'minute', 60], |
| 23 | + [86400, 'hour', 3600], |
| 24 | + [604800, 'day', 86400], |
| 25 | + [2629800, 'week', 604800], |
| 26 | + [31557600, 'month', 2629800], |
| 27 | + [Number.MAX_SAFE_INTEGER, 'year', 31557600] |
| 28 | + ]; |
| 29 | + const past = diff > 0; |
| 30 | + diff = Math.abs(diff); |
| 31 | + for (let i = 0; i < intervals.length; i++){ |
| 32 | + if (diff < intervals[i][0]){ |
| 33 | + const val = Math.floor(diff / intervals[i][2]) || 0; |
| 34 | + const unit = intervals[i][1] + (val === 1 ? '' : 's'); |
| 35 | + return past ? `${val} ${unit} ago` : `in ${val} ${unit}`; |
| 36 | + } |
| 37 | + } |
| 38 | + return ''; |
| 39 | + } |
| 40 | + |
| 41 | + function renderElement(el){ |
| 42 | + const epoch = el.getAttribute('data-epoch'); |
| 43 | + const fmt = el.getAttribute('data-format') || 'f'; |
| 44 | + if (!epoch) return; |
| 45 | + el.textContent = formatDate(epoch, fmt); |
| 46 | + el.title = new Date(Number(epoch) * 1000).toLocaleString(); |
| 47 | + } |
| 48 | + |
| 49 | + // Schedule updates per-element for relative timestamps |
| 50 | + function scheduleRelative(el){ |
| 51 | + // Render now |
| 52 | + renderElement(el); |
| 53 | + |
| 54 | + // Compute next update delay based on current distance |
| 55 | + const epochMs = Number(el.getAttribute('data-epoch')) * 1000; |
| 56 | + const now = Date.now(); |
| 57 | + let diff = Math.floor((now - epochMs) / 1000); |
| 58 | + const absDiff = Math.abs(diff); |
| 59 | + |
| 60 | + let unitSeconds; |
| 61 | + if (absDiff < 60) unitSeconds = 1; // seconds |
| 62 | + else if (absDiff < 3600) unitSeconds = 60; // minutes |
| 63 | + else if (absDiff < 86400) unitSeconds = 3600; // hours |
| 64 | + else if (absDiff < 604800) unitSeconds = 86400; // days |
| 65 | + else if (absDiff < 2629800) unitSeconds = 604800; // weeks |
| 66 | + else if (absDiff < 31557600) unitSeconds = 2629800; // months |
| 67 | + else unitSeconds = 31557600; // years |
| 68 | + |
| 69 | + const remainder = absDiff % unitSeconds; |
| 70 | + let nextSeconds = remainder === 0 ? unitSeconds : (unitSeconds - remainder); |
| 71 | + if (nextSeconds < 1) nextSeconds = 1; |
| 72 | + |
| 73 | + // Clear previous timer if any |
| 74 | + if (el._timeoutId) clearTimeout(el._timeoutId); |
| 75 | + |
| 76 | + el._timeoutId = setTimeout(function(){ |
| 77 | + // On timeout, re-schedule (this will re-render and compute next delay) |
| 78 | + scheduleRelative(el); |
| 79 | + }, nextSeconds * 1000); |
| 80 | + } |
| 81 | + |
| 82 | + function init(){ |
| 83 | + // Render non-relative timestamps once |
| 84 | + document.querySelectorAll('time.discord-timestamp').forEach(function(el){ |
| 85 | + const fmt = el.getAttribute('data-format') || 'f'; |
| 86 | + if (fmt === 'R'){ |
| 87 | + scheduleRelative(el); |
| 88 | + } else { |
| 89 | + renderElement(el); |
| 90 | + } |
| 91 | + }); |
| 92 | + } |
| 93 | + |
| 94 | + document.addEventListener('DOMContentLoaded', init); |
| 95 | +})(); |
0 commit comments