# Folder Service 时间戳规范修正 > **日期**:2026-01-29 > **类型**:规范修正(重大) > **影响范围**:`folder-service.md` 文档 --- ## 修正背景 在 v3.5 版本中,错误地将所有事件时间字段从 `TIMESTAMPTZ` 改为 `TIMESTAMP WITHOUT TIME ZONE`,这违反了 PostgreSQL 最佳实践和项目架构决策(ADR 006)。 本次修正(v3.6)将时间戳规范改回正确的 `TIMESTAMPTZ`。 --- ## 修正内容 ### 1. 时间戳类型修正(符合 ADR 006) **v3.5(错误)**: ```sql -- ❌ 错误:使用 TIMESTAMP WITHOUT TIME ZONE 记录事件时间 created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP WITHOUT TIME ZONE, ``` **v3.6(正确)**: ```sql -- ✅ 正确:使用 TIMESTAMPTZ 记录事件时间 created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), deleted_at TIMESTAMPTZ, ``` **涉及表**: - `folders` 表:`created_at`, `updated_at`, `deleted_at` - `folder_members` 表:`joined_at`, `created_at` - `folder_shares` 表:`expires_at`, `created_at`, `revoked_at` - `folder_export_jobs` 表:`created_at`, `started_at`, `completed_at`, `expires_at` ### 2. 默认值修正 **v3.5(错误)**: ```sql DEFAULT CURRENT_TIMESTAMP ``` **v3.6(正确)**: ```sql DEFAULT now() ``` **原因**: - `now()` 是 PostgreSQL 推荐的函数 - 与 `TIMESTAMPTZ` 类型配合使用 - 更简洁明确 ### 3. 字段注释修正 **v3.5(错误)**: ```sql COMMENT ON COLUMN folders.created_at IS '创建时间(UTC)'; ``` **v3.6(正确)**: ```sql COMMENT ON COLUMN folders.created_at IS '创建时间(自动记录时区)'; ``` **原因**: - `TIMESTAMPTZ` 自动记录时区信息 - 不应标注"UTC",因为数据库会自动处理时区转换 - 更准确地描述字段行为 --- ## 修正理由 根据 [ADR 006: 事件时间戳必须使用 TIMESTAMPTZ](../../architecture/adrs/006-timestamptz-for-event-timestamps.md): ### 1. 语义正确性 事件时间(`created_at`, `updated_at`, `deleted_at` 等)表示**真实世界发生的时间点**,必须包含时区信息。 ``` 错误理解:应用层使用 UTC,所以数据库不需要时区 正确理解:事件时间是绝对时间点,必须用 TIMESTAMPTZ 表示 ``` ### 2. PostgreSQL 官方推荐 PostgreSQL 官方文档明确推荐: - 使用 `TIMESTAMPTZ` 记录事件时间 - 数据库自动处理时区转换 - 避免应用层时区处理错误 ### 3. 性能相同 `TIMESTAMPTZ` 与 `TIMESTAMP WITHOUT TIME ZONE`: - 存储大小完全相同(8 字节) - 查询性能完全相同 - 索引性能完全相同 ### 4. 多时区支持 使用 `TIMESTAMPTZ` 的优势: - 支持全球化部署 - 支持多时区用户 - 数据库自动处理时区转换 - 避免应用层时区错误 ### 5. 数据一致性 ```sql -- TIMESTAMPTZ 自动处理时区 SET timezone = 'Asia/Shanghai'; SELECT created_at FROM folders WHERE id = '...'; -- 返回:2026-01-29 18:00:00+08 SET timezone = 'America/New_York'; SELECT created_at FROM folders WHERE id = '...'; -- 返回:2026-01-29 05:00:00-05 -- 同一时间点,不同时区显示 ``` --- ## 文档版本更新 ### v3.6 (2026-01-29) - 当前版本 - ✅ **修正时间戳规范(符合 ADR 006)**: - **数据库层**:所有事件时间字段使用 `TIMESTAMPTZ` - **默认值**:统一使用 `now()` 而非 `CURRENT_TIMESTAMP` - **字段注释**:改为"自动记录时区"而非"UTC" - **理由**:事件时间表示真实世界时间点,必须包含时区信息 ### v3.5 (2026-01-29) - 已废弃 - ❌ **错误的时间戳规范**(已被 v3.6 修正): - 错误地使用 `TIMESTAMP WITHOUT TIME ZONE` 记录事件时间 - 违反了 PostgreSQL 最佳实践和 ADR 006 决策 --- ## 技术栈规范更新 同时更新了 `jointo-tech-stack` skill 的时区规范: ### database.md 修正 ```sql -- ❌ v3.5 错误示例 created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, -- ✅ v3.6 正确示例 created_at TIMESTAMPTZ NOT NULL DEFAULT now(), ``` ### backend.md 修正 **核心原则**: - 数据库使用 `TIMESTAMPTZ` 记录事件时间 - Python 应用层使用 `datetime.now(timezone.utc)` 生成 UTC 时间 - PostgreSQL 自动处理时区转换 --- ## 影响评估 ### 文档层面 - ✅ 文档符合 PostgreSQL 最佳实践 - ✅ 文档符合 ADR 006 架构决策 - ✅ 示例代码正确可用 ### 实现层面 - ⚠️ 如果已按 v3.5 实现,需要修正 - ⚠️ 检查所有时间戳列类型 - ⚠️ 检查默认值定义 ### 数据库层面 - ⚠️ 如果已创建表,需要检查列类型 - ⚠️ 必要时创建迁移脚本修正 --- ## 检查清单 - [x] 所有事件时间字段使用 `TIMESTAMPTZ` - [x] 默认值使用 `now()` 而非 `CURRENT_TIMESTAMP` - [x] 字段注释使用"自动记录时区" - [x] 更新文档版本号(v3.4 → v3.6) - [x] 更新变更记录 - [x] 创建 Changelog 记录修正过程 - [x] 更新 jointo-tech-stack skill 规范 --- ## 相关文档 - [ADR 006: 事件时间戳必须使用 TIMESTAMPTZ](../../architecture/adrs/006-timestamptz-for-event-timestamps.md) - [时区和时间戳规范](../../architecture/datetime-timezone-standards.md) - [时间戳规范修正 Changelog](./2026-01-29-timestamptz-standard-correction.md) - [Jointo Tech Stack Skill 更新](./2026-01-29-jointo-tech-stack-skill-timestamptz-update.md) --- ## 总结 本次修正纠正了 v3.5 版本的错误决策,将时间戳规范改回正确的 `TIMESTAMPTZ`。这符合: 1. PostgreSQL 官方最佳实践 2. ADR 006 架构决策 3. 语义正确性(事件时间是绝对时间点) 4. 多时区支持需求 **重要提醒**:所有记录"真实世界发生时间点"的字段,必须使用 `TIMESTAMPTZ`。