# 日期时间处理规范

> **最后更新**: 2026-04-06

---

## 核心原则

1. **数据库存储**：时间戳统一存储为 **UTC**（使用 `TIMESTAMPTZ(3)`）
2. **数据传输**：API 统一使用 **ISO 8601** 格式（`2025-01-23T06:30:00.000Z`）
3. **用户展示**：前端自动转换为用户**本地时区**
4. **纯日期**：工作日期、生日等纯日期字段**不做时区转换**

---

## 字段类型选择

| 场景 | PostgreSQL | Prisma | 时区 |
|------|-----------|--------|------|
| 创建时间、提交时间等时间点 | `TIMESTAMPTZ(3)` | `DateTime @db.Timestamptz(3)` | UTC 存储，前端本地化 |
| 工作日期、生日等纯日期 | `DATE` | `DateTime @db.Date` | 无时区，全球统一 |
| 上班时间等纯时间 | `TIME` | `DateTime @db.Time` | 无时区 |

> 不要用 `TIMESTAMP`（不带时区），始终用 `TIMESTAMPTZ`。

---

## 前端格式化规则

| 字段类型 | 使用函数 | 行为 |
|---------|---------|------|
| DATE（纯日期） | `formatDate()` | 直接提取日期部分，不做时区转换 |
| TIMESTAMPTZ（时间戳） | `formatDateTime()` | 转换为用户本地时区显示 |

两个函数不可混用。纯日期用 `formatDate`，时间戳用 `formatDateTime`。

---

## 做/不做

### 正确

- 时间戳字段用 `TIMESTAMPTZ(3)`
- 前端传输用 ISO 8601 格式
- 前端显示用 `toLocaleString()` 本地化
- 纯日期字段用 `DATE` 类型
- 服务器时区设为 UTC

### 错误

- 用 `TIMESTAMP`（不带时区）
- 手动计算时区偏移（`getTime() + 8 * 3600 * 1000`）
- 后端格式化日期字符串返回给前端
- 对纯日期字段做时区转换
- 在代码中硬编码时区

---

## 多时区显示示例

数据库存储 `2025-01-23T06:30:00.000Z`（UTC）：

| 用户位置 | 显示 |
|---------|------|
| 北京 (UTC+8) | 2025/1/23 14:30 |
| 纽约 (UTC-5) | 1/23/2025, 1:30 AM |
| 伦敦 (UTC+0) | 1/23/2025, 6:30 AM |

所有用户看到各自本地时间，数据库存同一个 UTC 时间点。
