fix(forward): 转发引用里的「时间」按本地时区渲染(原先是 UTC 原样)
用户(转发 zcode 的信给我):「它说的它发的这个邮件你收到了吗?它说没有收到回复……」—— 问题不在那封信,而在它引用的时间:用户看到的「2026-09-15 01:23:19」其实是 UTC, 本地时间应为 09:23:19(本机 UTC+8)。 根因:quoteBody 用 `m.CreatedAt.Format(...)` 直接格式化(库里存 UTC),而界面各处都转本地; 同一族的正确写法在 scheduler/calendar.go 里(`e.EventTime.Local().Format(...)`)。 ★ 顺带发现一条**旧断言一直在保护这个 bug**:TestQuoteBodyPrefixesEveryLine 里写死了 「2026-09-02 10:30:00」(fixture 是 time.UTC)——已改为按本地计算,不再写死字面量 (与前几天 nav-merge.test.mjs 那条同一形态:断言钉着错的意图)。 判据:新增 TestQuoteBodyTimeIsLocal(引用时间必须等于 CreateAt.Local() 渲染,且不得等于 UTC 原样)。变异:去掉 .Local() → 2 条判据变红(新加的 + 改过的旧断言);复原 → 包内全绿。 注:该判据在 TZ=UTC 的机器上是空转的(本地==UTC),本机 UTC+8 会真红。 ▲ 部署未完成:/opt/agentmail 现在对我不可写(touch 都被拒;uid/能力问题,08:57 之后出现), 加上一个 08:57 留下的陈旧 /opt/agentmail/.deploy.lock 挡在 redeploy-gateway.sh 前面。 需要能写那里的人跑:rm -f /opt/agentmail/.deploy.lock && bash deploy/redeploy-gateway.sh
This commit is contained in:
@ -45,7 +45,10 @@ func quoteBody(m *models.Mail) string {
|
||||
}
|
||||
b.WriteString("\n")
|
||||
b.WriteString(fmt.Sprintf("> **主题** %s\n", m.Subject))
|
||||
b.WriteString(fmt.Sprintf("> **时间** %s\n", m.CreatedAt.Format("2006-01-02 15:04:05")))
|
||||
// ★ 2026-09-15 用户报:转发引用里显示「2026-09-15 01:23:19」,而实际是 09:23:19 ——
|
||||
// 原文时间按 **UTC** 原样格式化了。库里存的是 UTC,界面各处都转本地,只有这一处漏了
|
||||
// (同一族的写法在 scheduler/calendar.go 里是对的:e.EventTime.Local().Format(...))。
|
||||
b.WriteString(fmt.Sprintf("> **时间** %s\n", m.CreatedAt.Local().Format("2006-01-02 15:04:05")))
|
||||
if len(m.CCList) > 0 {
|
||||
names := make([]string, 0, len(m.CCList))
|
||||
for _, c := range m.CCList {
|
||||
|
||||
@ -51,7 +51,9 @@ func TestQuoteBodyPrefixesEveryLine(t *testing.T) {
|
||||
}
|
||||
|
||||
// 元信息必须齐全,否则收件人不知道这封转发的来路
|
||||
for _, want := range []string{"opencode@/root", "巡检结果", "2026-09-02 10:30:00", "pi@root.new"} {
|
||||
// ★ 2026-09-15:这里原先写死 "2026-09-02 10:30:00"(UTC 原样),而 fixture 是 time.UTC ——
|
||||
// 等于这条断言一直在**保护那个 bug**(引用块显示 UTC 时间)。时间一项按本地算,不写死字面量。
|
||||
for _, want := range []string{"opencode@/root", "巡检结果", m.CreatedAt.Local().Format("2006-01-02 15:04:05"), "pi@root.new"} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("引用块缺少 %q\n%s", want, out)
|
||||
}
|
||||
@ -75,3 +77,28 @@ func TestQuoteBodyOmitsEmptyCC(t *testing.T) {
|
||||
t.Error("无抄送时不应出现「抄送」行")
|
||||
}
|
||||
}
|
||||
|
||||
// TestQuoteBodyTimeIsLocal —— 转发引用里的「时间」必须是**本地**时间,不是 UTC 原样。
|
||||
//
|
||||
// 2026-09-15 用户报:引用块显示 01:23:19,而实际发生时间是 09:23:19(本机 UTC+8)。
|
||||
// 库里存 UTC、界面各处都转本地,只有 quoteBody 漏了 .Local()。
|
||||
//
|
||||
// ★ 这条判据在 **TZ=UTC 的机器上是空转的**(本地 == UTC,两种写法同结果)——
|
||||
// 本机是 UTC+8,所以这里真会红。CI 若在 UTC 下跑,它是"只能验形态"的那一类,
|
||||
// 形态判据另有一条:不得与 UTC 原样相等(当两者不同时)。
|
||||
func TestQuoteBodyTimeIsLocal(t *testing.T) {
|
||||
// 取一个"本地与 UTC 不同"的瞬间来测(否则本判据无从区分)
|
||||
m := &models.Mail{
|
||||
FromName: "zcode",
|
||||
Subject: "时间判定",
|
||||
CreatedAt: time.Date(2026, 9, 15, 1, 23, 19, 0, time.UTC),
|
||||
}
|
||||
got := quoteBody(m)
|
||||
want := m.CreatedAt.Local().Format("2006-01-02 15:04:05")
|
||||
if !strings.Contains(got, "> **时间** "+want) {
|
||||
t.Fatalf("引用里的时间不是本地时间:想要 %q,实际渲染:\n%s", want, got)
|
||||
}
|
||||
if u := m.CreatedAt.UTC().Format("2006-01-02 15:04:05"); u != want && strings.Contains(got, "> **时间** "+u) {
|
||||
t.Fatalf("引用里出现的是 UTC 原样 %q(应为本地 %q)", u, want)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user