Back to Blog
2026-02-19ToolBox Team

时间戳与时区处理 - 跨越时空的数据同步

🔧 返回工具箱 | Back to Tools

浏览所有工具 | View All Tools
timetimezonedatetimeprogramming

时间戳与时区处理 - 跨越时空的数据同步

如果说数据库是应用的心脏,那么时间就是应用的神经。从日志记录到支付交易,从缓存过期到定时任务,处处离不开时间。但时间处理的复杂性常常被开发者低估,导致时区混乱、时间计算错误等问题。

1. Unix 时间戳基础

什么是 Unix 时间戳?

Unix 时间戳是从 1970 年 1 月 1 日 00:00:00 UTC 开始计算的秒数(或毫秒数)。

// 获取当前时间戳(秒)
const timestamp = Math.floor(Date.now() / 1000);
console.log(timestamp);  // 1740747600

// 获取当前时间戳(毫秒,JavaScript 默认)
const timestampMs = Date.now();
console.log(timestampMs);  // 1740747600000

// 将时间戳转换回日期对象
const date = new Date(timestamp * 1000);
console.log(date.toISOString());  // 2026-02-27T10:00:00.000Z

为什么使用时间戳?

  1. 跨语言兼容:所有编程语言都支持
  2. 时区无关:时间戳是绝对的,不受时区影响
  3. 计算高效:数值运算比日期对象快
  4. 数据库友好:通常用整数存储,查询高效

2. 时区的复杂性

时区概念

世界被分为 24 个时区,从 UTC-12 到 UTC+14。同一时刻,不同地区显示的"当地时间"不同。

2026-02-27 10:00:00 UTC
  ↓
2026-02-27 18:00:00 UTC+8 (北京时间)
2026-02-27 02:00:00 UTC-8 (太平洋时间)
2026-02-27 17:00:00 UTC+7 (曼谷时间)

夏令时 (DST) 的陷阱

美国、欧洲等地采用夏令时,每年春秋两次调整时钟。这会导致某些时刻不存在或出现重复!

// 夏令时导致的时间"失踪"
// 美国开始夏令时(春季):2026-03-08 02:00:00 → 03:00:00
// 凌晨 2 点到 3 点的时间完全不存在

const problematicTime = new Date('2026-03-08T02:30:00-05:00');
// 这个时刻在美国东部时间并不存在!

处理时区的正确方式

原则:数据库和 API 始终使用 UTC,只在显示给用户时转换为本地时区。

// ✅ 正确做法
// 1. 接收用户输入时,转换为 UTC
const userInput = '2026-02-27 10:00:00';  // 用户的本地时间
const userTimezone = 'Asia/Shanghai';

const utcTime = convertToUTC(userInput, userTimezone);
// 存储到数据库的是 UTC 时间

// 2. 从数据库读取 UTC 时间
const utcTime = new Date('2026-02-27T02:00:00Z');

// 3. 显示给用户时,转换为用户的本地时区
const userLocalTime = convertToTimezone(utcTime, userTimezone);

3. JavaScript 日期处理

原生 Date 对象的限制

JavaScript 的 Date 对象只包含一个数值(时间戳),不存储时区信息。

// 创建日期对象
const date = new Date('2026-02-27T10:00:00Z');  // Z 表示 UTC

console.log(date.getHours());  // 格式化为什么不同?

// 在北京(UTC+8)显示为 18:00
// 在纽约(UTC-5)显示为 05:00
// 但数据本身(时间戳)完全相同

使用日期库 - date-fns 或 Day.js

const { parseISO, format, zonedTimeToUtc, utcToZonedTime } = require('date-fns-tz');

// 解析用户输入时间(假设用户当地时间)
const userLocalTime = '2026-02-27 10:00:00';
const userTimezone = 'Asia/Shanghai';

// 转换为 UTC
const utcTime = zonedTimeToUtc(
  new Date(userLocalTime),
  userTimezone
);

// 存储到数据库
db.events.insert({ scheduledAt: utcTime });

// 从数据库读取并转换回用户时区
const storedUtcTime = new Date('2026-02-27T02:00:00Z');

const displayTime = utcToZonedTime(storedUtcTime, userTimezone);
console.log(format(displayTime, 'yyyy-MM-dd HH:mm:ss'));  // 2026-02-27 10:00:00

4. 时间戳与日期的转换

使用我们的 时间戳转换工具 快速完成转换。

手工计算

// Unix 时间戳(秒)→ 日期
const timestamp = 1740747600;
const date = new Date(timestamp * 1000);
console.log(date.toISOString());  // 2026-02-27T10:00:00.000Z

// 日期 → Unix 时间戳(秒)
const date = new Date('2026-02-27T10:00:00Z');
const timestamp = Math.floor(date.getTime() / 1000);
console.log(timestamp);  // 1740747600

