时区转换工具+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 应用,功能完整、结构清晰。 适用于快速使用、系统集成或离线访问,具备良好扩展性。

相关推荐
海晨忆18 分钟前
【Vue】v-if和v-show的区别
前端·javascript·vue.js·v-show·v-if
JiangJiang43 分钟前
🚀 Vue人看React useRef:它不只是替代 ref
javascript·react.js·面试
1024小神1 小时前
在GitHub action中使用添加项目中配置文件的值为环境变量
前端·javascript
龙骑utr1 小时前
qiankun微应用动态设置静态资源访问路径
javascript
Jasmin Tin Wei1 小时前
css易混淆的知识点
开发语言·javascript·ecmascript
齐尹秦1 小时前
CSS 列表样式学习笔记
前端
wsz77771 小时前
js封装系列(一)
javascript
Mnxj1 小时前
渐变边框设计
前端
用户7678797737321 小时前
由Umi升级到Next方案
前端·next.js
快乐的小前端1 小时前
TypeScript基础一
前端