JS 时区转换工具 + PWA 离线网页

时区转换工具+PWA离线网页

一、时区转换工具对比

工具 说明
Date 原生 JS API,有限的时区支持,无法指定时区,仅使用本地时区。
Intl.DateTimeFormat 原生格式化显示,可指定时区,但不能修改时区逻辑。
luxon 强烈推荐,现代、轻量、功能强,原生支持时区、时间戳、格式化等。
dayjs + timezone 插件 类似 moment,更现代,但时区支持需插件。
moment-timezone 功能全面但体积大,moment 官方已不推荐用于新项目。

二、Luxon 使用示例

1. 美国时间 -> 中国时间

javascript 复制代码
import { DateTime } from 'luxon'

const usTime = DateTime.fromISO('2025-04-01T11:11:00', { zone: 'America/Los_Angeles' })

const timestamp = usTime.toMillis()

const cnTime = usTime.setZone('Asia/Shanghai')

console.log('美国时间:', usTime.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ'))
console.log('时间戳:', timestamp)
console.log('对应的中国时间:', cnTime.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ'))
shell 复制代码
美国时间:2025-04-01 11:11:00 GMT-7
时间戳:1743521460000
对应的中国时间:2025-04-02 02:11:00 GMT+8

2. 中国时间 -> 美国时间

javascript 复制代码
const cn = DateTime.fromISO('2025-04-01T11:11:00', { zone: 'Asia/Shanghai' })

const us = cn.setZone('America/Los_Angeles')

console.log('中国时间:', cn.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ'))
console.log('对应的美国时间:', us.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ'))
console.log('时间戳(UTC):', cn.toMillis())

3. 转换逻辑总结

场景 方法
指定时区的时间 → 时间戳 DateTime.fromISO(...).toMillis()
时间戳 → 指定时区时间 DateTime.fromMillis(...).setZone(...)
不同时区之间转换 .setZone(...)
时间格式化 .toFormat('yyyy-MM-dd HH:mm:ss') 等

4. 常用时区 ID 表

名称 IANA 时区 ID
北京/上海(Asia/Shanghai) Asia/Shanghai
香港(Asia/Hong_Kong) Asia/Hong_Kong
日本(Asia/Tokyo) Asia/Tokyo
韩国(Asia/Seoul) Asia/Seoul
新加坡(Asia/Singapore) Asia/Singapore
印度(Asia/Kolkata) Asia/Kolkata
美国西部 - 洛杉矶(America/Los_Angeles) America/Los_Angeles
美国中部 - 芝加哥(America/Chicago) America/Chicago
美国东部 - 纽约(America/New_York) America/New_York
英国(Europe/London) Europe/London
德国(Europe/Berlin) Europe/Berlin
法国(Europe/Paris) Europe/Paris
澳大利亚 - 悉尼(Australia/Sydney) Australia/Sydney
新西兰(Pacific/Auckland) Pacific/Auckland
夏威夷(Pacific/Honolulu) Pacific/Honolulu
UTC(协调世界时) UTC

三、时区转换脚本

1. NodeJS 脚本(使用 luxon)

javascript 复制代码
const { DateTime } = require('luxon')

function convertTime({
  timeStr = '2025-04-01 11:11:00',
  fromZone = 'America/Los_Angeles',
  toZone = 'Asia/Shanghai'
}) {
  const fromTime = DateTime.fromFormat(timeStr, 'yyyy-MM-dd HH:mm:ss', { zone: fromZone })

  const toTime = fromTime.setZone(toZone)

  console.log(`原始时间 (${fromZone}):`, fromTime.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ'))
  console.log(`时间戳(UTC 毫秒):`, fromTime.toMillis())
  console.log(`转换后 (${toZone}):`, toTime.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ'))
}

// 修改这里的参数即可
convertTime({
  timeStr: '2025-04-01 11:11:00',
  fromZone: 'America/Los_Angeles',
  toZone: 'Asia/Shanghai'
})

2. Python 脚本(使用 pytz)

shell 复制代码
pip install pytz
python 复制代码
from datetime import datetime
import pytz

def convert_time(time_str='2025-04-01 11:11:00', from_zone='America/Los_Angeles', to_zone='Asia/Shanghai'):
    from_tz = pytz.timezone(from_zone)
    to_tz = pytz.timezone(to_zone)

    naive_dt = datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S')
    from_dt = from_tz.localize(naive_dt)

    to_dt = from_dt.astimezone(to_tz)

    print(f'原始时间 ({from_zone}): {from_dt.strftime("%Y-%m-%d %H:%M:%S %Z%z")}')
    print(f'时间戳(UTC 秒): {int(from_dt.timestamp())}')
    print(f'转换后 ({to_zone}): {to_dt.strftime("%Y-%m-%d %H:%M:%S %Z%z")}')

# 修改参数即可
convert_time(
    time_str='2025-04-01 11:11:00',
    from_zone='America/Los_Angeles',
    to_zone='Asia/Shanghai'
)

四、网页小工具

使用 Luxon + HTML 原生控件制作的小工具,支持:

  • 输入时间
  • 原始/目标时区选择
  • 时间戳显示
  • 一键复制

1. 代码展示

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>🌏 时区时间转换工具</title>
  <script src="https://cdn.jsdelivr.net/npm/luxon@3/build/global/luxon.min.js"></script>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      padding: 2rem;
      max-width: 700px;
      margin: auto;
      background: #f8f9fa;
    }
    h2 {
      text-align: center;
      margin-bottom: 2rem;
    }
    label {
      font-weight: bold;
      margin-top: 1rem;
      display: block;
    }
    input, select, button {
      width: 100%;
      padding: 0.6rem;
      margin: 0.4rem 0 1rem;
      border: 1px solid #ccc;
      border-radius: 5px;
      font-size: 1rem;
    }
    button {
      background: #007bff;
      color: white;
      cursor: pointer;
      border: none;
      transition: background 0.3s;
    }
    button:hover {
      background: #0056b3;
    }
    .result {
      background: #fff;
      border-left: 5px solid #007bff;
      padding: 1rem;
      margin-top: 1rem;
      border-radius: 5px;
      white-space: pre-wrap;
      font-size: 0.95rem;
    }
    .error {
      color: red;
      margin-top: 1rem;
    }
    .copy-btn {
      margin-top: 0.5rem;
      background: #28a745;
    }
    .copy-btn:hover {
      background: #1e7e34;
    }
    table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 2rem;
      background: #fff;
    }
    th, td {
      border: 1px solid #ddd;
      padding: 0.6rem;
      text-align: left;
    }
    th {
      background-color: #007bff;
      color: white;
    }
  </style>
</head>
<body>
  <h2>🕒 时区转换小工具</h2>

  <label for="inputDate">选择时间</label>
  <input type="datetime-local" id="inputDate" />

  <label for="fromZone">原始时区</label>
  <select id="fromZone"></select>

  <label for="toZone">目标时区</label>
  <select id="toZone"></select>

  <button onclick="convertTime()">转换时间</button>

  <div class="result" id="output">👇 转换结果将在这里显示</div>
  <button class="copy-btn" onclick="copyResult()">📋 复制结果</button>
  <div class="error" id="error"></div>

  <script>
    const { DateTime } = luxon

    const timezones = [
      { label: '北京(Asia/Shanghai)', value: 'Asia/Shanghai' },
      { label: '香港(Asia/Hong_Kong)', value: 'Asia/Hong_Kong' },
      { label: '日本(Asia/Tokyo)', value: 'Asia/Tokyo' },
      { label: '韩国(Asia/Seoul)', value: 'Asia/Seoul' },
      { label: '新加坡(Asia/Singapore)', value: 'Asia/Singapore' },
      { label: '印度(Asia/Kolkata)', value: 'Asia/Kolkata' },
      { label: '美国西部 - 洛杉矶(America/Los_Angeles)', value: 'America/Los_Angeles' },
      { label: '美国中部 - 芝加哥(America/Chicago)', value: 'America/Chicago' },
      { label: '美国东部 - 纽约(America/New_York)', value: 'America/New_York' },
      { label: '英国(Europe/London)', value: 'Europe/London' },
      { label: '德国(Europe/Berlin)', value: 'Europe/Berlin' },
      { label: '法国(Europe/Paris)', value: 'Europe/Paris' },
      { label: '澳大利亚 - 悉尼(Australia/Sydney)', value: 'Australia/Sydney' },
      { label: '新西兰(Pacific/Auckland)', value: 'Pacific/Auckland' },
      { label: '夏威夷(Pacific/Honolulu)', value: 'Pacific/Honolulu' },
      { label: 'UTC(协调世界时)', value: 'UTC' },
    ]

    function renderTimezoneOptions() {
      const fromSelect = document.getElementById('fromZone')
      const toSelect = document.getElementById('toZone')
      const tableBody = document.getElementById('timezoneTable')

      timezones.forEach(({ label, value }) => {
        const opt1 = new Option(label, value)
        const opt2 = new Option(label, value)
        fromSelect.appendChild(opt1)
        toSelect.appendChild(opt2)
      })

      fromSelect.value = 'Asia/Shanghai'
      toSelect.value = 'America/Los_Angeles'
    }

    document.addEventListener('DOMContentLoaded', () => {
      renderTimezoneOptions()
      const now = new Date()
      const local = now.toISOString().slice(0, 16)
      document.getElementById('inputDate').value = local
    })

    function convertTime() {
      const input = document.getElementById('inputDate').value
      const fromZone = document.getElementById('fromZone').value
      const toZone = document.getElementById('toZone').value
      const output = document.getElementById('output')
      const error = document.getElementById('error')

      error.textContent = ''

      if (!input) {
        error.textContent = '❌ 请选择一个时间'
        return
      }

      try {
        const dt = DateTime.fromISO(input, { zone: fromZone })
        const toTime = dt.setZone(toZone)

        const result = `
🌍 原始时间(${fromZone}):
    ${dt.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ')}

🕗 时间戳(UTC 毫秒):
    ${dt.toMillis()}

➡️ 转换后时间(${toZone}):
    ${toTime.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ')}
            `.trim()

        output.textContent = result
      }
      catch (e) {
        error.textContent = '❌ 转换失败,请检查输入'
      }
    }

    function copyResult() {
      const result = document.getElementById('output').textContent
      if (!result || result.includes('将在这里显示'))
        return
      navigator.clipboard.writeText(result).then(() => {
        alert('✅ 已复制到剪贴板!')
      })
    }
  </script>
</body>
</html>

2. 示例截图

五、PWA 应用支持

1. PWA 结构

shell 复制代码
.
├── icons
│   ├── time_192.png
│   └── time_512.png
├── index.html
├── luxon.min.js
├── manifest.json
└── service-worker.js

2. manifest.json

用于定义名称、图标、启动方式等:

json 复制代码
{
  "name": "时区时间转换工具",
  "short_name": "时区转换",
  "start_url": "./index.html",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#007bff",
  "description": "支持多时区时间互转、时间戳生成的轻量工具",
  "icons": [
    {
      "src": "icons/time_192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icons/time_512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

3. service-worker.js

实现核心缓存功能,支持离线访问:

javascript 复制代码
const CACHE_NAME = 'timezone-converter-0.0.1'
const urlsToCache = [
  './',
  './index.html',
  './manifest.json',
  './service-worker.js',
  './icons/time_192.png',
  './icons/time_512.png',
  './luxon.min.js'
]

// 安装时预缓存核心资源
self.addEventListener('install', event => {
  self.skipWaiting()
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  )
})

// 激活时清除旧缓存
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys =>
      Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)))
    )
  )
  self.clients.claim()
})

