time.jsannotatedtime.jssource44 lines · 2.1 KB · raw
1// Moments. The server writes every time in UTC (`aldebaran_time::moment`): it knows neither this clock
2// nor this timezone. Here a `time[data-ago]` is rewritten as how long ago or how soon, with the date and
3// time where the reader is as its tooltip; an element with `data-when` keeps its text and gets only the
4// tooltip. Parts of the page are replaced as they change, so new moments are found as they arrive, and
5// 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})();