time.jsannotatedtime.jssource44 lines · 2.1 KB · raw

Moments. The server writes every time in UTC (aldebaran_time::moment): it knows neither this clock nor this timezone. Here a time[data-ago] is rewritten as how long ago or how soon, with the date and time where the reader is as its tooltip; an element with data-when keeps its text and gets only the tooltip. Parts of the page are replaced as they change, so new moments are found as they arrive, and all are brought up to date each minute.

6(() => {
7  const local = new Intl.DateTimeFormat(undefined, {
8    year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", timeZoneName: "short",
9  });
10  const relative = new Intl.RelativeTimeFormat(undefined, { numeric: "always" });
11  const UNITS = [["year", 31536000], ["month", 2592000], ["day", 86400], ["hour", 3600], ["minute", 60]];
12  function distance(seconds) {
13    const size = Math.abs(seconds);
14    if (size < 60) return seconds < 0 ? "just now" : "in under a minute";
15    const [unit, length] = UNITS.find(([, length]) => size >= length);
16    return relative.format(Math.trunc(seconds / length), unit);
17  }
18  function set(element, name, value) {
19    if (element[name] !== value) element[name] = value;
20  }
21  function refresh() {
22    const now = Date.now();
23    for (const time of document.querySelectorAll("time[data-ago][datetime]")) {
24      const at = Date.parse(time.dateTime);
25      if (Number.isNaN(at)) continue;
26      set(time, "textContent", distance((at - now) / 1000));
27      set(time, "title", local.format(at));
28    }
29    for (const element of document.querySelectorAll("[data-when]")) {
30      const at = Date.parse(element.dataset.when);
31      if (!Number.isNaN(at)) set(element, "title", local.format(at));
32    }
33  }
34  refresh();
35  // Writing a moment's own text is a change too; `set` writes nothing that is already so, which is what
36  // ends it.
37  let due = false;
38  new MutationObserver(() => {
39    if (due) return;
40    due = true;
41    requestAnimationFrame(() => { due = false; refresh(); });
42  }).observe(document.body, { childList: true, subtree: true });
43  setInterval(refresh, 60000);
44})();