// 拦截所有请求,优先从缓存读取,失败则网络请求
self.addEventListener('fetch', event => {
  const request = event.request

  event.respondWith(
    caches.match(request).then(cachedResponse => {
      if (cachedResponse) return cachedResponse

      return fetch(request)
        .then(networkResponse => {
          if (
            networkResponse &&
            networkResponse.status === 200 &&
            request.url.startsWith(self.location.origin)
          ) {
            const cloned = networkResponse.clone()
            caches.open(CACHE_NAME).then(cache => {
              cache.put(request, cloned)
            })
          }
          return networkResponse
        })
        .catch(() => {
          if (request.headers.get('accept')?.includes('text/html')) {
            return caches.match('./index.html')
          }
        })
    })
  )
})

4. html 文件

整合 Luxon + PWA 注册逻辑

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>🌏 时区时间转换工具1</title>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#007bff" />

  <link rel="manifest" href="manifest.json" />
  <link rel="icon" href="icons/time_192.png" />
  <link rel="apple-touch-icon" href="icons/time_512.png" />

  <script src="./luxon.min.js"></script>

  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      padding: 2rem;
      max-width: 700px;
      margin: auto;
      background: #f8f9fa;
    }
    h2 {
      text-align: center;
      margin-bottom: 2rem;
    }
    label {
      font-weight: bold;
      margin-top: 1rem;
      display: block;
    }
    input, select, button {
      width: 100%;
      padding: 0.6rem;
      margin: 0.4rem 0 1rem;
      border: 1px solid #ccc;
      border-radius: 5px;
      font-size: 1rem;
    }
    button {
      background: #007bff;
      color: white;
      cursor: pointer;
      border: none;
      transition: background 0.3s;
    }
    button:hover {
      background: #0056b3;
    }
    .result {
      background: #fff;
      border-left: 5px solid #007bff;
      padding: 1rem;
      margin-top: 1rem;
      border-radius: 5px;
      white-space: pre-wrap;
      font-size: 0.95rem;
    }
    .error {
      color: red;
      margin-top: 1rem;
    }
    .copy-btn {
      margin-top: 0.5rem;
      background: #28a745;
    }
    .copy-btn:hover {
      background: #1e7e34;
    }
    table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 2rem;
      background: #fff;
    }
    th, td {
      border: 1px solid #ddd;
      padding: 0.6rem;
      text-align: left;
    }
    th {
      background-color: #007bff;
      color: white;
    }
  </style>
