世界时间耳语者

即时将外国时间转换为本地时间。

目前为 2025-04-04 提交的版本。查看 最新版本

// ==UserScript==
// @name         WorldTime Whisperer
// @name:de WorldTime-Flüsterer
// @name:es Susurrador de Horas Globales
// @name:fr Chuchoteur de Fuseaux Horaires
// @name:it Sussurratore del Tempo Mondiale
// @name:ru Шептун Мирового Времени
// @name:zh-CN 世界时间耳语者
// @name:zh-TW 世界時間耳語者
// @name:ja ワールドタイム・ウィスパラー
// @name:ko 세계 시간 속삭임이
// @name:hi वर्ल्डटाइम विस्परर
// @name:ar همس الوقت العالمي
// @name:he לוחש הזמן העולמי
// @name:sv Världstidviskaren
// @name:nl Wereldtijdfluisteraar
// @name:pt Sussurrador do Tempo Mundial
// @namespace    https://greasyfork.org/en/users/1451802
// @version      1.0
// @description  Turns foreign times into familiar ones, instantly.
// @description:de Wandelt fremde Zeiten sofort in deine lokale Zeit um.
// @description:es Convierte tiempos extranjeros en horas locales al instante.
// @description:fr Transforme instantanément les heures étrangères en heures locales.
// @description:it Converte istantaneamente gli orari stranieri in orari locali.
// @description:ru Мгновенно превращает чужое время в ваше местное.
// @description:zh-CN 即时将外国时间转换为本地时间。
// @description:zh-TW 即時將外國時間轉換為本地時間。
// @description:ja 外国の時間を瞬時にあなたの現地時間に変換します。
// @description:ko 해외 시간을 즉시 현지 시간으로 변환합니다.
// @description:hi विदेशी समय को तुरंत स्थानीय समय में बदलता है।
// @description:ar يحول الأوقات الأجنبية إلى وقتك المحلي فورًا.
// @description:he ממיר מיד זמנים זרים לשעה המקומית שלך.
// @description:sv Förvandlar främmande tider till lokal tid direkt.
// @description:nl Zet buitenlandse tijden onmiddellijk om naar je lokale tijd.
// @description:pt Converte horários estrangeiros para o seu local instantaneamente.
// @match        *://*/*
// @grant        none
// @license      MIT
// @icon https://www.svgrepo.com/show/476947/timezone.svg
// @author       NormalRandomPeople (https://github.com/NormalRandomPeople)
// @compatible      chrome
// @compatible      firefox
// @compatible      opera
// @compatible      edge
// @compatible      brave
// ==/UserScript==

(function () {
    'use strict';

    const timezoneOffsets = {
        "PST": -8, "PDT": -7, "MST": -7, "MDT": -6, "CST": -6, "CDT": -5,
        "EST": -5, "EDT": -4, "UTC": 0, "GMT": 0, "BST": 1,
        "CET": 1, "CEST": 2, "EET": 2, "EEST": 3, "IST": 5.5,
        "JST": 9, "AEST": 10, "AEDT": 11, "ACST": 9.5, "ACDT": 10.5
    };

    const localTimeMessages = {
        "en": "Local time:",
        "de": "Ortszeit:",
        "fr": "Heure locale:",
        "es": "Hora local:",
        "it": "Ora locale:",
        "pt": "Hora local:",
        "nl": "Lokale tijd:",
        "sv": "Lokal tid:",
        "ru": "Местное время:",
        "ja": "現地時間:",
        "zh": "本地时间:",
        "ko": "현지 시간:",
        "hi": "स्थानीय समय:",
        "ar": "الوقت المحلي:",
        "he": "שעה מקומית:"
    };

    const rtlLangs = ["ar", "he", "fa", "ur"];
    const userLang = navigator.language || navigator.userLanguage;
    const langPrefix = userLang.slice(0, 2).toLowerCase();
    const isRTL = rtlLangs.includes(langPrefix);
    const timeLabel = localTimeMessages[langPrefix] || localTimeMessages["en"];
    const formatter = new Intl.DateTimeFormat(userLang, {
        hour: 'numeric',
        minute: '2-digit',
        second: '2-digit',
        hour12: undefined
    });

    const timeRegex = /(\b\d{1,2})(?:[:.](\d{2}))?(?:[:.](\d{2}))?\s*(AM|PM)?\s*([A-Za-z]{2,4})\b/gi;

    function convertTime(match, hourStr, minStr, secStr, period, tz) {
        tz = tz.toUpperCase();
        if (!(tz in timezoneOffsets)) return match;

        let hour = parseInt(hourStr);
        let minute = parseInt(minStr || "0");
        let second = parseInt(secStr || "0");

        if (period) {
            period = period.toUpperCase();
            if (period === "PM" && hour !== 12) hour += 12;
            if (period === "AM" && hour === 12) hour = 0;
        }

        const offset = timezoneOffsets[tz];
        const now = new Date();
        const localDate = new Date(Date.UTC(
            now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(),
            hour - offset, minute, second
        ));

        const formatted = formatter.format(localDate);
        return isRTL ? `(${timeLabel} ${formatted}) ${match}` : `${match} (${timeLabel} ${formatted})`;
    }

    function walk(node) {
        if (node.nodeType === 3) return handleText(node);

        if (node.nodeType === 1 && !['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA'].includes(node.tagName)) {
            for (let child = node.firstChild; child; child = child.nextSibling) {
                walk(child);
            }
        }
    }

    function handleText(textNode) {
        const original = textNode.nodeValue;
        const updated = original.replace(timeRegex, convertTime);
        if (updated !== original) textNode.nodeValue = updated;
    }

    walk(document.body);
    const observer = new MutationObserver(mutations => {
        mutations.forEach(m => {
            m.addedNodes.forEach(n => walk(n));
        });
    });
    observer.observe(document.body, { childList: true, subtree: true });
})();