GitHub Status - Local Time + Time Ago

Adds local time and relative time beside UTC timestamps on githubstatus.com

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu betiği yüklemek için bir betik yöneticisi eklentisi yüklemeniz gerekecektir.

(Zaten bir betik yöneticim var, hadi yükleyelim!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         GitHub Status - Local Time + Time Ago
// @namespace    https://githubstatus.com/
// @version      1.3
// @description  Adds local time and relative time beside UTC timestamps on githubstatus.com
// @match        https://www.githubstatus.com/*
// @match        https://githubstatus.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const DEBUG = true;

    function log(...args) {
        if (DEBUG) {
            console.log('[GitHub Status Local Time]', ...args);
        }
    }

    function warn(...args) {
        if (DEBUG) {
            console.warn('[GitHub Status Local Time]', ...args);
        }
    }

    function formatLocalDate(date) {
        const month = new Intl.DateTimeFormat('en-US', {
            month: 'short'
        }).format(date);

        const day = date.getDate();
        const year = date.getFullYear();

        const hours = String(date.getHours()).padStart(2, '0');
        const minutes = String(date.getMinutes()).padStart(2, '0');

        const timezone = new Intl.DateTimeFormat('en-US', {
            timeZoneName: 'short'
        })
            .formatToParts(date)
            .find(part => part.type === 'timeZoneName')?.value ?? '';

        return `${month} ${day}, ${year} - ${hours}:${minutes} ${timezone}`;
    }

    function formatTimeAgo(date) {
        const seconds = Math.floor((Date.now() - date.getTime()) / 1000);

        if (seconds < 5) {
            return 'just now';
        }

        if (seconds < 60) {
            return `${seconds} seconds ago`;
        }

        const minutes = Math.floor(seconds / 60);

        if (minutes < 60) {
            return `${minutes} minute${minutes === 1 ? '' : 's'} ago`;
        }

        const hours = Math.floor(minutes / 60);

        if (hours < 24) {
            return `${hours} hour${hours === 1 ? '' : 's'} ago`;
        }

        const days = Math.floor(hours / 24);

        if (days < 30) {
            return `${days} day${days === 1 ? '' : 's'} ago`;
        }

        const months = Math.floor(days / 30);

        if (months < 12) {
            return `${months} month${months === 1 ? '' : 's'} ago`;
        }

        const years = Math.floor(days / 365);

        return `${years} year${years === 1 ? '' : 's'} ago`;
    }

    function addLocalTimes() {
        const start = performance.now();

        const elements = document.querySelectorAll('.ago[data-datetime-unix]');

        log(`Processing ${elements.length} timestamp(s)`);

        let created = 0;
        let updated = 0;
        let skipped = 0;

        elements.forEach(ago => {
            const small = ago.closest('small');

            if (!small) {
                warn('Could not find <small> parent for:', ago);
                return;
            }

            const unixMs = Number(ago.dataset.datetimeUnix);

            if (!Number.isFinite(unixMs)) {
                warn('Invalid data-datetime-unix:', ago.dataset.datetimeUnix);
                return;
            }

            const date = new Date(unixMs);

            let local = small.querySelector(':scope > .tm-local-time');

            if (!local) {
                local = document.createElement('span');
                local.className = 'tm-local-time';
                small.appendChild(local);

                created++;
            }

            const newText = ` (${formatLocalDate(date)}, ${formatTimeAgo(date)})`;

            // Important: don't mutate the DOM if nothing changed.
            if (local.textContent !== newText) {
                local.textContent = newText;
                updated++;
            } else {
                skipped++;
            }
        });

        log({
            created,
            updated,
            skipped,
            durationMs: (performance.now() - start).toFixed(2)
        });
    }

    let observerTimer = null;

    const observer = new MutationObserver(mutations => {
        log(`MutationObserver fired with ${mutations.length} mutation(s)`);

        // Debounce DOM mutations to avoid repeated processing.
        clearTimeout(observerTimer);

        observerTimer = setTimeout(() => {
            log('Running update after DOM mutation');
            addLocalTimes();
        }, 100);
    });

    log('Script starting');

    addLocalTimes();

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    log('MutationObserver started');

    setInterval(() => {
        log('Interval update');
        addLocalTimes();
    }, 30_000);
})();