</head>
<body>
  <h2>🕒 时区转换小工具</h2>

  <label for="inputDate">选择时间</label>
  <input type="datetime-local" id="inputDate" />

  <label for="fromZone">原始时区</label>
  <select id="fromZone"></select>

  <label for="toZone">目标时区</label>
  <select id="toZone"></select>

  <button onclick="convertTime()">转换时间</button>

  <div class="result" id="output">👇 转换结果将在这里显示</div>
  <button class="copy-btn" onclick="copyResult()">📋 复制结果</button>
  <div class="error" id="error"></div>

  <script>
    const { DateTime } = luxon

    const timezones = [
      { label: '北京(Asia/Shanghai)', value: 'Asia/Shanghai' },
      { label: '香港(Asia/Hong_Kong)', value: 'Asia/Hong_Kong' },
      { label: '日本(Asia/Tokyo)', value: 'Asia/Tokyo' },
      { label: '韩国(Asia/Seoul)', value: 'Asia/Seoul' },
      { label: '新加坡(Asia/Singapore)', value: 'Asia/Singapore' },
      { label: '印度(Asia/Kolkata)', value: 'Asia/Kolkata' },
      { label: '美国西部 - 洛杉矶(America/Los_Angeles)', value: 'America/Los_Angeles' },
      { label: '美国中部 - 芝加哥(America/Chicago)', value: 'America/Chicago' },
      { label: '美国东部 - 纽约(America/New_York)', value: 'America/New_York' },
      { label: '英国(Europe/London)', value: 'Europe/London' },
      { label: '德国(Europe/Berlin)', value: 'Europe/Berlin' },
      { label: '法国(Europe/Paris)', value: 'Europe/Paris' },
      { label: '澳大利亚 - 悉尼(Australia/Sydney)', value: 'Australia/Sydney' },
      { label: '新西兰(Pacific/Auckland)', value: 'Pacific/Auckland' },
      { label: '夏威夷(Pacific/Honolulu)', value: 'Pacific/Honolulu' },
      { label: 'UTC(协调世界时)', value: 'UTC' }
    ]

    function renderTimezoneOptions() {
      const fromSelect = document.getElementById('fromZone')
      const toSelect = document.getElementById('toZone')

      timezones.forEach(({ label, value }) => {
        const opt1 = new Option(label, value)
        const opt2 = new Option(label, value)
        fromSelect.appendChild(opt1)
        toSelect.appendChild(opt2)

      })

      fromSelect.value = 'Asia/Shanghai'
      toSelect.value = 'America/Los_Angeles'
    }

    function convertTime() {
      const input = document.getElementById('inputDate').value
      const fromZone = document.getElementById('fromZone').value
      const toZone = document.getElementById('toZone').value
      const output = document.getElementById('output')
      const error = document.getElementById('error')

      error.textContent = ''

      if (!input) {
        error.textContent = '❌ 请选择一个时间'
        return
      }

      try {
        const dt = DateTime.fromISO(input, { zone: fromZone })
        const toTime = dt.setZone(toZone)

        const result = `
🌍 原始时间(${fromZone}):
${dt.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ')}

🕗 时间戳(UTC 毫秒):
${dt.toMillis()}

➡️ 转换后时间(${toZone}):
${toTime.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ')}
        `.trim()

        output.textContent = result
      } catch (e) {
        error.textContent = '❌ 转换失败,请检查输入'
      }
    }

    function copyResult() {
      const result = document.getElementById('output').textContent
      if (!result || result.includes('将在这里显示')) return
      navigator.clipboard.writeText(result).then(() => {
        alert('✅ 已复制到剪贴板!')
      })
    }

    // 初始化
    document.addEventListener('DOMContentLoaded', () => {
      renderTimezoneOptions()
      const now = new Date()
      const local = now.toISOString().slice(0, 16)
      document.getElementById('inputDate').value = local
    })

    // 注册 PWA service worker
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('service-worker.js')
          .then(() => console.log('✅ Service Worker 注册成功'))
          .catch(err => console.log('❌ 注册失败:', err))
      })
    }
  </script>
