# Time Zone Safety

当代码出现 LocalDate.now() / LocalDateTime.now() / Instant.now() / new Date() / Date.now() /...

- Repository: https://github.com/doccker/cc-use-exp (1063 stars)
- Category: Engineering
- License: see repository
- Last updated: 2026-08-26
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add doccker/cc-use-exp --skill time-zone-safety -g -a claude-code -y`
Codex: `npx skills add doccker/cc-use-exp --skill time-zone-safety -g -a codex -y`
Cursor: `npx skills add doccker/cc-use-exp --skill time-zone-safety -g -a cursor -y`
Gemini CLI: `npx skills add doccker/cc-use-exp --skill time-zone-safety -g -a gemini-cli -y`
Claude Code plugin marketplace: `/plugin marketplace add doccker/cc-use-exp`

## Description

当代码出现 LocalDate.now() / LocalDateTime.now() / Instant.now() / new Date() / Date.now() / time.Now() 等"当前时间"调用，或涉及周/月/日起点、跨时区比较、数据库时间字段时触发。防止生产环境时区漂移导致的统计错乱。

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# 时区安全规范

服务端代码里，"当前时间"几乎从不应该裸调。一个 docker 镜像默认 UTC、一个生产机配置 Asia/Shanghai，同一份代码跑出来的"本周起点"就差 8 小时。

> 配套：`query-performance-safety`（性能） / `multi-tenant-safety`（隔离）

---

## 陷阱 #1: 裸调 `.now()` 依赖 JVM 默认时区

**场景**: `LocalDate.now()` / `LocalDateTime.now()` / `Instant.now()` / `new Date()` 不传时区

### 问题根因

- `LocalDate.now()` 在 UTC 服务器上返回的"今天"比业务时区少 1 天（凌晨场景）
- `LocalDateTime.now()` 拿到的是 JVM 默认时区时间，docker base image 经常默认 UTC
- 同一段代码在开发机（Asia/Shanghai）和生产（UTC）跑出不同结果，无法本地复现
- 数据库时间通常按业务时区入库，比较时一边是业务时区一边是 UTC → 偏差 8h

### 错误示例

```java
// ❌ 错误：依赖 JVM 默认时区
LocalDateTime weekStart = LocalDate.now()
        .with(DayOfWeek.MONDAY)
        .atStartOfDay();

// ❌ 错误：跨日切换的统计依赖默认时区
LocalDate today = LocalDate.now();
List<Order> todayOrders = orderRepository.findByCreatedAtAfter(today.atStartOfDay());

// ❌ 错误：Date 用默认时区
Date now = new Date();
```

### 正确做法

**项目级业务时区常量** + 所有 `.now()` 显式传入：

```java
// ✅ 项目常量类（如 TimeConstants.java）
public final class TimeConstants {
    /** 系统业务时区（中国大陆） */
    public static final ZoneId BIZ_ZONE = ZoneId.of("Asia/Shanghai");
    private TimeConstants() {}
}

// ✅ 业务代码统一传入
LocalDateTime weekStart = LocalDate.now(BIZ_ZONE)
        .with(DayOfWeek.MONDAY)
        .atStartOfDay();

LocalDate today = LocalDate.now(BIZ_ZONE);
LocalDateTime nowBiz = LocalDateTime.now(BIZ_ZONE);
Instant instantNow = Instant.now();  // Instant 本身是 UTC 时间戳，无歧义
```

### 嗅探信号

```bash
# 任何裸 .now() 在业务代码中都该 review
grep -rnE "LocalDate\.now\(\)|LocalDateTime\.now\(\)|LocalTime\.now\(\)" src/main/java/ \
    | grep -v "TimeConstants\|@Configuration"

# Date 系列在新代码中应淘汰
grep -rnE "new Date\(\)|new GregorianCalendar\(\)" src/main/java/

# Go
grep -rnE "time\.Now\(\)" --include="*.go" .

# JS / TS
grep -rnE "new Date\(\)|Date\.now\(\)" --include="*.ts" --include="*.js" src/
```

### 检查清单

- [ ] 项目内是否有统一的 `BIZ_ZONE` 常量
- [ ] Service / Handler 层禁止裸 `.now()`，必须显式时区
- [ ] `Instant.now()` 可以裸用（UTC 时间戳无歧义）
- [ ] 测试用 `Clock.fixed(instant, zone)` 注入，不要硬编码 `now()`

---

The rest: https://github.com/doccker/cc-use-exp/blob/HEAD/.antigravity/skills/time-zone-safety/SKILL.md
