Show Absolute Datetime in Gitlab

Show absolute datetime in custom gitlab page.

// ==UserScript==
// @name         Show Absolute Datetime in Gitlab
// @namespace    http://tampermonkey.net/
// @version      0.3.5
// @description  Show absolute datetime in custom gitlab page.
// @author       Priestch
// @match        https://gitpd.paodingai.com/*
// @grant        none
// ==/UserScript==

(function() {
  'use strict';
  document.onreadystatechange = function () {
    if (document.readyState == "complete") {
      replaceRelativeDateTime();
    }
  }

  function padStart(string, length, pad) {
    const s = String(string)
    if (!s || s.length >= length) return string
    return `${Array((length + 1) - s.length).join(pad)}${string}`
  }

  const datetimePattern = /(\w+)\s(\d+),\s(\d{4}) (\d+):(\d+)([a|p]m) GMT([+-]\d{4})/;
  const datetimeFormatPattern = /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/

  const monthMap = {
    'Jan': 1,
    'Feb': 2,
    'Mar': 3,
    'Apr': 4,
    'May': 5,
    'Jun': 6,
    'Jul': 7,
    'Aug': 8,
    'Sep': 9,
    'Oct': 10,
    'Nov': 11,
    'Dec': 12,
  }

  const timezonePattern = /([-|+])(\d{2})(\d{2})/;

  function parseTimezone(timezone) {
    const matched = timezone.match(timezonePattern);
    if (matched) {
      return `${matched[1]}${matched[2]}:${matched[3]}`;
    } else {
      return timezone;
    }
  }

  function parseDatetime(datetimeString) {
    const matched = datetimeString.match(datetimePattern);
    if (matched) {
      let hours = Number(matched[4]);
      if (matched[6] === 'pm') {
        hours += 12;
      }

      const timezone = parseTimezone( matched[7]);
      const datetime = {
        year: matched[3],
        day: padStart(matched[2], 2, '0'),
        month: padStart(`${monthMap[matched[1]]}`, 2, '0'),
        hours: padStart(hours, 2, '0'),
        minutes: padStart(matched[5], 2, '0'),
        seconds: '00',
        milliseconds: '000',
        timezone
      };

      return `${datetime.year}-${datetime.month}-${datetime.day}T${datetime.hours}:${datetime.minutes}:${datetime.seconds}.${datetime.milliseconds}${datetime.timezone}`;
    }
    return datetimeString;
  }

  function getDatetime(el) {
    if (el.tagName === 'TIME') {
      const datetimeAttr = el.getAttribute('datetime');
      if (datetimeAttr !== 'date') {
        return datetimeAttr;
      }
    }

    return el.dataset.title || el.dataset.originalTitle;
  }

  function formatDatetime(datetime) {
    const month = padStart(`${datetime.getMonth() + 1}`, 2, '0');
    const day = padStart(`${datetime.getDate()}`, 2, '0');
    const hours = padStart(`${datetime.getHours()}`, 2, '0')
    const minutes = padStart(`${datetime.getMinutes()}`, 2, '0')
    const seconds = padStart(`${datetime.getSeconds()}`, 2, '0')
    let formatted = `${datetime.getFullYear()}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    return formatted;
  }

  function replaceRelativeDateTime() {
    const targetNode = document.getElementsByTagName('body')[0];

    // Options for the observer (which mutations to observe)
    const config = { attributes: true, childList: true, subtree: true };

    // Callback function to execute when mutations are observed
    const callback = function(mutationsList, observer) {
      for(let mutation of mutationsList) {
        const timeChildren = mutation.target.querySelectorAll('time');
        if (timeChildren.length > 0) {
          for (let t of timeChildren) {
            if (!t.textContent.match(datetimeFormatPattern)) {
              const datetimeValue = getDatetime(t);
              if (datetimeValue) {
                const parsedDatetime = Date.parse(parseDatetime(datetimeValue));
                t.textContent = formatDatetime(new Date(parsedDatetime));
              }
            }
          }
          continue;
        } else {
          if (mutation.target.nodeName !== 'TIME') {
            continue;
          }
          if (mutation.type == 'childList' && mutation.target.nodeName === 'TIME') {
            if (!mutation.target.textContent.match(datetimeFormatPattern)) {
              const utcDatetime = mutation.target.getAttribute('datetime');
              const textContent = utcDatetime ? formatDatetime(new Date(utcDatetime)) : getDatetime(mutation.target);
              mutation.target.textContent = textContent;
            }
          }
        }
      }
    };

    // Create an observer instance linked to the callback function
    const observer = new MutationObserver(callback);

    // Start observing the target node for configured mutations
    observer.observe(targetNode, config);

    const relativeDatetimeElements = document.getElementsByTagName('time');
    for (let element of relativeDatetimeElements) {
      element.textContent = getDatetime(element);
    }
  }
})();