# 问题处理：定时任务时区问题

## 问题描述

用户配置定时任务"每天 18:15 执行"，但实际触发时间是 UTC 18:15（北京时间凌晨 02:15），导致任务在预期时间之外触发。

## 问题现象

| 任务 | 配置时间 | 预期触发时间 | 实际触发时间 |
|------|---------|-------------|-------------|
| 5.44服务器全量巡检 | 每天 18:15 | 北京时间 18:15 | UTC 18:15 = 北京时间 02:15（次日凌晨）|

## 根因分析

### 1. 服务器时区

```
Time zone: Etc/UTC (UTC, +0000)
```

5.60 服务器系统时区为 UTC，无时区偏移。

### 2. CronTrigger 时区处理

`schedule_service.py` 中 `_add_job()` 函数：

```python
trigger = CronTrigger.from_crontab(sched["cron"])
```

`CronTrigger.from_crontab()` **默认使用本地系统时区**解析 cron 表达式。当服务器时区为 UTC 时：
- cron `15 18 * * *` 被解析为 UTC 18:15
- 北京时间（UTC+8）对应的实际触发时间是 02:15

### 3. next_run_at 计算

`_calc_next_run()` 使用 `datetime.now()` 作为基准计算下次执行时间，返回的是本地时间字符串（UTC），但页面展示时没有时区提示，用户误以为是北京时间。

## 影响范围

- **所有定时任务**都受影响
- 用户配置的"工作时间执行"（如工作日 09:30）实际会在北京时间 17:30 才触发
- 页面显示的"下次执行"时间无时区标识，容易误导

## 解决方案

### 方案 A：修改配置时间（临时方案）

用户手动计算 UTC 时间：北京时间 18:15 = UTC 10:15，配置为"每天 10:15"。

**缺点**：每个任务都要手动换算，不直观，易出错。

### 方案 B：固定使用 Asia/Shanghai 时区（根本方案）

修改 `schedule_service.py`，让 `CronTrigger` 和 `croniter` 统一使用 `Asia/Shanghai` 时区：

1. `CronTrigger.from_crontab()` 添加 `timezone` 参数
2. `_calc_next_run()` 中 `croniter` 使用 `Asia/Shanghai` 时区
3. `next_run_at` 显示时保持北京时间格式

**优点**：用户配置什么时间就什么时间执行，无需换算。

---

## 修复计划

### 改动文件

| 文件 | 改动内容 |
|------|---------|
| `skill/code/web/service_monitor/services/schedule_service.py` | `_add_job()` 和 `_calc_next_run()` 使用 `Asia/Shanghai` 时区 |

### 具体改动

#### 1. `_add_job()` 函数（L412-446）

```python
# 改前
trigger = CronTrigger.from_crontab(sched["cron"])

# 改后
from apscheduler.triggers.cron import CronTrigger
from pytz import timezone as pytz_timezone
SHANGHAI_TZ = pytz_timezone("Asia/Shanghai")
trigger = CronTrigger.from_crontab(sched["cron"], timezone=SHANGHAI_TZ)
```

#### 2. `_calc_next_run()` 函数（L144-154）

```python
# 改前
base = datetime.now()
iter = croniter(cron_expr, base)

# 改后
from pytz import timezone as pytz_timezone
SHANGHAI_TZ = pytz_timezone("Asia/Shanghai")
base = datetime.now(SHANGHAI_TZ)
iter = croniter(cron_expr, base)
```

#### 3. `start_date`/`end_date` 时区（L428-434）

```python
# 改前
trigger.start_date = datetime.strptime(start_date, "%Y-%m-%d").replace(tzinfo=timezone.utc)

# 改后
trigger.start_date = SHANGHAI_TZ.localize(
    datetime.strptime(start_date, "%Y-%m-%d")
)
```

### 依赖检查

- `pytz` 是否在 requirements.txt 中？—— 需检查并补充
- APScheduler 3.x 支持 `timezone` 参数 ✅

### 验证步骤

1. 本地启动服务，创建定时任务"每天 18:15"
2. 检查 `next_run_at` 是否为 18:15（北京时间）
3. 部署到 5.60，验证 scheduler 状态
4. 等待触发时间，确认实际执行时间与配置一致

---

## 风险评估

| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| pytz 未安装 | 服务启动失败 | 检查依赖，部署脚本补充 |
| 已有任务时区变化 | 已注册任务触发时间改变 | 重新注册后生效，用户需确认 |
| 夏令时问题 | 无 | 中国无夏令时 |

---

## 文档信息

- 创建时间：2026-07-21
- 创建人：Claude
- 关联任务：定时任务执行状态展示 + 立即执行功能
- 关联提交：d10c77e4, 0d84df9f