您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Time Converter widget (bottom-left): TCT & your timezone clocks + Any→TCT and TCT→YourTZ converters. Clean corners, compact, themed.
// ==UserScript== // @name Torn: Time → TCT // @namespace Njoric // @version 1.6.6 // @description Time Converter widget (bottom-left): TCT & your timezone clocks + Any→TCT and TCT→YourTZ converters. Clean corners, compact, themed. // @license MIT // @match https://www.torn.com/* // @grant GM_addStyle // @require https://cdnjs.cloudflare.com/ajax/libs/luxon/3.4.4/luxon.min.js // ==/UserScript== (function () { 'use strict'; const { DateTime } = luxon; const KEY = 'tctWidget.settings.v1'; const defaults = { myTZ: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC', collapsed: false }; const load = () => { try { return Object.assign({}, defaults, JSON.parse(localStorage.getItem(KEY)||'{}')); } catch { return {...defaults}; } }; const save = (s) => localStorage.setItem(KEY, JSON.stringify(s)); let S = load(); GM_addStyle(` .tctw-wrap, .tctw-wrap * { box-sizing: border-box; } .tctw-wrap{ position:fixed; left:10px; bottom:10px; z-index:99999; width:300px; background:#191919; border:1px solid #bf542f; border-radius:10px; color:#48711e; font:12px/1.25 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif; box-shadow:0 10px 24px rgba(0,0,0,.45); overflow:hidden; /* Fix: clip inner content to rounded edges */ } .tctw-hdr{ display:flex; align-items:center; justify-content:space-between; padding:8px 10px; border-bottom:1px solid #bf542f; background:#191919; border-top-left-radius:10px; /* Fix: match container curve */ border-top-right-radius:10px; /* Fix: match container curve */ } .tctw-title{ font-weight:700; letter-spacing:.2px } .tctw-btn{ cursor:pointer; user-select:none; padding:3px 8px; font-size:11px; background:#333333; border:1px solid #bf542f; border-radius:6px; color:#48711e; display:inline-block; line-height:1; } .tctw-btn:hover{ background:#2b2b2b } .tctw-btn:focus, .tctw-sel:focus, .tctw-input:focus { outline:none; } .tctw-body{ padding:10px } .tctw-grid{ display:grid; grid-template-columns:1fr; gap:8px } .tctw-box{ background:#333333; border:1px solid #bf542f; border-radius:8px; padding:8px; overflow:hidden; } .tctw-label{ opacity:.9; font-weight:600; font-size:11px; text-transform:uppercase; margin:0 0 4px 0 } .tctw-time{ font-variant-numeric:tabular-nums; font-weight:700; font-size:16px; margin:0 } .tctw-sub{ opacity:.7; font-size:11px; margin-top:2px } .tctw-sel,.tctw-input,.tctw-out{ display:block; width:100%; margin:0; border-radius:6px; border:1px solid #bf542f; background:#191919; color:#48711e; font-size:12px; } .tctw-sel,.tctw-input{ padding:5px 8px; } .tctw-out{ padding:6px 8px; border-style:dashed; margin-top:4px; word-break:break-word; font-variant-numeric:tabular-nums } .tctw-sel:focus,.tctw-input:focus{ border-color:#61d0d7; box-shadow:0 0 0 2px rgba(84,161,230,.15) } .tctw-help{ opacity:.7; font-size:11px; margin-top:4px } .tctw-collapsed .tctw-body{ display:none } `); const root = document.createElement('div'); root.className = 'tctw-wrap' + (S.collapsed ? ' tctw-collapsed':''); root.innerHTML = ` <div class="tctw-hdr"> <div class="tctw-title">Time Converter</div> <div><span class="tctw-btn" id="tctw-collapse">${S.collapsed ? 'Expand' : 'Collapse'}</span></div> </div> <div class="tctw-body"> <div class="tctw-grid"> <div class="tctw-box"> <div class="tctw-label">TCT now</div> <div class="tctw-time" id="tctw-tct-now">--:--:--</div> <div class="tctw-sub" id="tctw-tct-date">--/--/--</div> </div> <div class="tctw-box"> <div class="tctw-label">My timezone</div> <select class="tctw-sel" id="tctw-tz"></select> <div class="tctw-time" id="tctw-local-now">--:--:--</div> <div class="tctw-sub" id="tctw-local-date">--/--/--</div> </div> <div class="tctw-box"> <div class="tctw-label">Any → TCT</div> <input class="tctw-input" id="tctw-any" placeholder="e.g. 10:00 CST | 1000 CST | 7am PST | 2025-09-16 08:00 CEST"> <div class="tctw-help">If date is omitted, today (in the source TZ) is assumed.</div> <div class="tctw-out" id="tctw-any-out"></div> </div> <div class="tctw-box"> <div class="tctw-label">TCT → My TZ</div> <input class="tctw-input" id="tctw-tct-in" placeholder="TCT time, e.g. 11:00 or 2025-09-16 11:00"> <div class="tctw-help">Enter TCT (UTC+0). If date is omitted, today (UTC) is used.</div> <div class="tctw-out" id="tctw-tct-out"></div> </div> </div> </div> `; document.body.appendChild(root); const el = { collapse: root.querySelector('#tctw-collapse'), tz: root.querySelector('#tctw-tz'), tctNow: root.querySelector('#tctw-tct-now'), tctDate: root.querySelector('#tctw-tct-date'), localNow: root.querySelector('#tctw-local-now'), localDate: root.querySelector('#tctw-local-date'), anyIn: root.querySelector('#tctw-any'), anyOut: root.querySelector('#tctw-any-out'), tctIn: root.querySelector('#tctw-tct-in'), tctOut: root.querySelector('#tctw-tct-out') }; const COMMON_TZS = [ 'UTC','Europe/London','Europe/Berlin','Europe/Helsinki', 'America/New_York','America/Chicago','America/Denver','America/Los_Angeles', 'America/Phoenix','America/Anchorage','Pacific/Honolulu', 'Asia/Kolkata','Asia/Singapore','Asia/Hong_Kong','Asia/Tokyo','Asia/Seoul', 'Australia/Sydney','Australia/Adelaide','Australia/Perth','Pacific/Auckland' ]; const uniq = (a) => Array.from(new Set(a)); const tzOptions = uniq([S.myTZ, Intl.DateTimeFormat().resolvedOptions().timeZone, ...COMMON_TZS]).filter(Boolean); el.tz.innerHTML = tzOptions.map(z => `<option value="${z}">${z}</option>`).join(''); el.tz.value = S.myTZ; const fmtTCT = (dt) => dt.setZone('UTC').toFormat('HH:mm:ss'); const fmtDateTCT = (dt) => dt.setZone('UTC').toFormat('EEE dd/MM/yy'); const fmtLocal = (dt,z) => dt.setZone(z).toFormat('HH:mm:ss'); const fmtDateLocal = (dt,z) => dt.setZone(z).toFormat('EEE dd/MM/yy'); function tick(){ const now = DateTime.now(); el.tctNow.textContent = fmtTCT(now); el.tctDate.textContent = fmtDateTCT(now); el.localNow.textContent = fmtLocal(now, S.myTZ); el.localDate.textContent = fmtDateLocal(now, S.myTZ); } tick(); setInterval(tick, 1000); el.collapse.addEventListener('click', () => { S.collapsed = !S.collapsed; root.classList.toggle('tctw-collapsed', S.collapsed); el.collapse.textContent = S.collapsed ? 'Expand' : 'Collapse'; save(S); }); // --- Timezone abbreviations --- const TZ_ABBR = { UTC:'UTC', GMT:'UTC', EST:'America/New_York', EDT:'America/New_York', CST:'America/Chicago', CDT:'America/Chicago', MST:'America/Denver', MDT:'America/Denver', PST:'America/Los_Angeles', PDT:'America/Los_Angeles', AKST:'America/Anchorage', AKDT:'America/Anchorage', HST:'Pacific/Honolulu', CET:'Europe/Berlin', CEST:'Europe/Berlin', EET:'Europe/Helsinki', EEST:'Europe/Helsinki', BST:'Europe/London', IST:'Asia/Kolkata', SGT:'Asia/Singapore', HKT:'Asia/Hong_Kong', JST:'Asia/Tokyo', KST:'Asia/Seoul', AEST:'Australia/Sydney', AEDT:'Australia/Sydney', ACST:'Australia/Adelaide', ACDT:'Australia/Adelaide', AWST:'Australia/Perth', NZST:'Pacific/Auckland', NZDT:'Pacific/Auckland', SAST:'Africa/Johannesburg', AZT:'America/Phoenix' }; const resolveTZ = (t) => { if (!t) return null; const k=t.toUpperCase(); if (TZ_ABBR[k]) return TZ_ABBR[k]; if (/^[A-Za-z]+\/[A-Za-z_]+$/.test(t)) return t; return null; }; function parseAnyToUTC(query) { const raw = (query || '').trim().replace(/\s+/g, ' '); if (!raw) return { ok:false, err:'Type a time and timezone (e.g., "10:00 CST", "1000 CST")' }; const parts = raw.split(' '); let tzToken = parts[parts.length - 1]; let tz = resolveTZ(tzToken); let rest = parts.slice(0, parts.length - 1).join(' '); if (!tz) { const iana = parts.find(p => p.includes('/')); if (iana) { tz = iana; rest = parts.filter(p => p !== iana).join(' '); } } if (!tz) return { ok:false, err:`Could not recognize timezone in "${raw}".` }; let dateStr = null; let timeStr = rest.trim(); const m = timeStr.match(/^(\d{4}[-/]\d{2}[-/]\d{2})\s+(.+)$/); if (m) { dateStr = m[1].replace(/\//g, '-'); timeStr = m[2]; } timeStr = timeStr.replace(/(\d)(am|pm)$/i, '$1 $2'); if (/^\d{3,4}$/.test(timeStr)) { const s = timeStr.padStart(4, '0'); const hh = parseInt(s.slice(0, -2), 10), mm = parseInt(s.slice(-2), 10); if (hh >= 0 && hh <= 23 && mm >= 0 && mm <= 59) timeStr = `${hh}:${String(mm).padStart(2,'0')}`; } if (/^\d{1,2}(\s*[ap]m)?$/i.test(timeStr)) timeStr = timeStr.replace(/^(\d{1,2})(?:\s*([ap]m))?$/i, '$1:00$2'); const formats = ['H:mm','h:mm a','H','h a','H:mm:ss','h:mm:ss a']; const base = dateStr ? DateTime.fromISO(dateStr, { zone: tz }) : DateTime.now().setZone(tz); let parsed = null; for (const f of formats) { const t = DateTime.fromFormat(timeStr, f, { zone: tz }); if (t.isValid) { parsed = base.set({ hour: t.hour, minute: t.minute, second: t.second }); break; } } if (!parsed) { const t = DateTime.fromISO(timeStr, { zone: tz }); if (t.isValid) parsed = t; } if (!parsed || !parsed.isValid) return { ok:false, err:`Could not parse the time part "${timeStr}".` }; const dtUTC = parsed.setZone('UTC'); return { ok:true, dtUTC }; } function parseTCTtoMyTZ(query, myTZ) { const raw = (query || '').trim(); if (!raw) return { ok:false, err:'Enter a TCT time like "11:00" or "2025-09-16 11:00"' }; const nowUTC = DateTime.now().setZone('UTC'); let parsed = DateTime.fromFormat(raw, 'H:mm', { zone:'UTC' }); if (!parsed.isValid) parsed = DateTime.fromFormat(raw, 'H:mm:ss', { zone:'UTC' }); if (!parsed.isValid) parsed = DateTime.fromISO(raw, { zone:'UTC' }); if (!parsed.isValid) return { ok:false, err:'Use "HH:mm", "HH:mm:ss", or "YYYY-MM-DD HH:mm".' }; if (!/^\d{4}-\d{2}-\d{2}/.test(raw)) { parsed = nowUTC.set({ hour: parsed.hour, minute: parsed.minute, second: parsed.second }); } const local = parsed.setZone(myTZ); return { ok:true, local }; } const runAnyToTCT = () => { const q = el.anyIn.value; if (!q) { el.anyOut.textContent = ''; return; } const res = parseAnyToUTC(q); el.anyOut.textContent = res.ok ? `TCT: ${res.dtUTC.toFormat("EEE HH:mm - dd/MM/yy")}` : res.err; }; const runTCTtoMyTZ = () => { const q = el.tctIn.value; if (!q) { el.tctOut.textContent = ''; return; } const res = parseTCTtoMyTZ(q, S.myTZ); el.tctOut.textContent = res.ok ? res.local.toFormat(`EEE HH:mm - dd/MM/yy '(${S.myTZ})'`) : res.err; }; el.tz.addEventListener('change', () => { S.myTZ = el.tz.value; save(S); tick(); runTCTtoMyTZ(); }); el.anyIn.addEventListener('input', runAnyToTCT); el.tctIn.addEventListener('input', runTCTtoMyTZ); document.addEventListener('keydown', (e) => { if (e.altKey && (e.key === 't' || e.key === 'T')) { if (S.collapsed) { S.collapsed = false; root.classList.remove('tctw-collapsed'); el.collapse.textContent = 'Collapse'; save(S); setTimeout(()=> el.anyIn.focus(), 0); } else { el.anyIn.focus(); } } }); })();