Greasy Fork

Greasy Fork is available in English.

时间修改器

修改浏览器JS获取的本地时间,支持偏移和固定时间设置

当前为 2025-05-27 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         时间修改器
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  修改浏览器JS获取的本地时间,支持偏移和固定时间设置
// @author       You
// @match        *://*/*
// @grant        none
// @run-at       document-start
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 配置存储键名
    const STORAGE_KEY = 'TIME_MODIFIER_CONFIG';

    // 默认配置
    const defaultConfig = {
        enabled: false,
        mode: 'offset', // 'offset' 或 'fixed'
        offsetHours: 0,
        offsetMinutes: 0,
        fixedDateTime: '',
        enabledDomains: [],
        uiCollapsed: false
    };

    // 获取当前域名
    const currentDomain = window.location.hostname;

    // 加载配置
    function loadConfig() {
        try {
            const stored = localStorage.getItem(STORAGE_KEY);
            return stored ? { ...defaultConfig, ...JSON.parse(stored) } : defaultConfig;
        } catch (e) {
            return defaultConfig;
        }
    }

    // 保存配置
    function saveConfig(config) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
    }

    // 检查当前域名是否启用
    function isDomainEnabled(config) {
        return config.enabledDomains.includes(currentDomain) || config.enabledDomains.length === 0;
    }

    // 计算时间偏移
    function calculateTimeOffset(config) {
        if (config.mode === 'offset') {
            return (config.offsetHours * 60 + config.offsetMinutes) * 60 * 1000;
        } else if (config.mode === 'fixed' && config.fixedDateTime) {
            const fixedTime = new Date(config.fixedDateTime).getTime();
            const currentTime = Date.now();
            return fixedTime - currentTime;
        }
        return 0;
    }

    // 修改时间相关的原生方法
    function modifyTimeMethods(offsetMs) {
        const originalDate = window.Date;
        const originalNow = Date.now;
        const originalGetTime = Date.prototype.getTime;

        // 重写 Date 构造函数
        window.Date = function(...args) {
            if (args.length === 0) {
                const modifiedTime = new originalDate(originalNow() + offsetMs);
                return modifiedTime;
            }
            return new originalDate(...args);
        };

        // 继承原型
        window.Date.prototype = originalDate.prototype;

        // 重写静态方法
        window.Date.now = function() {
            return originalNow() + offsetMs;
        };

        // 重写其他静态方法
        Object.getOwnPropertyNames(originalDate).forEach(prop => {
            if (typeof originalDate[prop] === 'function' && prop !== 'now') {
                window.Date[prop] = originalDate[prop];
            }
        });

        // 重写 getTime 方法
        Date.prototype.getTime = function() {
            const originalTime = originalGetTime.call(this);
            if (this.constructor === window.Date && arguments.length === 0) {
                return originalTime + offsetMs;
            }
            return originalTime;
        };
    }

    // 格式化日期时间为本地输入格式
    function formatDateTimeLocal(date) {
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const day = String(date.getDate()).padStart(2, '0');
        const hours = String(date.getHours()).padStart(2, '0');
        const minutes = String(date.getMinutes()).padStart(2, '0');
        return `${year}-${month}-${day}T${hours}:${minutes}`;
    }

    // 创建UI界面
    function createUI() {
        const config = loadConfig();

        // 主容器
        const container = document.createElement('div');
        container.id = 'time-modifier-ui';
        container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 999999;
            font-family: Arial, sans-serif;
            font-size: 12px;
            background: #fff;
            border: 1px solid #ddd;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            min-width: 320px;
            transition: all 0.3s ease;
        `;

        // 标题栏
        const header = document.createElement('div');
        header.style.cssText = `
            background: #f5f5f5;
            padding: 8px 12px;
            border-bottom: 1px solid #ddd;
            border-radius: 7px 7px 0 0;
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            align-items: center;
            user-select: none;
        `;
        header.innerHTML = `
            <span style="font-weight: bold; color: #333;">[表情] 时间修改器</span>
            <span id="toggle-btn" style="cursor: pointer; font-size: 14px;">${config.uiCollapsed ? '▼' : '▲'}</span>
        `;

        // 内容区域
        const content = document.createElement('div');
        content.id = 'time-modifier-content';
        content.style.cssText = `
            padding: 12px;
            ${config.uiCollapsed ? 'display: none;' : ''}
        `;

        // 启用开关
        const enableRow = document.createElement('div');
        enableRow.style.cssText = 'margin-bottom: 12px; display: flex; align-items: center;';
        enableRow.innerHTML = `
            <label style="display: flex; align-items: center; cursor: pointer;">
                <input type="checkbox" id="enable-checkbox" ${config.enabled ? 'checked' : ''}
                       style="margin-right: 8px;">
                <span>启用时间修改</span>
            </label>
        `;

        // 模式选择
        const modeRow = document.createElement('div');
        modeRow.style.cssText = 'margin-bottom: 12px;';
        modeRow.innerHTML = `
            <div style="margin-bottom: 6px; color: #666; font-weight: bold;">修改模式:</div>
            <div style="display: flex; gap: 15px;">
                <label style="display: flex; align-items: center; cursor: pointer;">
                    <input type="radio" name="mode" value="offset" ${config.mode === 'offset' ? 'checked' : ''}
                           style="margin-right: 5px;">
                    <span>时间偏移</span>
                </label>
                <label style="display: flex; align-items: center; cursor: pointer;">
                    <input type="radio" name="mode" value="fixed" ${config.mode === 'fixed' ? 'checked' : ''}
                           style="margin-right: 5px;">
                    <span>固定时间</span>
                </label>
            </div>
        `;

        // 时间偏移设置
        const offsetRow = document.createElement('div');
        offsetRow.id = 'offset-settings';
        offsetRow.style.cssText = `margin-bottom: 12px; ${config.mode === 'fixed' ? 'display: none;' : ''}`;
        offsetRow.innerHTML = `
            <div style="margin-bottom: 6px; color: #666;">时间偏移:</div>
            <div style="display: flex; gap: 10px; align-items: center;">
                <input type="number" id="hours-input" value="${config.offsetHours}"
                       style="width: 60px; padding: 4px; border: 1px solid #ddd; border-radius: 4px;"
                       min="-23" max="23"> 小时
                <input type="number" id="minutes-input" value="${config.offsetMinutes}"
                       style="width: 60px; padding: 4px; border: 1px solid #ddd; border-radius: 4px;"
                       min="-59" max="59"> 分钟
            </div>
        `;

        // 固定时间设置
        const fixedRow = document.createElement('div');
        fixedRow.id = 'fixed-settings';
        fixedRow.style.cssText = `margin-bottom: 12px; ${config.mode === 'offset' ? 'display: none;' : ''}`;
        const currentTime = new Date();
        const defaultDateTime = config.fixedDateTime || formatDateTimeLocal(currentTime);
        fixedRow.innerHTML = `
            <div style="margin-bottom: 6px; color: #666;">固定时间:</div>
            <div style="display: flex; flex-direction: column; gap: 6px;">
                <input type="datetime-local" id="fixed-datetime-input" value="${defaultDateTime}"
                       style="padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px;">
                <div style="display: flex; gap: 5px;">
                    <button id="set-current-time" style="padding: 4px 8px; background: #17a2b8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">设为当前时间</button>
                    <button id="add-one-hour" style="padding: 4px 8px; background: #ffc107; color: black; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">+1小时</button>
                    <button id="add-one-day" style="padding: 4px 8px; background: #fd7e14; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">+1天</button>
                </div>
            </div>
        `;

        // 域名管理
        const domainRow = document.createElement('div');
        domainRow.style.cssText = 'margin-bottom: 12px;';
        domainRow.innerHTML = `
            <div style="margin-bottom: 6px; color: #666;">启用域名 (留空表示所有域名):</div>
            <div style="display: flex; gap: 5px; margin-bottom: 5px;">
                <input type="text" id="domain-input" placeholder="输入域名,如: example.com"
                       style="flex: 1; padding: 4px; border: 1px solid #ddd; border-radius: 4px; font-size: 11px;">
                <button id="add-domain-btn" style="padding: 4px 8px; background: #007cba; color: white; border: none; border-radius: 4px; cursor: pointer;">添加</button>
            </div>
            <div id="domain-list" style="max-height: 80px; overflow-y: auto;"></div>
        `;

        // 当前状态显示
        const statusRow = document.createElement('div');
        statusRow.style.cssText = 'margin-bottom: 12px; padding: 8px; background: #f9f9f9; border-radius: 4px; font-size: 11px;';
        statusRow.innerHTML = `
            <div><strong>当前域名:</strong> ${currentDomain}</div>
            <div><strong>系统时间:</strong> <span id="system-time">--</span></div>
            <div id="current-time"><strong>修改后时间:</strong> <span id="time-display">--</span></div>
            <div id="mode-status"><strong>当前模式:</strong> <span id="mode-display">${config.mode === 'offset' ? '时间偏移' : '固定时间'}</span></div>
        `;

        // 操作按钮
        const buttonRow = document.createElement('div');
        buttonRow.style.cssText = 'display: flex; gap: 8px; justify-content: flex-end;';
        buttonRow.innerHTML = `
            <button id="apply-btn" style="padding: 6px 12px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer;">应用</button>
            <button id="reset-btn" style="padding: 6px 12px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer;">重置</button>
        `;

        // 组装UI
        content.appendChild(enableRow);
        content.appendChild(modeRow);
        content.appendChild(offsetRow);
        content.appendChild(fixedRow);
        content.appendChild(domainRow);
        content.appendChild(statusRow);
        content.appendChild(buttonRow);

        container.appendChild(header);
        container.appendChild(content);

        return container;
    }

    // 更新域名列表显示
    function updateDomainList(container, config) {
        const domainList = container.querySelector('#domain-list');
        domainList.innerHTML = '';

        config.enabledDomains.forEach((domain, index) => {
            const domainItem = document.createElement('div');
            domainItem.style.cssText = 'display: flex; justify-content: space-between; align-items: center; padding: 2px 0; font-size: 11px;';
            domainItem.innerHTML = `
                <span>${domain}</span>
                <button data-index="${index}" class="remove-domain" style="background: #dc3545; color: white; border: none; border-radius: 2px; padding: 1px 4px; cursor: pointer; font-size: 10px;">×</button>
            `;
            domainList.appendChild(domainItem);
        });
    }

    // 更新时间显示
    function updateTimeDisplay(container) {
        const systemTimeDisplay = container.querySelector('#system-time');
        const timeDisplay = container.querySelector('#time-display');
        const modeDisplay = container.querySelector('#mode-display');

        if (systemTimeDisplay) {
            systemTimeDisplay.textContent = new Date().toLocaleString();
        }

        if (timeDisplay) {
            const config = loadConfig();
            if (config.enabled && isDomainEnabled(config)) {
                const offsetMs = calculateTimeOffset(config);
                const modifiedTime = new Date(Date.now() + offsetMs);
                timeDisplay.textContent = modifiedTime.toLocaleString();
                timeDisplay.style.color = '#28a745';
                timeDisplay.style.fontWeight = 'bold';
            } else {
                timeDisplay.textContent = '未启用';
                timeDisplay.style.color = '#6c757d';
                timeDisplay.style.fontWeight = 'normal';
            }
        }

        if (modeDisplay) {
            const config = loadConfig();
            modeDisplay.textContent = config.mode === 'offset' ? '时间偏移' : '固定时间';
        }
    }

    // 绑定事件
    function bindEvents(container) {
        const config = loadConfig();

        // 折叠/展开
        const toggleBtn = container.querySelector('#toggle-btn');
        const content = container.querySelector('#time-modifier-content');
        container.querySelector('div').addEventListener('click', (e) => {
            if (e.target === toggleBtn || e.target.parentElement === container.querySelector('div')) {
                const isCollapsed = content.style.display === 'none';
                content.style.display = isCollapsed ? 'block' : 'none';
                toggleBtn.textContent = isCollapsed ? '▲' : '▼';
                config.uiCollapsed = !isCollapsed;
                saveConfig(config);
            }
        });

        // 模式切换
        const modeRadios = container.querySelectorAll('input[name="mode"]');
        modeRadios.forEach(radio => {
            radio.addEventListener('change', () => {
                const offsetSettings = container.querySelector('#offset-settings');
                const fixedSettings = container.querySelector('#fixed-settings');

                if (radio.value === 'offset') {
                    offsetSettings.style.display = 'block';
                    fixedSettings.style.display = 'none';
                } else {
                    offsetSettings.style.display = 'none';
                    fixedSettings.style.display = 'block';
                }
                updateTimeDisplay(container);
            });
        });

        // 固定时间快捷按钮
        const setCurrentTimeBtn = container.querySelector('#set-current-time');
        const addOneHourBtn = container.querySelector('#add-one-hour');
        const addOneDayBtn = container.querySelector('#add-one-day');
        const fixedDatetimeInput = container.querySelector('#fixed-datetime-input');

        setCurrentTimeBtn.addEventListener('click', () => {
            fixedDatetimeInput.value = formatDateTimeLocal(new Date());
            updateTimeDisplay(container);
        });

        addOneHourBtn.addEventListener('click', () => {
            const currentValue = new Date(fixedDatetimeInput.value || new Date());
            currentValue.setHours(currentValue.getHours() + 1);
            fixedDatetimeInput.value = formatDateTimeLocal(currentValue);
            updateTimeDisplay(container);
        });

        addOneDayBtn.addEventListener('click', () => {
            const currentValue = new Date(fixedDatetimeInput.value || new Date());
            currentValue.setDate(currentValue.getDate() + 1);
            fixedDatetimeInput.value = formatDateTimeLocal(currentValue);
            updateTimeDisplay(container);
        });

        // 固定时间输入变化
        fixedDatetimeInput.addEventListener('change', () => {
            updateTimeDisplay(container);
        });

        // 偏移时间输入变化
        const hoursInput = container.querySelector('#hours-input');
        const minutesInput = container.querySelector('#minutes-input');
        [hoursInput, minutesInput].forEach(input => {
            input.addEventListener('input', () => {
                updateTimeDisplay(container);
            });
        });

        // 添加域名
        const addDomainBtn = container.querySelector('#add-domain-btn');
        const domainInput = container.querySelector('#domain-input');
        addDomainBtn.addEventListener('click', () => {
            const domain = domainInput.value.trim();
            if (domain && !config.enabledDomains.includes(domain)) {
                config.enabledDomains.push(domain);
                saveConfig(config);
                updateDomainList(container, config);
                domainInput.value = '';
            }
        });

        // 删除域名
        container.addEventListener('click', (e) => {
            if (e.target.classList.contains('remove-domain')) {
                const index = parseInt(e.target.dataset.index);
                config.enabledDomains.splice(index, 1);
                saveConfig(config);
                updateDomainList(container, config);
            }
        });

        // 应用设置
        const applyBtn = container.querySelector('#apply-btn');
        applyBtn.addEventListener('click', () => {
            const enabled = container.querySelector('#enable-checkbox').checked;
            const mode = container.querySelector('input[name="mode"]:checked').value;
            const hours = parseInt(container.querySelector('#hours-input').value) || 0;
            const minutes = parseInt(container.querySelector('#minutes-input').value) || 0;
            const fixedDateTime = container.querySelector('#fixed-datetime-input').value;

            config.enabled = enabled;
            config.mode = mode;
            config.offsetHours = hours;
            config.offsetMinutes = minutes;
            config.fixedDateTime = fixedDateTime;
            saveConfig(config);

            if (enabled && isDomainEnabled(config)) {
                const offsetMs = calculateTimeOffset(config);
                modifyTimeMethods(offsetMs);
            }

            alert('设置已应用!刷新页面生效。');
        });

        // 重置设置
        const resetBtn = container.querySelector('#reset-btn');
        resetBtn.addEventListener('click', () => {
            if (confirm('确定要重置所有设置吗?')) {
                localStorage.removeItem(STORAGE_KEY);
                location.reload();
            }
        });

        // 初始化域名列表
        updateDomainList(container, config);

        // 定时更新时间显示
        setInterval(() => updateTimeDisplay(container), 1000);
        updateTimeDisplay(container);
    }

    // 初始化
    function init() {
        const config = loadConfig();

        // 如果启用且当前域名在列表中,则修改时间
        if (config.enabled && isDomainEnabled(config)) {
            const offsetMs = calculateTimeOffset(config);
            modifyTimeMethods(offsetMs);
        }

        // 页面加载完成后创建UI
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                setTimeout(() => {
                    const ui = createUI();
                    document.body.appendChild(ui);
                    bindEvents(ui);
                }, 1000);
            });
        } else {
            setTimeout(() => {
                const ui = createUI();
                document.body.appendChild(ui);
                bindEvents(ui);
            }, 1000);
        }
    }

    // 启动脚本
    init();
})();