优雅的实现一个时区处理插件
1. UMD 模式解析
1.1 立即执行函数 (IIFE)
外层是一个立即执行函数,接收 global 和 factory 两个参数。
global 参数说明:
typeof window !== "undefined" ? window : this;
根据不同的运行环境,global 参数会指向:
-
浏览器环境:
window对象 -
Node.js 环境:
global对象 -
其他环境:当前上下文
this
1.2 UMD 模块定义
通过条件判断支持多种模块系统:
if (typeof define === "function" && define.amd) {
// AMD 模式 (RequireJS)
define(function () {
return factory();
});
} else if (typeof module === "object" && module.exports) {
// CommonJS 模式 (Node.js)
module.exports = factory();
} else {
// 浏览器全局变量模式
global.Timezone = factory();
}
支持的模块系统:
-
AMD:用于 RequireJS 等加载器
-
CommonJS:用于 Node.js 环境
-
全局变量:用于直接在浏览器中使用
1.3 工厂函数解析
工厂函数 factory 返回插件的构造函数:
function () {
'use strict';
// 构造函数定义
function Timezone(options) {
}
// 原型方法定义
Timezone.prototype = {
constructor: Timezone, // 修复 constructor 指向
version: '1.0.0',
_init: function () {
}
}
// 返回构造函数
return Timezone;
}
核心组成部分:
-
严格模式:使用
'use strict'确保代码质量 -
构造函数:定义
Timezone类 -
原型方法:通过原型链添加共享方法
-
返回值:导出构造函数供外部使用
1.4 执行流程
UMD 模块的加载和执行流程:
-
立即执行:代码加载后立即执行 IIFE
-
环境检测:检测当前支持的模块系统(AMD / CommonJS / 全局变量)
-
工厂调用:执行
factory()函数,返回Timezone构造函数 -
模块导出:根据环境将构造函数导出到相应位置
(function (global, factory) {
// 环境检测和模块导出逻辑
}(typeof window !== "undefined" ? window : this, function () {
// 工厂函数:创建并返回构造函数
}));
2. 单例模式实现
2.1 为什么使用单例模式?
在时区处理插件中,我们通常只需要一个全局实例来管理配置和状态:
-
避免重复实例:防止多次实例化造成的内存浪费
-
全局状态管理:统一管理时区配置、数据映射表等
-
配置一致性:确保整个应用使用相同的时区设置
2.2 单例模式实现
2.2.1 私有实例存储
// 闭包中的私有变量,存储单例实例
var instance = null;
2.2.2 构造函数实现
function Timezone(options) {
// 1. 如果已存在实例,直接返回
if (instance) {
return instance;
}
// 2. 确保通过 new 调用
if (!(this instanceof Timezone)) {
return new Timezone(options);
}
// 3. 初始化配置
this.options = options || {};
// 4. 保存单例实例
instance = this;
// 5. 执行初始化
this._init();
return instance;
}
实现要点:
-
实例检查:首次检查是否已存在实例,有则直接返回
-
new 检查:确保即使不用
new关键字也能正常工作 -
配置初始化:保存传入的配置选项
-
实例保存:将当前实例保存到闭包变量中
-
初始化执行:调用内部初始化方法
2.2.3 静态方法
/**
* 获取单例实例
*/
Timezone.getInstance = function (options) {
if (!instance) {
instance = new Timezone(options);
}
return instance;
}
/**
* 重置单例(用于测试)
*/
Timezone.resetInstance = function () {
instance = null;
}
2.3 使用方式
方式一:使用 new 关键字
const instance1 = new Timezone({ timezone: 'Asia/Shanghai' });
const instance2 = new Timezone({ timezone: 'America/New_York' });
console.log(instance1 === instance2); // true(返回同一个实例)
方式二:使用 getInstance 静态方法
const instance = Timezone.getInstance({ timezone: 'Asia/Shanghai' });
方式三:不使用 new(自动转换)
const instance = Timezone({ timezone: 'Asia/Shanghai' });
2.4 单例模式的优势
| 优势 | 说明 |
|------|------|
| 内存优化 | 只创建一个实例,减少内存占用 |
| 状态一致 | 全局共享同一个实例,避免状态不一致 |
| 易于管理 | 集中管理配置和数据 |
| 防止冲突 | 避免多个实例之间的配置冲突 |
2.5 完整示例
// 第一次创建实例
const timezone1 = new Timezone({
timezone: 'Asia/Shanghai',
locale: 'zh-CN'
});
// 第二次尝试创建(返回第一次的实例)
const timezone2 = new Timezone({
timezone: 'America/New_York' // 这个配置会被忽略
});
console.log(timezone1 === timezone2); // true
console.log(timezone1.options.timezone); // 'Asia/Shanghai'
// 重置单例后可以创建新实例
Timezone.resetInstance();
const timezone3 = new Timezone({
timezone: 'Europe/London'
});
console.log(timezone1 === timezone3); // false
console.log(timezone3.options.timezone); // 'Europe/London'
3. format 方法详解
3.1 方法说明
format 方法用于格式化日期对象,兼容 dayjs 和 laydate 两种流行的日期格式化风格。
方法签名:
// 原型方法
instance.format(date, format)
// 静态方法(向后兼容)
Timezone.format(date, format)
参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| date | Date | ✅ | - | 要格式化的 JavaScript Date 对象 |
| format | string | ❌ | 'YYYY-MM-DD HH:mm:ss' | 格式化模板字符串 |
返回值:
-
类型:
string -
说明:格式化后的日期字符串,无效日期返回空字符串
''
3.2 支持的格式化标记
3.2.1 年份标记
| 标记 | 说明 | 示例输出 | 兼容性 |
|------|------|----------|--------|
| YYYY | 四位年份 | 2025 | dayjs |
| yyyy | 四位年份 | 2025 | laydate |
| YY | 两位年份 | 25 | dayjs |
| y | 两位年份 | 25 | laydate |
3.2.2 月份标记
| 标记 | 说明 | 示例输出 | 兼容性 |
|------|------|----------|--------|
| MMM | 英文月份缩写 | Jan, Feb, Mar... | 通用 |
| MM | 两位月份(补零) | 01, 02... 12 | 通用 |
| M | 月份(不补零) | 1, 2... 12 | 通用 |
3.2.3 日期标记
| 标记 | 说明 | 示例输出 | 兼容性 |
|------|------|----------|--------|
| DD | 两位日期(补零) | 01, 02... 31 | dayjs |
| dd | 两位日期(补零) | 01, 02... 31 | laydate |
| D | 日期(不补零) | 1, 2... 31 | dayjs |
| d | 日期(不补零) | 1, 2... 31 | laydate |
3.2.4 时间标记(24小时制)
| 标记 | 说明 | 示例输出 | 兼容性 |
|------|------|----------|--------|
| HH | 24小时制小时(补零) | 00, 01... 23 | 通用 |
| H | 24小时制小时(不补零) | 0, 1... 23 | 通用 |
| mm | 分钟(补零) | 00, 01... 59 | 通用 |
| m | 分钟(不补零) | 0, 1... 59 | 通用 |
| ss | 秒(补零) | 00, 01... 59 | 通用 |
| s | 秒(不补零) | 0, 1... 59 | 通用 |
3.2.5 时间标记(12小时制)⭐ 新增
| 标记 | 说明 | 示例输出 | 兼容性 |
|------|------|----------|--------|
| hh | 12小时制小时(补零) | 01, 02... 12 | dayjs |
| h | 12小时制小时(不补零) | 1, 2... 12 | dayjs |
转换规则:
-
00:00 (24小时) → 12:00 AM (12小时)
-
01:00 (24小时) → 01:00 AM (12小时)
-
12:00 (24小时) → 12:00 PM (12小时)
-
13:00 (24小时) → 01:00 PM (12小时)
-
23:00 (24小时) → 11:00 PM (12小时)
3.2.6 上午/下午标记 ⭐ 新增
| 标记 | 说明 | 示例输出 | 兼容性 |
|------|------|----------|--------|
| A | 大写 AM/PM | AM, PM | dayjs |
| a | 小写 am/pm | am, pm | dayjs |
判断规则:
-
AM(上午):小时 < 12(00:00 - 11:59)
-
PM(下午):小时 ≥ 12(12:00 - 23:59)
3.2.7 毫秒标记
| 标记 | 说明 | 示例输出 | 兼容性 |
|------|------|----------|--------|
| SSS | 三位毫秒 | 000, 001... 999 | dayjs |
3.3 使用示例
3.3.1 基础用法
const tz = new Timezone();
const now = new Date('2025-10-28 14:30:45.123');
// 默认格式
tz.format(now); // '2025-10-28 14:30:45'
// 自定义格式
tz.format(now, 'YYYY/MM/DD'); // '2025/10/28'
tz.format(now, 'HH:mm:ss'); // '14:30:45'
3.3.2 dayjs 风格格式
const tz = new Timezone();
const now = new Date('2025-10-28 14:30:45.123');
tz.format(now, 'YYYY-MM-DD HH:mm:ss'); // '2025-10-28 14:30:45'
tz.format(now, 'YYYY-MM-DD HH:mm:ss.SSS'); // '2025-10-28 14:30:45.123'
tz.format(now, 'YY/M/D H:m:s'); // '25/10/28 14:30:45'
tz.format(now, 'MMM DD, YYYY'); // 'Oct 28, 2025'
3.3.3 laydate 风格格式
const tz = new Timezone();
const now = new Date('2025-10-28 14:30:45');
tz.format(now, 'yyyy-MM-dd HH:mm:ss'); // '2025-10-28 14:30:45'
tz.format(now, 'yyyy年MM月dd日'); // '2025年10月28日'
tz.format(now, 'y-M-d H:m:s'); // '25-10-28 14:30:45'
tz.format(now, 'dd/MM/yyyy'); // '28/10/2025'
3.3.4 中文日期格式
const tz = new Timezone();
const now = new Date('2025-10-28 14:30:45');
tz.format(now, 'YYYY年MM月DD日'); // '2025年10月28日'
tz.format(now, 'YYYY年MM月DD日 HH时mm分ss秒'); // '2025年10月28日 14时30分45秒'
tz.format(now, 'yyyy年MM月dd日 HH:mm'); // '2025年10月28日 14:30'
3.3.5 12小时制和AM/PM格式 ⭐ 新增
const tz = new Timezone();
const morning = new Date('2025-10-28 06:30:00');
const afternoon = new Date('2025-10-28 14:30:00');
// 12小时制(补零)+ 大写AM/PM
tz.format(morning, 'YYYY-MM-DD hh:mm:ss A'); // '2025-10-28 06:30:00 AM'
tz.format(afternoon, 'YYYY-MM-DD hh:mm:ss A'); // '2025-10-28 02:30:00 PM'
// 12小时制(不补零)+ 小写am/pm
tz.format(morning, 'h:mm a'); // '6:30 am'
tz.format(afternoon, 'h:mm a'); // '2:30 pm'
// 英文格式
tz.format(afternoon, 'MMM DD, YYYY h:mm A'); // 'Oct 28, 2025 2:30 PM'
// 中文格式
tz.format(afternoon, 'yyyy年MM月dd日 hh:mm A'); // '2025年10月28日 02:30 PM'
// 24小时制对比
tz.format(afternoon, 'HH:mm'); // '14:30' (24小时)
tz.format(afternoon, 'hh:mm A'); // '02:30 PM' (12小时)
特殊时间点示例:
const midnight = new Date('2025-10-28 00:00:00');
const noon = new Date('2025-10-28 12:00:00');
tz.format(midnight, 'hh:mm A'); // '12:00 AM' (午夜)
tz.format(noon, 'hh:mm A'); // '12:00 PM' (正午)
3.3.6 静态方法调用(无需实例化)
const now = new Date('2025-10-28 14:30:45');
// 直接使用静态方法
Timezone.format(now, 'YYYY-MM-DD'); // '2025-10-28'
Timezone.format(now, 'yyyy年MM月dd日'); // '2025年10月28日'
Timezone.format(now, 'MMM DD, yyyy'); // 'Oct 28, 2025'
3.4 常用格式模板
| 格式模板 | 输出示例 | 使用场景 |
|----------|----------|----------|
| YYYY-MM-DD | 2025-10-28 | 标准日期格式 |
| YYYY-MM-DD HH:mm:ss | 2025-10-28 14:30:45 | 完整日期时间(24小时制) |
| YYYY-MM-DD hh:mm:ss A ⭐ | 2025-10-28 02:30:45 PM | 完整日期时间(12小时制) |
| yyyy年MM月dd日 | 2025年10月28日 | 中文日期 |
| MMM DD, yyyy h:mm A ⭐ | Oct 28, 2025 2:30 PM | 英文日期(12小时制) |
| MMM DD, yyyy | Oct 28, 2025 | 英文日期 |
| YYYY/MM/DD HH:mm | 2025/10/28 14:30 | 简短日期时间 |
| HH:mm:ss | 14:30:45 | 仅时间(24小时制) |
| hh:mm A ⭐ | 02:30 PM | 仅时间(12小时制) |
| YY-M-D | 25-10-28 | 简短日期 |
| YYYY-MM-DD HH:mm:ss.SSS | 2025-10-28 14:30:45.123 | 带毫秒 |
3.5 错误处理
const tz = new Timezone();
// 无效日期返回空字符串
tz.format(null); // ''
tz.format(undefined); // ''
tz.format(new Date('invalid')); // ''
tz.format('2025-10-28'); // ''(字符串不是 Date 对象)
// 有效日期
tz.format(new Date()); // '2025-10-28 14:30:45'(当前时间)
3.6 实现原理
format 方法采用正则替换策略实现格式化:
-
标记解析:将格式字符串中的标记(如
YYYY、MM)识别出来 -
长度优先:按标记长度从长到短处理,避免
YYYY被YY误替换 -
顺序处理:依次替换每个标记为对应的日期值
-
类型转换:使用 JavaScript Date 对象的原生方法获取年、月、日等值
关键代码逻辑:
// 标记处理顺序(长的在前,短的在后)
var tokens = ['YYYY', 'yyyy', 'MMM', 'SSS', 'MM', 'DD', 'dd', 'HH', 'mm', 'ss', 'YY', 'M', 'D', 'd', 'H', 'm', 's', 'y'];
// 依次替换每个标记
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (result.indexOf(token) !== -1) {
result = result.replace(new RegExp(token, 'g'), matches[token]());
}
}
3.7 原型方法 vs 静态方法
| 特性 | 原型方法 | 静态方法 |
|------|----------|----------|
| 调用方式 | instance.format() | Timezone.format() |
| 是否需要实例化 | ✅ 需要 | ❌ 不需要 |
| 访问实例属性 | ✅ 可以 | ❌ 不可以 |
| 推荐使用场景 | 面向对象编程 | 工具函数调用 |
| 性能 | 略优 | 略低(多一层调用) |
关系说明:
-
静态方法内部调用原型方法实现
-
两种方式返回结果完全一致
-
静态方法保证向后兼容性
// 静态方法实现(调用原型方法)
Timezone.format = function (date, format) {
return Timezone.prototype.format.call(null, date, format);
}
4. parse 方法详解
4.1 方法说明
parse 方法用于将日期时间字符串解析为 JavaScript Date 对象,支持多种常见格式的自动识别。
方法签名:
// 原型方法
instance.parse(dateString, format)
// 静态方法(向后兼容)
Timezone.parse(dateString, format)
参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| dateString | string\|number | ✅ | - | 日期时间字符串或时间戳 |
| format | string | ❌ | - | 可选的格式模板,用于指定解析格式 |
返回值:
-
类型:
Date | null -
说明:解析成功返回 Date 对象,失败返回
null
4.2 支持的日期格式
4.2.1 自动识别格式(无需指定 format)
| 格式 | 示例 | 说明 |
|------|------|------|
| ISO 8601 UTC(带毫秒) ⭐ | 2025-10-28T06:30:00.000Z | UTC时间,按本地时区解析 |
| ISO 8601 UTC(无毫秒) ⭐ | 2025-10-28T06:30:00Z | UTC时间,按本地时区解析 |
| ISO 8601 本地(带毫秒) ⭐ | 2025-10-28T14:30:00.000 | 本地时间格式 |
| ISO 8601 本地(无毫秒) ⭐ | 2025-10-28T14:30:00 | 本地时间格式 |
| 标准格式(带时分秒毫秒) | 2025-10-28 14:30:45.123 | 常用格式 |
| 标准格式(带时分秒) | 2025-10-28 14:30:45 | 常用格式 |
| 标准格式(带时分) | 2025-10-28 14:30 | 常用格式 |
| 标准日期 | 2025-10-28 | 仅日期 |
| 斜杠格式(带时间) | 2025/10/28 14:30:45 | 常用格式 |
| 斜杠格式(日期) | 2025/10/28 | 常用格式 |
| 中文格式(带时间) | 2025年10月28日 14时30分45秒 | 中文日期时间 |
| 中文格式(日期) | 2025年10月28日 | 中文日期 |
| 欧洲格式 | 28/10/2025 | DD/MM/YYYY |
| 美式格式 | 10-28-2025 | MM-DD-YYYY |
| 时间戳 | 1698484245000 | 毫秒时间戳 |
4.2.2 指定格式解析
当自动识别失败时,可以指定 format 参数明确告知解析格式:
const tz = new Timezone();
// 指定格式解析
tz.parse('28-10-2025', 'DD-MM-YYYY');
tz.parse('10/28/2025', 'MM/DD/YYYY');
tz.parse('25/10/28 14:30', 'YY/MM/DD HH:mm');
支持的格式标记:
-
YYYY/yyyy- 四位年份 -
YY/yy- 两位年份(00-49 → 2000-2049,50-99 → 1950-1999) -
MM- 月份 -
DD/dd- 日期 -
HH- 小时 -
mm- 分钟 -
ss- 秒 -
SSS- 毫秒
4.3 使用示例
4.3.1 基础用法(自动识别)
const tz = new Timezone();
// ISO 8601 格式 ⭐ 新增
tz.parse('2025-10-28T06:30:00.000Z'); // Date 对象(UTC时间)
tz.parse('2025-10-28T06:30:00Z'); // Date 对象(UTC时间)
tz.parse('2025-10-28T14:30:00.000'); // Date 对象(本地时间)
tz.parse('2025-10-28T14:30:00'); // Date 对象(本地时间)
// 标准格式
tz.parse('2025-10-28 14:30:45'); // Date 对象
tz.parse('2025-10-28'); // Date 对象
// 斜杠格式
tz.parse('2025/10/28 14:30:45'); // Date 对象
tz.parse('2025/10/28'); // Date 对象
// 中文格式
tz.parse('2025年10月28日'); // Date 对象
tz.parse('2025年10月28日 14时30分45秒'); // Date 对象
// 时间戳
tz.parse(1698484245000); // Date 对象
// 带毫秒
tz.parse('2025-10-28 14:30:45.123'); // Date 对象
4.3.2 指定格式解析
const tz = new Timezone();
// 欧洲日期格式(DD/MM/YYYY)
tz.parse('28/10/2025', 'DD/MM/YYYY');
// 美式日期格式(MM/DD/YYYY)
tz.parse('10/28/2025', 'MM/DD/YYYY');
// 短年份格式
tz.parse('25/10/28', 'YY/MM/DD'); // 2025-10-28
// 自定义格式
tz.parse('28-10-2025 14:30', 'DD-MM-YYYY HH:mm');
4.3.3 错误处理
const tz = new Timezone();
// 无效输入返回 null
tz.parse(null); // null
tz.parse(undefined); // null
tz.parse(''); // null
tz.parse('invalid date'); // null
tz.parse('2025-13-40'); // null(无效日期)
// 类型检查
const result = tz.parse('2025-10-28');
if (result) {
console.log('解析成功:', result);
} else {
console.log('解析失败');
}
4.3.4 静态方法调用
// 直接使用静态方法,无需实例化
const date1 = Timezone.parse('2025-10-28');
const date2 = Timezone.parse('2025/10/28');
const date3 = Timezone.parse('2025年10月28日');
const date4 = Timezone.parse('28-10-2025', 'DD-MM-YYYY');
4.3.5 parse + format 组合使用
const tz = new Timezone();
// 解析后格式化输出
const parsedDate = tz.parse('2025-10-28 14:30:45');
if (parsedDate) {
console.log(tz.format(parsedDate, 'YYYY年MM月DD日')); // '2025年10月28日'
console.log(tz.format(parsedDate, 'MMM DD, yyyy')); // 'Oct 28, 2025'
console.log(tz.format(parsedDate, 'HH:mm:ss')); // '14:30:45'
}
// 格式转换
const input = '28/10/2025';
const date = tz.parse(input, 'DD/MM/YYYY');
const output = tz.format(date, 'YYYY-MM-DD');
console.log(output); // '2025-10-28'
4.4 解析流程
parse 方法采用多层次解析策略:
输入值
↓
┌─────────────────────────────────┐
│ 1. 类型检查 │
│ - null/undefined → null │
│ - Date 对象 → 验证后返回 │
│ - 数字 → 时间戳解析 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 2. 格式化参数检查 │
│ - 有 format → 使用格式模板解析 │
│ - 无 format → 自动识别 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 3. 原生解析尝试 │
│ - new Date(dateString) │
│ - 成功 → 返回 │
│ - 失败 → 继续 │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ 4. 正则匹配解析 │
│ - 遍历预定义格式列表 │
│ - 匹配成功 → 返回 │
│ - 全部失败 → 返回 null │
└─────────────────────────────────┘
4.5 常见场景示例
场景1:表单日期输入
const tz = new Timezone();
// 用户输入的日期字符串
const userInput = document.getElementById('dateInput').value; // '2025-10-28'
const date = tz.parse(userInput);
if (date) {
// 转换为显示格式
const displayText = tz.format(date, 'YYYY年MM月DD日');
console.log(displayText); // '2025年10月28日'
}
场景2:API 数据转换
const tz = new Timezone();
// API 返回的日期字符串
const apiData = {
createdAt: '2025-10-28T14:30:45.123Z',
updatedAt: '2025/10/28 14:30:45'
};
// 解析并格式化
const createdDate = tz.parse(apiData.createdAt);
const updatedDate = tz.parse(apiData.updatedAt);
console.log(tz.format(createdDate, 'YYYY-MM-DD HH:mm:ss'));
console.log(tz.format(updatedDate, 'YYYY-MM-DD HH:mm:ss'));
场景3:日期格式统一化
const tz = new Timezone();
// 不同格式的日期数组
const dates = [
'2025-10-28',
'2025/10/28',
'2025年10月28日',
'28/10/2025' // 需要指定格式
];
// 统一转换为标准格式
const normalized = dates.map((dateStr, index) => {
const format = index === 3 ? 'DD/MM/YYYY' : undefined;
const date = tz.parse(dateStr, format);
return date ? tz.format(date, 'YYYY-MM-DD') : null;
});
console.log(normalized); // ['2025-10-28', '2025-10-28', '2025-10-28', '2025-10-28']
4.6 性能考虑
最佳实践:
-
优先使用标准格式:ISO 8601 格式解析最快
-
指定格式模板:已知格式时指定 format 参数可跳过自动识别
-
缓存解析结果:避免重复解析相同字符串
-
提前验证:在解析前进行基本格式验证
const tz = new Timezone();
// ❌ 不推荐:每次都自动识别
for (let i = 0; i < 1000; i++) {
tz.parse('28/10/2025');
}
// ✅ 推荐:指定格式
for (let i = 0; i < 1000; i++) {
tz.parse('28/10/2025', 'DD/MM/YYYY');
}
4.7 与 format 方法的配合
parse 和 format 是互补的两个方法:
| 方法 | 输入 | 输出 | 用途 |
|------|------|------|------|
| parse | 字符串 → Date | 将日期字符串转换为 Date 对象 | 数据输入、解析 |
| format | Date → 字符串 | 将 Date 对象转换为格式化字符串 | 数据显示、输出 |
完整的数据流:
const tz = new Timezone();
// 数据输入 → 处理 → 输出
const input = '28/10/2025'; // 用户输入
const date = tz.parse(input, 'DD/MM/YYYY'); // 解析为 Date 对象
const output = tz.format(date, 'YYYY年MM月DD日'); // 格式化为显示文本
console.log(output); // '2025年10月28日'
4.8 原型方法 vs 静态方法
| 特性 | 原型方法 | 静态方法 |
|------|----------|----------|
| 调用方式 | instance.parse() | Timezone.parse() |
| 是否需要实例化 | ✅ 需要 | ❌ 不需要 |
| 访问实例属性 | ✅ 可以 | ❌ 不可以 |
| 推荐使用场景 | 面向对象编程 | 工具函数调用 |
关系说明:
// 静态方法实现(调用原型方法)
Timezone.parse = function (dateString, format) {
// 使用原型对象作为上下文,以便访问内部方法
return Timezone.prototype.parse.call(Timezone.prototype, dateString, format);
}
技术说明:
静态方法 parse 内部调用原型方法时,需要使用 Timezone.prototype 作为上下文(this),而不是 null。这是因为原型方法中可能会调用其他内部方法(如 _parseWithFormat),如果 this 为 null 会导致错误。
5. 时区功能详解
5.1 功能概述
Timezone 插件内置了全球时区数据库,包含 400+ 个时区信息,支持:
-
自动检测:获取当前浏览器时区
-
时区查询:根据时区名称查询国家代码和时间偏移量
-
实例缓存:初始化时自动缓存当前时区信息
-
别名支持:支持时区别名查询(如
PRC→Asia/Shanghai)
5.2 时区数据结构
每个时区包含以下信息:
{
name: "Asia/Shanghai", // IANA 时区标识符(主名称)
alternativeName: "China Time", // 可读的时区名称
group: ["Asia/Shanghai", "PRC", ...], // 时区别名组
continentCode: "AS", // 洲代码
continentName: "Asia", // 洲名称
countryName: "China", // 国家名称
countryCode: "CN", // 国家代码(ISO 3166-1)
mainCities: ["Shanghai", "Beijing", ...],// 主要城市
rawOffsetInMinutes: 480, // UTC 偏移量(分钟)
abbreviation: "CST", // 时区缩写
rawFormat: "+08:00 China Time - Shanghai, Beijing, ..." // 完整格式
}
5.3 静态方法:getCurrentTimezone()
功能说明:
获取当前浏览器的时区设置。
方法签名:
Timezone.getCurrentTimezone() → string
返回值:
-
返回 IANA 时区标识符(如
'Asia/Shanghai') -
如果
Intl API不可用,返回'UTC'
使用示例:
// 静态方法调用(无需实例化)
const timezone = Timezone.getCurrentTimezone();
console.log(timezone); // 'Asia/Shanghai'(根据浏览器设置)
技术实现:
Timezone.getCurrentTimezone = function () {
try {
// 使用 Intl API 获取时区
return Intl.DateTimeFormat().resolvedOptions().timeZone;
} catch (e) {
// 如果 Intl API 不可用,返回默认值
console.warn('无法获取时区信息,使用默认值');
return 'UTC';
}
}
浏览器兼容性:
-
✅ Chrome 24+
-
✅ Firefox 29+
-
✅ Safari 10+
-
✅ Edge 12+
-
❌ IE 11 以下(返回
'UTC')
5.4 实例方法:getCountryCode()
功能说明:
根据时区名称获取对应的国家代码(ISO 3166-1)。
方法签名:
instance.getCountryCode(timezone?) → string | null
参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| timezone | string | ❌ | 时区名称(可选,默认使用当前时区)|
返回值:
-
成功:返回国家代码(如
'CN'、'US') -
失败:返回
null
使用示例:
const tz = new Timezone();
// 1. 不传参数,获取当前时区的国家代码
console.log(tz.getCountryCode()); // 'CN'(如果当前时区是 Asia/Shanghai)
// 2. 传入时区名称
console.log(tz.getCountryCode('Asia/Shanghai')); // 'CN'
console.log(tz.getCountryCode('America/New_York')); // 'US'
console.log(tz.getCountryCode('Europe/London')); // 'GB'
console.log(tz.getCountryCode('Asia/Tokyo')); // 'JP'
// 3. 使用时区别名
console.log(tz.getCountryCode('PRC')); // 'CN'
console.log(tz.getCountryCode('Hongkong')); // 'HK'
console.log(tz.getCountryCode('US/Eastern')); // 'US'
// 4. 时区不存在
console.log(tz.getCountryCode('Invalid/Timezone')); // null
应用场景:
-
地区检测:根据用户时区自动判断所在国家
-
内容本地化:根据国家代码提供本地化内容
-
数据统计:按国家统计用户分布
-
合规检查:根据国家代码应用不同的法规要求
示例:根据时区显示国旗
const tz = new Timezone();
const countryCode = tz.getCountryCode();
const flagEmoji = {
'CN': '🇨🇳',
'US': '🇺🇸',
'GB': '🇬🇧',
'JP': '🇯🇵'
};
console.log('您的国家:' + flagEmoji[countryCode]); // 您的国家:🇨🇳
5.5 实例方法:getTimezoneOffset()
功能说明:
根据时区名称获取相对于 UTC 的时间偏移量(单位:分钟)。✅ 支持夏令时自动判断。
方法签名:
instance.getTimezoneOffset(timezone?, date?) → number | null
参数:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| timezone | string | ❌ | 时区名称(可选,默认使用当前时区)|
| date | Date\|string\|number | ❌ | 可选的日期参数,用于计算该日期的偏移量(考虑夏令时)|
返回值:
-
成功:返回偏移量(分钟,正数表示东时区,负数表示西时区)
-
失败:返回
null
使用示例:
const tz = new Timezone();
// 1. 不传参数,获取当前时区的偏移量
console.log(tz.getTimezoneOffset()); // 480(如果当前时区是 Asia/Shanghai,UTC+8)
// 2. 传入时区名称(自动使用当前日期)
console.log(tz.getTimezoneOffset('Asia/Shanghai')); // 480 (UTC+8)
console.log(tz.getTimezoneOffset('America/New_York')); // -300 或 -240(取决于当前是否夏令时)
console.log(tz.getTimezoneOffset('Europe/London')); // 0 或 60(取决于当前是否夏令时)
console.log(tz.getTimezoneOffset('Asia/Tokyo')); // 540 (UTC+9)
// 3. 指定日期,考虑夏令时
// 纽约冬季(1月):UTC-5
console.log(tz.getTimezoneOffset('America/New_York', new Date('2025-01-15'))); // -300
// 纽约夏季(7月):UTC-4(夏令时)
console.log(tz.getTimezoneOffset('America/New_York', new Date('2025-07-15'))); // -240
// 4. 转换为小时
const offsetMinutes = tz.getTimezoneOffset('Asia/Shanghai');
const offsetHours = offsetMinutes / 60;
console.log('UTC+' + offsetHours); // 'UTC+8'
// 5. 使用时区别名
console.log(tz.getTimezoneOffset('PRC')); // 480
console.log(tz.getTimezoneOffset('US/Pacific')); // -480 或 -420(取决于夏令时)
// 6. 时区不存在
console.log(tz.getTimezoneOffset('Invalid/Timezone')); // null
偏移量说明:
| 偏移量(分钟) | 偏移量(小时) | 示例时区 |
|---------------|---------------|----------|
| 480 | +8 | Asia/Shanghai (中国) |
| 540 | +9 | Asia/Tokyo (日本) |
| 0 | ±0 | Europe/London (英国) |
| -300 | -5 | America/New_York (美国东部) |
| -480 | -8 | America/Los_Angeles (美国西部) |
应用场景:
-
时间转换:计算不同时区的时间差
-
会议安排:确定跨时区会议的合适时间
-
显示本地时间:将 UTC 时间转换为用户本地时间
-
时区提示:显示时区偏移信息
示例:计算两个时区的时间差
const tz = new Timezone();
const shanghaiOffset = tz.getTimezoneOffset('Asia/Shanghai'); // 480
const newYorkOffset = tz.getTimezoneOffset('America/New_York'); // -300
const diffMinutes = shanghaiOffset - newYorkOffset; // 780
const diffHours = diffMinutes / 60; // 13
console.log('上海比纽约早 ' + diffHours + ' 小时'); // 上海比纽约早 13 小时
示例:格式化显示 UTC 偏移
function formatUTCOffset(offsetMinutes) {
const sign = offsetMinutes >= 0 ? '+' : '-';
const absMinutes = Math.abs(offsetMinutes);
const hours = Math.floor(absMinutes / 60);
const minutes = absMinutes % 60;
if (minutes === 0) {
return 'UTC' + sign + hours;
} else {
return 'UTC' + sign + hours + ':' + (minutes < 10 ? '0' : '') + minutes;
}
}
const tz = new Timezone();
console.log(formatUTCOffset(tz.getTimezoneOffset('Asia/Shanghai'))); // 'UTC+8'
console.log(formatUTCOffset(tz.getTimezoneOffset('America/New_York'))); // 'UTC-5'
console.log(formatUTCOffset(tz.getTimezoneOffset('Asia/Kolkata'))); // 'UTC+5:30'
5.6 实例属性:时区信息缓存
功能说明:
在实例初始化时,自动缓存当前时区相关信息,无需重复查询。
缓存属性:
| 属性 | 类型 | 说明 |
|------|------|------|
| currentTimezone | string | 当前时区名称(IANA 标识符)|
| currentCountryCode | string \| null | 当前时区对应的国家代码 |
| currentTimezoneOffset | number \| null | 当前时区偏移量(分钟)|
使用示例:
const tz = new Timezone();
// 直接访问缓存的时区信息
console.log('当前时区:', tz.currentTimezone); // 'Asia/Shanghai'
console.log('国家代码:', tz.currentCountryCode); // 'CN'
console.log('偏移量(分钟):', tz.currentTimezoneOffset); // 480
console.log('偏移量(小时):', tz.currentTimezoneOffset / 60); // 8
初始化逻辑:
_init: function () {
// 获取当前浏览器时区
this.currentTimezone = Timezone.getCurrentTimezone();
// 根据时区获取时区数据
var tzData = timezoneDataMap[this.currentTimezone];
if (tzData) {
// 缓存当前时区对应的国家代码
this.currentCountryCode = tzData.countryCode;
// 缓存当前时区对应的时区偏移量(分钟)
this.currentTimezoneOffset = tzData.rawOffsetInMinutes;
} else {
// 如果找不到时区数据,使用默认值
this.currentCountryCode = null;
this.currentTimezoneOffset = null;
}
}
性能优势:
-
✅ 一次初始化,多次使用
-
✅ 避免重复查询时区数据
-
✅ 提升访问速度
5.7 时区别名支持
功能说明:
除了标准的 IANA 时区标识符,插件还支持常用的时区别名。
常用别名示例:
| 标准名称 | 别名 | 国家 |
|---------|------|------|
| Asia/Shanghai | PRC, Asia/Chongqing, Asia/Harbin | 中国 |
| Asia/Hong_Kong | Hongkong | 香港 |
| America/New_York | US/Eastern, EST5EDT | 美国东部 |
| America/Los_Angeles | US/Pacific, PST8PDT | 美国西部 |
| America/Chicago | US/Central, CST6CDT | 美国中部 |
| Europe/London | GB, GB-Eire | 英国 |
| Asia/Tokyo | Japan | 日本 |
使用示例:
const tz = new Timezone();
// 使用标准名称和别名效果相同
console.log(tz.getCountryCode('Asia/Shanghai')); // 'CN'
console.log(tz.getCountryCode('PRC')); // 'CN'(别名)
console.log(tz.getTimezoneOffset('America/New_York')); // -300
console.log(tz.getTimezoneOffset('US/Eastern')); // -300(别名)
技术实现:
时区数据初始化时,会将 group 中的所有别名都映射到同一个时区数据对象:
(function initTimezoneDataMap() {
for (var i = 0; i < TIMEZONE_CONFIG.length; i++) {
var timezone = TIMEZONE_CONFIG[i];
// 主时区名称作为 key
timezoneDataMap[timezone.name] = timezone;
// 同时将 group 中的所有别名也映射到同一个时区数据
if (timezone.group && timezone.group.length > 0) {
for (var j = 0; j < timezone.group.length; j++) {
timezoneDataMap[timezone.group[j]] = timezone;
}
}
}
})();
5.8 完整应用示例
示例1:显示用户所在时区信息
const tz = new Timezone();
// 获取并显示时区信息
const timezone = tz.currentTimezone;
const country = tz.currentCountryCode;
const offset = tz.currentTimezoneOffset;
const offsetHours = offset >= 0 ? '+' + (offset / 60) : (offset / 60);
console.log('您的时区信息:');
console.log('时区:' + timezone);
console.log('国家:' + country);
console.log('UTC 偏移:UTC' + offsetHours);
// 输出示例:
// 您的时区信息:
// 时区:Asia/Shanghai
// 国家:CN
// UTC 偏移:UTC+8
示例2:批量查询多个时区
const tz = new Timezone();
const timezones = [
'Asia/Shanghai',
'America/New_York',
'Europe/London',
'Asia/Tokyo',
'Australia/Sydney'
];
console.log('=== 全球主要时区信息 ===');
timezones.forEach(function(timezone) {
const countryCode = tz.getCountryCode(timezone);
const offset = tz.getTimezoneOffset(timezone);
const offsetHours = (offset / 60).toFixed(1);
console.log(timezone + ':');
console.log(' 国家代码: ' + countryCode);
console.log(' UTC偏移: ' + (offset >= 0 ? '+' : '') + offsetHours + ' 小时');
});
示例3:根据时区显示当前时间
const tz = new Timezone();
function getTimeInTimezone(timezoneName) {
const offset = tz.getTimezoneOffset(timezoneName);
if (offset === null) {
return '时区不存在';
}
const now = new Date();
const utcTime = now.getTime() + (now.getTimezoneOffset() * 60000);
const timezoneTime = new Date(utcTime + (offset * 60000));
return tz.format(timezoneTime, 'YYYY-MM-DD HH:mm:ss');
}
console.log('上海时间:' + getTimeInTimezone('Asia/Shanghai'));
console.log('纽约时间:' + getTimeInTimezone('America/New_York'));
console.log('伦敦时间:' + getTimeInTimezone('Europe/London'));
示例4:时区选择器
const tz = new Timezone();
// 常用时区列表
const popularTimezones = [
{ name: 'Asia/Shanghai', label: '中国 - 北京' },
{ name: 'Asia/Hong_Kong', label: '中国 - 香港' },
{ name: 'America/New_York', label: '美国 - 纽约' },
{ name: 'America/Los_Angeles', label: '美国 - 洛杉矶' },
{ name: 'Europe/London', label: '英国 - 伦敦' },
{ name: 'Asia/Tokyo', label: '日本 - 东京' }
];
// 生成选择器选项
popularTimezones.forEach(function(tz) {
const offset = tz.getTimezoneOffset(tz.name);
const offsetHours = (offset / 60).toFixed(1);
const utcString = 'UTC' + (offset >= 0 ? '+' : '') + offsetHours;
console.log('<option value="' + tz.name + '">' +
tz.label + ' (' + utcString + ')' +
'</option>');
});
5.9 实例方法:时区转换
5.9.1 toUTC() - 将特定时区时间转为 UTC
功能说明:
将指定时区的本地时间转换为 UTC 时间。特别注意:特定时区可能不是本地浏览器时区。
方法签名:
instance.toUTC(date, timezone) → Date | null
参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| date | Date\|string\|number | ✅ | - | 日期对象、日期字符串或时间戳 |
| timezone | string | ❌ | 当前浏览器时区 ⭐ | 源时区名称(该时间所在的时区)|
返回值:
-
成功:返回 UTC 时间的
Date对象 -
失败:返回
null
默认时区行为 ⭐ 新增:
当不传入 timezone 参数时,自动使用当前浏览器时区,简化常见用例:
const tz = new Timezone();
const localTime = new Date(2025, 9, 28, 14, 30, 0);
// 不传时区参数,自动使用浏览器时区
const utc = tz.toUTC(localTime);
// 等价于显式指定浏览器时区
const utc2 = tz.toUTC(localTime, Timezone.getCurrentTimezone());
转换公式:
UTC 时间 = 本地时间 - 时区偏移量
使用示例:
const tz = new Timezone();
// 示例1:将上海时间转为 UTC
const shanghaiTime = new Date(2025, 9, 28, 14, 30, 0); // 假设这是上海本地时间
const utcTime = tz.toUTC(shanghaiTime, 'Asia/Shanghai');
console.log(tz.format(utcTime, 'YYYY-MM-DD HH:mm:ss')); // '2025-10-28 06:30:00'
// 说明:上海是 UTC+8,所以 14:30 - 8小时 = 06:30 UTC
// 示例2:将纽约时间转为 UTC
const nyTime = new Date(2025, 9, 28, 14, 30, 0); // 假设这是纽约本地时间
const utcTime2 = tz.toUTC(nyTime, 'America/New_York');
console.log(tz.format(utcTime2, 'YYYY-MM-DD HH:mm:ss')); // '2025-10-28 19:30:00'
// 说明:纽约是 UTC-5,所以 14:30 - (-5小时) = 19:30 UTC
// 示例3:使用字符串输入
const utcTime3 = tz.toUTC('2025-10-28 14:30:00', 'Asia/Tokyo');
console.log(tz.format(utcTime3, 'YYYY-MM-DD HH:mm:ss')); // '2025-10-28 05:30:00'
// 示例4:使用时间戳
const utcTime4 = tz.toUTC(1698484245000, 'Europe/London');
重要说明:
本方法的 date 参数表示指定时区的本地时间,而不是浏览器的本地时间。例如:
const tz = new Timezone();
// 假设浏览器时区是 Asia/Shanghai (UTC+8)
// 但我们要转换的是 America/New_York (UTC-5) 的时间
const nyLocalTime = new Date(2025, 9, 28, 14, 30, 0); // 这表示纽约的 14:30
const utc = tz.toUTC(nyLocalTime, 'America/New_York'); // 转为 UTC
// 结果是 19:30 UTC,而不是基于上海时区计算
5.9.2 fromUTC() - 将 UTC 时间转为特定时区
功能说明:
将 UTC 时间转换为指定时区的本地时间。特别注意:特定时区可能不是本地浏览器时区。
方法签名:
instance.fromUTC(date, timezone) → Date | null
参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| date | Date\|string\|number | ✅ | - | UTC 时间的 Date 对象、日期字符串或时间戳 |
| timezone | string | ❌ | 当前浏览器时区 ⭐ | 目标时区名称(要转换到的时区)|
返回值:
-
成功:返回目标时区的本地时间
Date对象 -
失败:返回
null
默认时区行为 ⭐ 新增:
当不传入 timezone 参数时,自动使用当前浏览器时区,简化常见用例:
const tz = new Timezone();
const utcTime = new Date(Date.UTC(2025, 9, 28, 6, 30, 0));
// 不传时区参数,自动转换为浏览器本地时间
const local = tz.fromUTC(utcTime);
// 等价于显式指定浏览器时区
const local2 = tz.fromUTC(utcTime, Timezone.getCurrentTimezone());
转换公式:
本地时间 = UTC 时间 + 时区偏移量
使用示例:
const tz = new Timezone();
// 示例1:将 UTC 时间转为上海时间
const utcTime = new Date(Date.UTC(2025, 9, 28, 6, 30, 0)); // UTC 时间
const shanghaiTime = tz.fromUTC(utcTime, 'Asia/Shanghai');
console.log(tz.format(shanghaiTime, 'YYYY-MM-DD HH:mm:ss')); // '2025-10-28 14:30:00'
// 说明:上海是 UTC+8,所以 06:30 UTC + 8小时 = 14:30
// 示例2:将 UTC 时间转为纽约时间
const nyTime = tz.fromUTC(utcTime, 'America/New_York');
console.log(tz.format(nyTime, 'YYYY-MM-DD HH:mm:ss')); // '2025-10-28 01:30:00'
// 说明:纽约是 UTC-5,所以 06:30 UTC + (-5小时) = 01:30
// 示例3:使用字符串输入
const tokyoTime = tz.fromUTC('2025-10-28 06:30:00', 'Asia/Tokyo');
console.log(tz.format(tokyoTime, 'YYYY-MM-DD HH:mm:ss')); // '2025-10-28 15:30:00'
// 示例4:使用时间戳
const londonTime = tz.fromUTC(1698484245000, 'Europe/London');
5.9.3 跨时区转换应用
场景1:将一个时区的时间转换到另一个时区
const tz = new Timezone();
// 需求:将上海时间 14:30 转换为纽约时间
const shanghaiTime = new Date(2025, 9, 28, 14, 30, 0);
console.log('上海时间:', tz.format(shanghaiTime, 'YYYY-MM-DD HH:mm:ss'));
// 步骤1: 上海时间 -> UTC
const utcTime = tz.toUTC(shanghaiTime, 'Asia/Shanghai');
console.log('UTC 时间:', tz.format(utcTime, 'YYYY-MM-DD HH:mm:ss'));
// 步骤2: UTC -> 纽约时间
const nyTime = tz.fromUTC(utcTime, 'America/New_York');
console.log('纽约时间:', tz.format(nyTime, 'YYYY-MM-DD HH:mm:ss'));
// 输出:
// 上海时间: 2025-10-28 14:30:00
// UTC 时间: 2025-10-28 06:30:00
// 纽约时间: 2025-10-28 01:30:00
场景2:显示全球多个时区的同一时刻
const tz = new Timezone();
// 一个固定的 UTC 时间
const utcTime = new Date(Date.UTC(2025, 9, 28, 12, 0, 0));
console.log('UTC 时间:', tz.format(utcTime, 'YYYY-MM-DD HH:mm:ss'));
// 转换为多个时区
const timezones = [
'Asia/Shanghai',
'Asia/Tokyo',
'Europe/London',
'America/New_York',
'America/Los_Angeles'
];
timezones.forEach(function(timezone) {
const localTime = tz.fromUTC(utcTime, timezone);
const offset = tz.getTimezoneOffset(timezone);
const offsetHours = (offset / 60).toFixed(1);
console.log(timezone + ':',
tz.format(localTime, 'YYYY-MM-DD HH:mm:ss'),
'(UTC' + (offset >= 0 ? '+' : '') + offsetHours + ')');
});
// 输出:
// Asia/Shanghai: 2025-10-28 20:00:00 (UTC+8.0)
// Asia/Tokyo: 2025-10-28 21:00:00 (UTC+9.0)
// Europe/London: 2025-10-28 12:00:00 (UTC+0.0)
// America/New_York: 2025-10-28 07:00:00 (UTC-5.0)
// America/Los_Angeles: 2025-10-28 04:00:00 (UTC-8.0)
场景3:验证往返转换
const tz = new Timezone();
// 原始时间
const original = new Date(2025, 9, 28, 14, 30, 0);
console.log('原始时间:', tz.format(original, 'YYYY-MM-DD HH:mm:ss'));
// 往返转换:本地 -> UTC -> 本地
const toUtc = tz.toUTC(original, 'Asia/Shanghai');
const backToLocal = tz.fromUTC(toUtc, 'Asia/Shanghai');
console.log('往返后:', tz.format(backToLocal, 'YYYY-MM-DD HH:mm:ss'));
// 验证
console.log('时间相等:', original.getTime() === backToLocal.getTime());
// 输出: true
场景4:计算不同时区的会议时间
const tz = new Timezone();
// 纽约团队的会议时间:上午 10:00
const nyMeetingTime = new Date(2025, 9, 28, 10, 0, 0);
console.log('纽约会议时间:', tz.format(nyMeetingTime, 'YYYY-MM-DD HH:mm:ss'));
// 转换为 UTC
const utc = tz.toUTC(nyMeetingTime, 'America/New_York');
// 上海团队需要在什么时间参加?
const shanghaiTime = tz.fromUTC(utc, 'Asia/Shanghai');
console.log('上海对应时间:', tz.format(shanghaiTime, 'YYYY-MM-DD HH:mm:ss'));
// 伦敦团队需要在什么时间参加?
const londonTime = tz.fromUTC(utc, 'Europe/London');
console.log('伦敦对应时间:', tz.format(londonTime, 'YYYY-MM-DD HH:mm:ss'));
// 输出:
// 纽约会议时间: 2025-10-28 10:00:00
// 上海对应时间: 2025-10-28 23:00:00 (晚上11点)
// 伦敦对应时间: 2025-10-28 15:00:00 (下午3点)
5.9.4 错误处理
两个方法都包含完善的错误处理:
const tz = new Timezone();
const testDate = new Date(2025, 9, 28, 14, 30, 0);
// 错误1:时区不存在
const result1 = tz.toUTC(testDate, 'Invalid/Timezone');
console.log(result1); // null
// 控制台错误:toUTC: 时区 "Invalid/Timezone" 不存在
// 错误2:缺少时区参数
const result2 = tz.toUTC(testDate);
console.log(result2); // null
// 控制台错误:toUTC: 必须指定时区名称
// 错误3:无效的日期
const result3 = tz.fromUTC('invalid date', 'Asia/Shanghai');
console.log(result3); // null
// 控制台错误:fromUTC: 无效的日期
// 错误4:无效的日期参数类型
const result4 = tz.toUTC({}, 'Asia/Shanghai');
console.log(result4); // null
// 控制台错误:toUTC: 无效的日期参数
5.9.5 注意事项
1. Date 对象的时区陷阱
JavaScript 的 Date 对象内部使用 UTC 时间戳,但在显示时会自动应用浏览器的本地时区。使用这些方法时需要理解:
const tz = new Timezone();
// 创建一个 Date 对象
const date = new Date(2025, 9, 28, 14, 30, 0);
// date 内部存储的是时间戳(UTC),但这个构造函数会被解释为浏览器本地时区的时间
// 如果浏览器在上海(UTC+8),date 实际表示 UTC 时间 06:30
// 如果浏览器在纽约(UTC-5),date 实际表示 UTC 时间 19:30
// 使用 toUTC 时,我们指定这个时间属于哪个时区
const utc = tz.toUTC(date, 'Asia/Shanghai'); // 明确告诉函数:date 是上海时间
2. 使用 Date.UTC() 创建真正的 UTC 时间
// 推荐:创建真正的 UTC 时间
const utcTime = new Date(Date.UTC(2025, 9, 28, 6, 30, 0));
// 不推荐:这会受到浏览器时区影响
const localTime = new Date(2025, 9, 28, 6, 30, 0);
3. 夏令时(DST)支持
✅ 已支持夏令时自动判断
插件已经实现了夏令时的自动处理。getTimezoneOffset、toUTC 和 fromUTC 方法会根据具体日期自动判断并使用正确的偏移量。
夏令时工作原理:
const tz = new Timezone();
// 纽约冬季(1月)偏移量:UTC-5
const winterOffset = tz.getTimezoneOffset('America/New_York', new Date('2025-01-15'));
console.log(winterOffset); // -300 分钟 (UTC-5)
// 纽约夏季(7月)偏移量:UTC-4(夏令时)
const summerOffset = tz.getTimezoneOffset('America/New_York', new Date('2025-07-15'));
console.log(summerOffset); // -240 分钟 (UTC-4)
常见时区的夏令时规则:
| 时区 | 冬季偏移 | 夏季偏移 | 是否实行夏令时 |
|------|---------|---------|--------------|
| America/New_York | UTC-5 | UTC-4 | ✅ 是 |
| America/Los_Angeles | UTC-8 | UTC-7 | ✅ 是 |
| Europe/London | UTC+0 | UTC+1 | ✅ 是 |
| Europe/Paris | UTC+1 | UTC+2 | ✅ 是 |
| Asia/Shanghai | UTC+8 | UTC+8 | ❌ 否 |
| Asia/Tokyo | UTC+9 | UTC+9 | ❌ 否 |
技术实现:
插件使用 JavaScript 的 Intl.DateTimeFormat API 自动获取指定日期在指定时区的精确偏移量:
// 通过比较 UTC 时间和本地时间来计算偏移量
var localStr = date.toLocaleString('en-US', { timeZone: timezone });
var localDate = new Date(localStr);
var offset = Math.round((localDate.getTime() - date.getTime()) / 60000);
如果 Intl API 不可用(旧浏览器),会回退到使用静态配置中的 rawOffsetInMinutes。
夏令时转换示例:
const tz = new Timezone();
// 示例1:纽约冬季时间转 UTC
const nyWinter = new Date('2025-01-15T14:30:00');
const utcWinter = tz.toUTC(nyWinter, 'America/New_York');
console.log(tz.format(utcWinter, 'YYYY-MM-DD HH:mm:ss'));
// 输出: 2025-01-15 19:30:00 (14:30 + 5小时)
// 示例2:纽约夏季时间转 UTC
const nySummer = new Date('2025-07-15T14:30:00');
const utcSummer = tz.toUTC(nySummer, 'America/New_York');
console.log(tz.format(utcSummer, 'YYYY-MM-DD HH:mm:ss'));
// 输出: 2025-07-15 18:30:00 (14:30 + 4小时,夏令时)
5.10 方法总结
| 类型 | 方法 | 参数 | 返回值 | 说明 |
|------|------|------|--------|------|
| 静态方法 | getCurrentTimezone() | - | string | 获取当前浏览器时区 |
| 实例方法 | getCountryCode(timezone?) | string? | string\|null | 根据时区获取国家代码 |
| 实例方法 | getTimezoneOffset(timezone?) | string? | number\|null | 根据时区获取偏移量(分钟)|
| 实例方法 | toUTC(date, timezone) | Date\|string\|number, string | Date\|null | 将特定时区时间转为 UTC |
| 实例方法 | fromUTC(date, timezone) | Date\|string\|number, string | Date\|null | 将 UTC 时间转为特定时区 |
| 实例属性 | currentTimezone | - | string | 当前时区名称(缓存)|
| 实例属性 | currentCountryCode | - | string\|null | 当前国家代码(缓存)|
| 实例属性 | currentTimezoneOffset | - | number\|null | 当前偏移量(缓存)|
5.11 时区数据范围
插件内置时区数据涵盖:
-
✅ 400+ 时区:覆盖全球所有国家和地区
-
✅ 洲分布:
- 亚洲(AS):120+ 时区
- 欧洲(EU):80+ 时区
- 北美洲(NA):90+ 时区
- 南美洲(SA):30+ 时区
- 大洋洲(OC):50+ 时区
- 非洲(AF):50+ 时区
- 南极洲(AN):10+ 时区
-
✅ IANA 标准:使用 IANA 时区数据库标识符
-
✅ 别名支持:支持常用时区别名
数据来源:
基于 IANA 时区数据库(tzdata),定期更新以确保准确性。