</body>
</html>

5. Live server 启动

启动完成安装到本地即可

六、总结

本项目对比并选用 Luxon 实现多时区转换,支持各时区时间互转。 提供 NodeJS 与 Python 脚本、网页小工具及 PWA 应用,功能完整、结构清晰。 适用于快速使用、系统集成或离线访问,具备良好扩展性。

相关推荐
前端九哥3 分钟前
🚀 新一代图片格式 AVIF,对比 WebP/JPEG 有多强?【附真实图片对比】
前端
谦谦橘子3 分钟前
服务端渲染原理解析姐妹篇
前端·javascript·react.js
i编程_撸码4 分钟前
webpack详细打包配置,包含性能优化、资源处理...
前端
小小小小宇6 分钟前
React 中 useMemo 和 useCallback 源码原理
前端
Trae首席推荐官9 分钟前
Trae 版本更新|支持自定义智能体、MCP等,打造个人专属“AI 工程师”
前端·trae
木三_copy9 分钟前
前端截图工具--html2canvas和html-to-image的一些踩坑
前端
小桥风满袖11 分钟前
Three.js-硬要自学系列7 (查看几何体顶点位置和索引、旋转,缩放,平移几何体)
前端·css·three.js
kim__jin13 分钟前
Vue3 使用项目内嵌iFrame
前端
独立开阀者_FwtCoder26 分钟前
# 一天 Star 破万的开源项目「GitHub 热点速览」
前端·javascript·面试
天天扭码37 分钟前
前端进阶 | 面试必考—— JavaScript手写定时器
前端·javascript·面试