// 计算时间差(天数)
const start = new Date('2026-02-27');
const end = new Date('2026-03-06');
const days = Math.floor((end - start) / (1000 * 60 * 60 * 24));
console.log(days);  // 7

5. 定时任务与时区

Cron 表达式示例

使用 Cron 生成器 快速生成 Cron 表达式。

// 每天上午 10 点(UTC)执行任务
const schedule = '0 10 * * *';

// 每个工作日下午 3 点(UTC)
const schedule = '0 15 * * 1-5';

// 每个月 1 号午夜(UTC)
const schedule = '0 0 1 * *';

在代码中使用:

const cron = require('node-cron');

cron.schedule('0 10 * * *', () => {
  console.log('每天 UTC 10:00 执行');
  // 执行任务
}, {
  timezone: 'UTC'
});

// 注意:如果需要按"用户时区"执行,需要手动计算偏移量
cron.schedule('0 2 * * *', () => {
  // 这在 UTC 时间 02:00 执行
  // 即北京时间的 10:00(UTC+8)
}, {
  timezone: 'Asia/Shanghai'
});

6. 数据库存储最佳实践

PostgreSQL

-- 使用 TIMESTAMP WITH TIME ZONE(推荐)
CREATE TABLE events (
  id SERIAL PRIMARY KEY,
  scheduled_at TIMESTAMP WITH TIME ZONE NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- 查询用户时区的本地时间
SELECT 
  id,
  scheduled_at AT TIME ZONE 'Asia/Shanghai' AS shanghai_time,
  scheduled_at AT TIME ZONE 'America/New_York' AS ny_time
FROM events;

MongoDB

// 存储 ISO 8601 格式的日期字符串或时间戳
db.events.insertOne({
  eventName: 'Meeting',
  scheduledAt: new Date('2026-02-27T10:00:00Z'),  // 自动转换为 BSON Date
  createdAt: new Date()
});

// 查询给定时间范围内的事件
db.events.find({
  scheduledAt: {
    $gte: new Date('2026-02-27T00:00:00Z'),
    $lt: new Date('2026-02-28T00:00:00Z')
  }
});

在应用中处理

// Node.js 模型层
class Event {
  static async create(data) {
    // 接收用户输入
    const { eventName, userLocalTime, userTimezone } = data;
    
    // 转换为 UTC
    const utcTime = zonedTimeToUtc(
      new Date(userLocalTime),
      userTimezone
    );
    
    // 存储到数据库(存的是 UTC)
    return db.events.insertOne({
      eventName,
      scheduledAt: utcTime
    });
  }

  static async getUserView(eventId, userTimezone) {
    // 从数据库读取 UTC 时间
    const event = await db.events.findOne({ _id: eventId });
    
    // 转换为用户的本地时区
    const userLocalTime = utcToZonedTime(
      event.scheduledAt,
      userTimezone
    );
    
    return {
      ...event,
      displayTime: format(userLocalTime, 'yyyy-MM-dd HH:mm:ss')
    };
  }
}

7. 常见时间问题与解决

问题 1:跨午夜的时间段

// 需要获取"昨天"的数据
const now = new Date();
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);

// 错误的方式(不考虑时区)
yesterday.setHours(0, 0, 0, 0);

// 正确的方式(转换到 UTC)
const startOfYesterday = startOfDay(
  subDays(now, 1)
);

问题 2:计算用户年龄

function calculateAge(birthDate, { timezone } = {}) {
  let today = new Date();
  
  if (timezone) {
    // 根据时区调整"今天"的日期
    today = utcToZonedTime(today, timezone);
  }
  
  let age = today.getFullYear() - birthDate.getFullYear();
  const monthDiff = today.getMonth() - birthDate.getMonth();
  
  if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
    age--;
  }
  
  return age;
}

问题 3:事件通知的时区处理

async function scheduleNotification(userId, eventId, notifyAt) {
  const user = await db.users.findOne(userId);
  
  // 用户指定的"通知时间"是本地时间
  const utcNotifyTime = zonedTimeToUtc(
    notifyAt,
    user.timezone
  );
  
  // 计算距离现在的延迟(毫秒)
  const delay = utcNotifyTime - new Date();
  
  if (delay > 0) {
    setTimeout(() => {
      sendNotification(userId, eventId);
    }, delay);
  }
}

时间处理清单

项目启动时检查:

  • 数据库始终存储 UTC 时间
  • API 接受并返回 ISO 8601 格式或 Unix 时间戳
  • 前端使用用户的本地时区显示时间
  • 定时任务明确指定时区(通常 UTC)
  • 考虑夏令时的影响
  • 使用现代日期库(date-fns、Day.js),而非原生 Date
  • 在国际化应用中测试多个时区
  • 日志和审计记录使用 UTC 时间

相关工具推荐