Skip to content

fix(drivers/189): support updated time format from 189Cloud - #2918

Closed
AmPlace wants to merge 2 commits into
OpenListTeam:mainfrom
AmPlace:fix/189pc-time-format
Closed

fix(drivers/189): support updated time format from 189Cloud#2918
AmPlace wants to merge 2 commits into
OpenListTeam:mainfrom
AmPlace:fix/189pc-time-format

Conversation

@AmPlace

@AmPlace AmPlace commented Aug 11, 2026

Copy link
Copy Markdown

Summary / 摘要

Add compatibility for the updated time strings returned by the 189Cloud PC and TV clients.

  • Normalize Unicode narrow no-break space (U+202F) and no-break space (U+00A0) before parsing.
  • Preserve the existing 189PC and 189TV time layouts and add Jan 2, 2006, 3:04:05 PM -07 for the current response format.
  • Add unit coverage for both drivers, including legacy formats, the updated 12-hour format, both Unicode spaces, AM/PM conversion, and invalid input.

The 189Cloud API may now return values such as Aug 12, 2026, 3:32:44 AM. The existing parsers did not accept the comma after the year or the Unicode space before AM/PM. The parsing error can propagate to callers and may cause an otherwise successful operation to be reported as failed.

Real-world verification was performed against 189Cloud PC after opening this PR: uploads complete successfully and the previous time parsing error no longer occurs. The same parser compatibility update is also applied to 189_tv, which uses the equivalent time parsing logic.

  • This PR has breaking changes.
    / 此 PR 包含破坏性变更。
  • This PR changes public API, config, storage format, or migration behavior.
    / 此 PR 修改了公开 API、配置、存储格式或迁移行为。
  • This PR requires corresponding changes in related repositories.
    / 此 PR 需要关联仓库同步修改。

Related repository PRs / 关联仓库 PR:

  • OpenList-Frontend: N/A
  • OpenList-Docs: N/A

Related Issues / 关联 Issue

Fixes #2917

Testing / 测试

  • go test -vet=off ./drivers/189pc
  • go test -vet=off ./drivers/189_tv
  • go test -vet=off ./drivers/189pc ./drivers/189_tv ./internal/model ./internal/op ./internal/fs ./pkg/utils
  • go test ./... — FAILED due to pre-existing vet, environment, and service-availability failures described below.
  • Manual test / 手动测试: Real-world 189Cloud PC upload verification was performed after this PR was opened; the upload completed successfully without the previous time parsing error.

The full command was run, but it did not pass for reasons unrelated to this change:

  • Pre-existing non-constant format-string vet errors across multiple packages, including drivers/189pc/utils.go:358 and drivers/189pc/utils.go:418.
  • internal/net environment-sensitive failure: TestNewOSSClientUsesEnvironmentHTTPSProxy expected *http.Transport but received *net.safeTransport.
  • pkg/aria2/rpc tests could not connect to the required local service at localhost:6800.

Checklist / 检查清单

  • I have read CONTRIBUTING.
    / 我已阅读 CONTRIBUTING
  • I confirm this contribution follows the repository license, contribution policy, and code of conduct.
    / 我确认此贡献符合仓库许可证、贡献规范和行为准则。
  • I have formatted the changed code with gofmt, go fmt, or prettier where applicable.
    / 我已按适用情况使用 gofmtgo fmtprettier 格式化变更代码。
  • I have requested review from relevant maintainers or code owners where applicable.
    / 我已在适用情况下请求相关维护者或代码所有者审查。

AI Disclosure / AI 使用声明

  • This PR includes AI-assisted content.
    / 此 PR 包含 AI 辅助内容。

Tools used / 使用工具:

  • ChatGPT
  • Codex
  • GitHub Copilot
  • Claude
  • Gemini
  • Other (please specify) / 其他(请注明):

Usage scope / 使用范围:

  • Code generation / 代码生成

  • Refactoring / 重构

  • Documentation / 文档

  • Tests / 测试

  • Translation / 翻译

  • Review assistance / 审查辅助

  • I have reviewed and validated all AI-assisted content included in this PR.
    / 我已审核并验证此 PR 中的所有 AI 辅助内容。

  • I have ensured that all AI-assisted commits include Co-Authored-By attribution.
    / 我已确保所有 AI 辅助提交都包含 Co-Authored-By 归属信息。

  • I can reproduce all AI-assisted content included in this PR without any AI tools.
    / 我可以在没有任何 AI 工具的情况下重现此 PR 中包含的所有 AI 辅助内容。

Co-Authored-By: Codex <noreply@openai.com>
@AmPlace
AmPlace marked this pull request as ready for review August 11, 2026 20:09
- Normalize Unicode spaces and preserve existing time layouts.
- Add the updated 12-hour layout and focused parser regression tests.

Co-Authored-By: Codex <267193182+codex@users.noreply.github.com>
@AmPlace AmPlace changed the title fix(189pc): support updated time format from 189Cloud fix(drivers/189): support updated time format from 189Cloud Aug 12, 2026
@PIKACHUIM

Copy link
Copy Markdown
Member

Hi @AmPlace,

Thank you for taking the time to work on this important bug fix! I appreciate your effort in addressing the 189Cloud time format issue.

I noticed that PR #2919 by @shelken tackles the same problem with a slightly different approach that handles an additional edge case. Specifically:

Key Difference: Timezone Handling

Your current implementation always appends +08 to the time string:

v, err = time.ParseInLocation(f, bs+" +08", time.Local)

However, according to issue #2917, the 189Cloud API may now return time strings that already include a timezone, such as:

"Aug 11, 2026, 7:20:03 PM +08"

In this case, appending another +08 would result in "Aug 11, 2026, 7:20:03 PM +08 +08", which would fail to parse.

PR #2919 addresses this by using a two-level loop that tries both the original string and the string with +08 appended:

for _, s := range []string{bs, bs + " +08"} {
    for _, f := range []string{...} {
        v, err = time.ParseInLocation(f, s, time.Local)
        if err == nil { break }
    }
}

This ensures compatibility with both:

  • New format with timezone: "Aug 11, 2026, 10:37:18 PM +08"
  • Old format without timezone: "Aug 11, 2026 10:37:18 PM"

Additional Note: Time Format Template

The template "Jan 2, 2006 15:04:05 PM -07" uses 15 (24-hour format) with PM, which is technically incorrect according to Go's time parsing rules. The correct approach is to use 3 (12-hour format) with AM/PM:

"Jan 2, 2006 3:04:05 PM -07"

Suggestion

You might want to take a look at PR #2919's implementation. If you'd like to update your PR with the timezone handling logic, I'd be happy to see it! Otherwise, both PRs solve the core Unicode space issue excellently.

Thanks again for your contribution! 🙌

@PIKACHUIM

Copy link
Copy Markdown
Member

Close by #2919

@PIKACHUIM PIKACHUIM closed this Aug 14, 2026
@AmPlace

AmPlace commented Aug 14, 2026

Copy link
Copy Markdown
Author

@PIKACHUIM

我还是想把 #2918 这个事问清楚。

先说明白,我不是觉得谁先提 PR 就必须合谁,维护者有权选最终实现。

但我重新看完 #2918#2919 两边的完整时间线以后,我认为这次处理本身就需要一个明确解释。因为两个高度重叠、而且都能解决核心问题的 PR,实际走的根本不是同一条 review 路径。

#2918#2919 早提交 27 分 59 秒

我最开始的 189pc commit 已经包含了这次实际问题涉及的核心修改:

  • U+202F narrow no-break space 处理
  • U+00A0 no-break space 处理
  • 年份后带逗号的 12 小时制格式
  • 对应 parser regression tests

这里也需要说明:#2918 后来补充 189_tv 的第二个 commit,提交时间晚于 #2919 已经公开覆盖 189pc 和 189_tv 的版本。我看到 #2919 同时处理了 189_tv 后,也把我原本已经完成的 189pc parser 修复和 tests 扩展到了 189_tv。

所以我不主张 #2918 的 189_tv 覆盖早于 #2919。我的主张是:#2918 最早的 189pc commit 在 #2919 创建之前,就已经独立包含了这次核心 parser 修复和 regression tests。

我在 8 月 12 日 13:04 完成了补充 189_tv 的第二个 commit。这个 PC + TV 版本在你开始 review #2919、并首次 APPROVED #2919 之前,就已经公开存在了约 45 小时

所以到这轮 review 开始时,#2918 已经不是最初只修改 189pc 的版本,而是同时包含 189pc、189_tv 和两边 regression tests 的版本。

而且我用真实 189Cloud PC 上传验证过,#2917 里那个 parsing time 报错已经不再出现。

所以 #2918 不是一个没修好、没测试,或者不能解决实际问题的 PR。

这一点你自己在 #2918 下面也明确说了:

both PRs solve the core Unicode space issue excellently.

所以至少从你自己的评论来看,当时并不存在“#2918 核心修复不成立,只有 #2919 才能修”的情况。

但后面的 review 流程很奇怪。

10:08,#2919 先收到了一份 AI-generated review。它当时的 GitHub review 状态是 COMMENTED,不是正式 approve,但正文已经给出了“建议立即合并”的结论。

更关键的是,10:16,你又对 #2919 提交了一次正式的 APPROVED review,并在其中提出了三个具体问题。

而在你首次 APPROVED #2919 的时候,#2918 包含 189pc、189_tv 和两边 regression tests 的版本,已经公开存在了约 45 小时。

后来作者 force-push 了新的 commit,之前的 approval 因此变成 stale 并被 dismissed;作者回复这三个问题以后,#2919 又获得了一次新的 APPROVED,随后 merge。

所以从公开记录来看,#2919 实际走的是一条持续推进的:

AI review → 正式 APPROVED(同时提出三个问题) → 作者更新并回复 → 再次 APPROVED → merge

的路径。

然后 10:21,你才来到 #2918,说 #2919 多处理了一个 timezone edge case,同时跟我说:

If you’d like to update your PR with the timezone handling logic, I’d be happy to see it!

正常理解就是,我这个 PR 仍然可以继续改、继续 review,对吧?

但现在结合完整时间线来看,我收到这句话的时候,#2919 实际上已经获得过一次正式 approval。

如果当时 #2919 已经实际上进入优先合并路径,那么这句“可以继续修改”就很容易让人误以为 #2918 仍然有真实的竞争机会。

所以这里我更想确认:

你 10:21 告诉我可以继续修改 #2918 的时候,#2918 到底还是不是一个实际可能被选择的候选?

但实际发生的是:

也就是说,从 #2919 作者完成 review 回复,到我的 PR 被关闭只有 55 秒;到 #2919 最终 merge,只有 2 分 15 秒

所以我现在真正想问的已经不是“为什么只等我 87 分钟”这么简单。

我想问的是:为什么 #2919 在我收到“可以继续修改”的评论之前就已经获得过正式 approval,之后又继续得到明确问题、作者更新和回复、重新 approve、merge,而 #2918 得到的是一句“你可以继续补”,然后在 #2919 作者回复后 55 秒就直接被关闭?

从 10:21 到关闭 #2918,中间没有 deadline,也没有告诉我 #2919 马上就要 merge,更没有说 87 分钟没回复就算放弃。

无论你 10:21 那句 I’d be happy to see it 当时具体是什么意思,从最终执行结果来看,#2918 并没有真正经历:

修改 → review → 和 #2919 再比较

这个过程。

如果 #2918 当时仍然是一个实际可能被选择的候选,那为什么在 #2919 已经获得过正式 approval 的情况下,不给 #2918 一个正常的异步修改和复审时间,而是在 87 分钟以后直接关闭?

如果不是,那为什么当时不直接说明已经明确倾向选择 #2919

另外,几处技术细节我也一并说明。

先说 timezone。

你当时在 #2918 里说:

according to issue #2917, the 189Cloud API may now return time strings that already include a timezone

#2917 的公开错误其实不能证明 raw API 返回值本身已经带 +08

因为旧代码本来就是:

v, err = time.ParseInLocation(f, bs+" +08", time.Local)

也就是说,parser 收到的是 OpenList 自己构造出来的 bs + " +08",而不是原始 API 字段。

我后来用 Go 独立复现过。当 raw 原值为:

"Aug 11, 2026, 7:20:03\u202fPM"

也就是不带 timezone,且 AM/PM 前就是 U+202F 空格时,旧代码自己追加 +08 后,一样可以得到和 #2917 相同形状的错误。

反过来,如果 raw 本来已经带了 +08,旧代码实际传给 parser 的字符串会包含:

+08 +08

#2917 公布的错误输入中只有一个 +08

所以 #2917 能证明的是 parser 确实出问题了,但不能靠报错里那个单独的 +08 推出“API 原始返回已经自带 timezone”。

#2919 支持 raw timestamp 本身已经包含 timezone 的输入,这一点我没有异议。但这只能说明实现覆盖了这种输入形式;它是否是修复 #2917 所必需的,还需要 raw response 等独立证据支持。

#2919new_format_with_tz 的单元测试可以证明实现支持这种输入,但单元测试本身不能证明 189Cloud 实际返回过这种 raw timestamp。

#2918 这边已经有真实 189Cloud 上传验证,原来的报错确实已经消失。

更关键的是,就算你认为这个 timezone case 很重要,你 10:21 的评论也已经明确告诉我可以把同样的 timezone handling 补到 #2918。这说明这个差异本身就是一个可以通过 review 要求补上的修改项,而不是 #2918 无法继续推进的根本技术问题。

再说说你提到的时间格式问题。

就是:

"Jan 2, 2006 15:04:05 PM -07"

混用 24 小时字段与 AM/PM 那个点。

这个写法语义上确实不规范,但 Go parser 实际能够接受它。更重要的是,这个 layout 不是 #2918 新引入的,而是共同 base 中已有的 legacy 格式。

#2918 保留了原有接受范围,同时为这次新格式新增了正确的:

"Jan 2, 2006, 3:04:05 PM -07"

#2919 把旧 layout 也一起改成了 3,语义上更规范,但这属于另一项兼容性取舍,也完全可以作为 review item 讨论。

另外补充一句:最终 #2919 还把 time.Local 改成了 utils.CNLoc。这个修改让时区意图表达得更明确,也对未来新增无时区 layout 更稳,但 #2919 作者自己在 review 回复中也明确称当前行为“零变化”,因此它不是这次 #2917 实际故障能否修复的决定性差异。

所以如果最后选择 #2919 的主要理由是“它多处理了 raw 自带时区、修正了旧格式模板”,那我更想问:

这两处都不是 #2918 无法解决的根本性技术问题,其中 timezone handling 你当时甚至已经明确邀请我补充。既然如此,为什么实际流程不是给 #2918 一个有效的修改和复审机会,再比较两个 PR,而是在 #2919 作者回复后 55 秒直接关闭 #2918

我没有否认 #2919 当时的版本在细节处理上更完整。

但这最多可以解释:为什么你认为当时的 #2919 更完整。

它不能自动解释:为什么 #2918 没有获得把这些细节补齐以后再比较的机会。

这是两个完全不同的问题。

最后我把问题明确成三点,希望你逐条回应:

  1. 两个高度重叠的 PR,项目实际的选择标准是什么?

    为什么在 fix(drivers/189): support updated time format from 189Cloud #2918 的 PC + TV 版本已经公开存在约 45 小时的情况下,fix(drivers/189): support updated 189 time format #2919 仍然先获得了完整的 review 和正式 APPROVED;而 fix(drivers/189): support updated time format from 189Cloud #2918 直到之后才收到一句“可以继续修改”,并且没有得到对应的修改和复审机会?

    如果 10:21 时 fix(drivers/189): support updated 189 time format #2919 已经是明确倾向的方案,为什么当时没有直接说明?

  2. Raw 自带 timezone 和 legacy layout 修正,是不是最终选择 fix(drivers/189): support updated 189 time format #2919 的主要技术原因?

    如果是,为什么这些可以通过 review 补充的差异,没有作为 fix(drivers/189): support updated time format from 189Cloud #2918 的修改项继续推进?

    如果不是,还有什么具体的决定性技术原因?

  3. 如果重新审视后无法合理解释上述 review 路径差异,或者当时选择 fix(drivers/189): support updated 189 time format #2919 所依据的主要技术判断不能由现有证据支持,项目准备怎么处理?

    我不认为“fix(drivers/189): support updated 189 time format #2919 已经 merged”本身应该成为拒绝重新审视这次选择的理由。具体是否需要 reopen fix(drivers/189): support updated time format from 189Cloud #2918、在补全对应细节后替换现有实现,或是其他代码层面的调整,可以在重新 review 以后根据结论决定;前提是不会重新引入 [BUG] crypt驱动挂载天翼云盘客户端时上传文件报错parsing time #2917 的解析故障。

如果项目最终仍然决定保留 #2919,我希望至少在 #2919、Release Note 或其他公开记录中补充 #2918 / @AmPlace 的 cross-reference,准确说明:#2918 最早的 189pc commit 在 #2919 之前已经独立实现了核心 parser 修复和 regression tests;后续 #2918 又扩展到了 189_tv,并完成了真实环境验证。

我不主张 #2918 的 189_tv 覆盖早于 #2919,也不是在没有代码复用证据的情况下要求追加 Co-authored-by。我要的是让最终公开记录准确反映两边各自贡献的先后和范围,而不是让更早出现的 189pc 核心实现完全消失。

现在 #2919 没有 cross-reference #2918,merge 记录里也没有 @AmPlace#2918 最后只剩一句:

Close by #2919

这个结果我不能接受,我希望这次不要再用一句 Close by #2919 就结束讨论,而是正面回答这三个问题。
而且现在完整时间线看下来,不就是已经明显在往 #2919 那边推了吗?那你直接把我 PR 关了不就好了?
#2919 那边都已经 approve 过一次了,你 10:21 还过来跟我说 I'd be happy to see it,那我正常理解肯定就是 #2918 还能继续改、还能继续 review 啊。
结果那边继续 approval、修改、reply、merge,我这边等到作者一回复,55 秒以后就直接被关了。
那我就想问,如果当时其实已经基本决定选 #2919 了,为什么不直接跟我说?如果 #2918 当时根本就没有实际被选中的机会,那你前面叫我继续改到底是什么意思?

@PIKACHUIM

Copy link
Copy Markdown
Member

你好,感谢您为这个BUG贡献,也提出了疑问:
1、两个高度重叠的 PR,项目实际的选择标准是什么?——经过人工和AI评估 ,我们认为#2919 修复更合理,在前面的评论也有对比和说明
2、根据评估,是本PR存在一些问题,所以没有选择本PR,下面是AI评审的原始信息:

OpenList PR 对比分析:#2918 vs #2919

基本信息

项目 PR #2918 PR #2919
标题 fix(drivers/189): support updated 189 time format fix(drivers/189): support updated 189 time format
作者 @AmPlace @shelken
创建时间 2026-08-11 2026-08-11
关联Issue Fixes #2917 Fixes #2917
状态 Open Open

问题背景

天翼云盘(189云盘)API 近期变更了时间字段的返回格式,导致 OpenList 的 189pc189_tv 驱动在处理 crypt 加密存储时出现解析错误,所有写操作失败。

旧格式示例

"Aug 11, 2026 10:37:18 PM"

新格式示例

"Aug 11, 2026, 10:37:18 PM +08"
"Aug 12, 2026, 12:35:41\u202fAM +08"  // 注意:AM 前使用了 Unicode 空格 U+202F

关键变化

  1. 月份后增加了逗号(,
  2. 从 24 小时制改为 12 小时制(配合 AM/PM)
  3. AM/PM 前使用 Unicode 空格(U+202F 或 U+00A0)而非普通空格
  4. 时间串自带时区(+08

技术方案对比

1. 时区处理逻辑

PR #2918 的实现

func (t *Time) Unmarshal(b []byte) error {
    bs := strings.Trim(string(b), "\"")
    // Unicode 空格处理
    bs = strings.ReplaceAll(bs, "\u202f", " ")
    bs = strings.ReplaceAll(bs, "\u00a0", " ")
    
    var v time.Time
    var err error
    
    // ❌ 问题:直接在所有时间串后追加 " +08"
    for _, f := range []string{
        "Jan 2, 2006 15:04:05 PM -07",
        "Jan 2, 2006 15:04:05 PM -07",
        "2006-01-02 15:04:05 -07",
    } {
        v, err = time.ParseInLocation(f, bs+" +08", time.Local)
        if err == nil {
            break
        }
    }
    
    if err != nil {
        return err
    }
    *t = Time(v)
    return nil
}

缺陷分析

PR #2919 的实现

func (t *Time) Unmarshal(b []byte) error {
    bs := strings.Trim(string(b), "\"")
    // Unicode 空格处理
    bs = strings.ReplaceAll(bs, "\u202f", " ")
    bs = strings.ReplaceAll(bs, "\u00a0", " ")
    
    var v time.Time
    var err error
    
    // ✅ 优势:双层循环,先尝试原始串,再尝试追加时区
    for _, s := range []string{bs, bs + " +08"} {
        for _, f := range []string{
            "Jan 2, 2006, 3:04:05 PM -07",
            "Jan 2, 2006 3:04:05 PM -07",
            "2006-01-02 15:04:05 -07",
        } {
            v, err = time.ParseInLocation(f, s, time.Local)
            if err == nil {
                break
            }
        }
        if err == nil {
            break
        }
    }
    
    if err != nil {
        return err
    }
    *t = Time(v)
    return nil
}

优势分析

  • 外层循环遍历两种时间串:bs(原始串)和 bs + " +08"(追加时区)
  • 先尝试解析原始串,如果成功则说明时间串自带时区
  • 如果失败,再尝试追加 +08 后解析,保证向后兼容
  • 完美处理自带时区和不带时区两种情况

2. 时间格式模板

PR #2918 的模板

"Jan 2, 2006 15:04:05 PM -07"   // ❌ 错误!
"Jan 2, 2006 15:04:05 PM -07"   // ❌ 重复且错误
"2006-01-02 15:04:05 -07"       // ✅ 正确(数字格式)

问题分析

  • 15:04:05 PM 组合是技术上错误的
  • Go 的 time.Parse 格式规范:
    • 3 = 12 小时制(0-12),应配合 AM/PM 使用
    • 15 = 24 小时制(0-23),不应配合 AM/PM 使用
  • 违反了 Go 时间格式的标准约定
  • 可能在某些边界情况下导致解析错误或结果不符合预期

PR #2919 的模板

"Jan 2, 2006, 3:04:05 PM -07"   // ✅ 正确!带逗号 + 12小时制 + AM/PM
"Jan 2, 2006 3:04:05 PM -07"    // ✅ 正确!不带逗号(向后兼容)
"2006-01-02 15:04:05 -07"       // ✅ 正确!数字格式

优势分析

  • 使用 3(12 小时制)正确配合 PM 使用
  • 三种模板各司其职,覆盖所有已知格式变体
  • 严格遵循 Go 时间解析规范

3. 测试用例质量

PR #2918 的测试

func TestTimeUnmarshal(t *testing.T) {
    tests := []struct {
        name     string
        input    string
        expected string
    }{
        {"numeric date", "2026-08-11 10:37:18", "2026-08-11 18:37:18 +0800 CST"},
        {"legacy month date", "Aug 12, 2026 15:32:44 PM", "2026-08-12 23:32:44 +0800 CST"},  // ❌ 无效数据
        // ...
    }
    // ...
}

问题分析

  • "Aug 12, 2026 15:32:44 PM" 不是合法的 12 小时制时间表示
  • 15:32:44 是 24 小时制格式,不应该配合 PM 使用
  • 这个测试用例本身就是错误的,无法验证正确性
  • 缺少对自带时区格式的测试

PR #2919 的测试

func TestTimeUnmarshal(t *testing.T) {
    tests := []struct {
        name     string
        input    string
        expected string
    }{
        {"numeric date", "2026-08-11 10:37:18", "2026-08-11 18:37:18 +0800 CST"},
        {"legacy month date", "Aug 11, 2026 10:37:18 PM", "2026-08-11 22:37:18 +0800 CST"},  // ✅ 正确
        {"new format with tz", "Aug 11, 2026, 10:37:18 PM +08", "2026-08-11 22:37:18 +0800 CST"},  // ✅ 测试自带时区
        {"new format no tz", "Aug 11, 2026, 10:37:18 PM", "2026-08-11 22:37:18 +0800 CST"},  // ✅ 测试不带时区
        {"narrow no-break space (U+202F)", "Aug 12, 2026, 12:35:41\u202fAM +08", "2026-08-12 00:35:41 +0800 CST"},
        {"no-break space (U+00A0)", "Aug 12, 2026, 12:35:41\u00a0PM +08", "2026-08-12 12:35:41 +0800 CST"},
    }
    // ...
}

func TestTimeUnmarshalRejectsInvalid(t *testing.T) {
    // 负面测试:验证错误处理
    input := "Aug 11, 2026, 25:37:18 PM +08"
    // ...
}

优势分析


实际场景验证

场景 1:API 返回自带时区的新格式(关键场景)

输入"Aug 11, 2026, 10:37:18 PM +08"
(这正是 issue #2917 中报告的实际格式)

PR 处理流程 结果
#2918 1. 替换 Unicode 空格
2. 执行 bs+" +08"
3. 得到 "Aug 11, 2026, 10:37:18 PM +08 +08"
4. 尝试用所有模板解析
解析失败
时区重复,无法解析
#2919 1. 替换 Unicode 空格
2. 先尝试原始串 bs
3. 用 "Jan 2, 2006, 3:04:05 PM -07" 成功解析
解析成功
正确处理自带时区

场景 2:API 返回不带时区的旧格式

输入"Aug 11, 2026 10:37:18 PM"
(向后兼容测试)

PR 处理流程 结果
#2918 1. 替换 Unicode 空格
2. 执行 bs+" +08"
3. 得到 "Aug 11, 2026 10:37:18 PM +08"
4. 尝试解析
⚠️ 可能成功
但使用了错误的模板
(15小时制+PM)
#2919 1. 替换 Unicode 空格
2. 先尝试原始串 bs(失败)
3. 再尝试 bs+" +08"
4. 用 "Jan 2, 2006 3:04:05 PM -07" 成功解析
解析成功
使用正确的模板

场景 3:含 Unicode 空格的时间串

输入"Aug 12, 2026, 12:35:41\u202fAM +08"
(issue #2917 中报告的实际格式)

PR 处理流程 结果
#2918 1. 替换 \u202f → 空格
2. 执行 bs+" +08"
3. 得到 "Aug 12, 2026, 12:35:41 AM +08 +08"
解析失败
时区重复
#2919 1. 替换 \u202f → 空格
2. 先尝试原始串 bs
3. 用 "Jan 2, 2006, 3:04:05 PM -07" 成功解析
解析成功
正确处理 Unicode 空格 + 自带时区

综合对比总结

评估维度 PR #2918 PR #2919 差异说明
时区处理正确性 ❌ 不正确 ✅ 正确 #2918 无法处理自带时区的情况
时间格式模板 ❌ 包含错误模板 ✅ 所有模板正确 #2918 使用了 15:04:05 PM 错误组合
测试覆盖 ❌ 测试数据无效 ✅ 测试全面且正确 #2918 使用了不合法的测试数据
关键场景验证 ❌ 失败 ✅ 成功 #2918 在自带时区场景下直接失败
向后兼容性 ⚠️ 理论兼容但有缺陷 ✅ 完全兼容 #2919 更稳健
代码逻辑 ❌ 存在明显缺陷 ✅ 逻辑严谨 #2919 使用双层循环处理所有情况
鲁棒性 ❌ 低 ✅ 高 #2919 能处理更多边界情况
是否能解决 issue #2917 不能 这是最关键的差异

推荐结论

🏆 强烈推荐合并 PR #2919

核心理由

  1. 能够真正解决问题

  2. 技术实现正确

  3. 测试更严谨

  4. 实际风险评估


建议行动

针对项目维护者

  1. 立即合并 PR fix(drivers/189): support updated 189 time format #2919

    • 这是唯一能够真正解决问题的方案
    • 代码质量高,测试覆盖全面
    • 已在本地 Docker 环境使用真实 189 存储验证通过
  2. 关闭或请求修改 PR fix(drivers/189): support updated time format from 189Cloud #2918

  3. 💬 沟通建议

针对贡献者


附录:技术细节说明

Go 时间格式规范

Go 的 time.Parse 使用参考时间 Mon Jan 2 15:04:05 MST 2006 作为格式模板:

  • 小时格式

    • 303:12 小时制(01-12),必须配合 PMpm 使用
    • 15:24 小时制(00-23),不应配合 AM/PM 使用
  • 正确示例

    • "3:04:05 PM" → 12 小时制 + AM/PM ✅
    • "15:04:05" → 24 小时制,无 AM/PM ✅
  • 错误示例

    • "15:04:05 PM" → 24 小时制 + AM/PM ❌(技术上错误)

时区处理最佳实践

当时间串可能自带时区也可能不带时区时,应该:

  1. 先尝试原始串:如果自带时区,直接解析成功
  2. 再尝试追加时区:如果不带时区,追加默认时区后解析
  3. 避免盲目追加:不要假设所有时间串都不带时区

PR #2919 的双层循环实现正是这种最佳实践的体现。


总结

PR #2918 和 PR #2919 都是为了修复同一个问题,但在技术实现上存在关键差异:

最终推荐:合并 PR #2919,关闭或请求修改 PR #2918

请注意,不是谁先提PR就会采纳谁的贡献,#2919在合并的时候已经满足修复问题以及我们评审
我们认可您的贡献和帮助,但肯定会择优选择的,并且这种情况下,不设置Co-authored-by是行业惯例

@AmPlace

AmPlace commented Aug 14, 2026

Copy link
Copy Markdown
Author

你好,感谢您为这个BUG贡献,也提出了疑问: 1、两个高度重叠的 PR,项目实际的选择标准是什么?——经过人工和AI评估 ,我们认为#2919 修复更合理,在前面的评论也有对比和说明 2、根据评估,是本PR存在一些问题,所以没有选择本PR,下面是AI评审的原始信息:

OpenList PR 对比分析:#2918 vs #2919

基本信息

项目
PR #2918
PR #2919

标题
fix(drivers/189): support updated 189 time format
fix(drivers/189): support updated 189 time format

作者
@AmPlace
@shelken

创建时间
2026-08-11
2026-08-11

关联Issue
Fixes #2917
Fixes #2917

状态
Open
Open

问题背景

天翼云盘(189云盘)API 近期变更了时间字段的返回格式,导致 OpenList 的 189pc189_tv 驱动在处理 crypt 加密存储时出现解析错误,所有写操作失败。
旧格式示例

"Aug 11, 2026 10:37:18 PM"

新格式示例

"Aug 11, 2026, 10:37:18 PM +08"
"Aug 12, 2026, 12:35:41\u202fAM +08"  // 注意:AM 前使用了 Unicode 空格 U+202F

关键变化

  1. 月份后增加了逗号(,
  2. 从 24 小时制改为 12 小时制(配合 AM/PM)
  3. AM/PM 前使用 Unicode 空格(U+202F 或 U+00A0)而非普通空格
  4. 时间串自带时区(+08

技术方案对比

1. 时区处理逻辑

PR #2918 的实现

func (t *Time) Unmarshal(b []byte) error {
    bs := strings.Trim(string(b), "\"")
    // Unicode 空格处理
    bs = strings.ReplaceAll(bs, "\u202f", " ")
    bs = strings.ReplaceAll(bs, "\u00a0", " ")
    
    var v time.Time
    var err error
    
    // ❌ 问题:直接在所有时间串后追加 " +08"
    for _, f := range []string{
        "Jan 2, 2006 15:04:05 PM -07",
        "Jan 2, 2006 15:04:05 PM -07",
        "2006-01-02 15:04:05 -07",
    } {
        v, err = time.ParseInLocation(f, bs+" +08", time.Local)
        if err == nil {
            break
        }
    }
    
    if err != nil {
        return err
    }
    *t = Time(v)
    return nil
}

缺陷分析

PR #2919 的实现

func (t *Time) Unmarshal(b []byte) error {
    bs := strings.Trim(string(b), "\"")
    // Unicode 空格处理
    bs = strings.ReplaceAll(bs, "\u202f", " ")
    bs = strings.ReplaceAll(bs, "\u00a0", " ")
    
    var v time.Time
    var err error
    
    // ✅ 优势:双层循环,先尝试原始串,再尝试追加时区
    for _, s := range []string{bs, bs + " +08"} {
        for _, f := range []string{
            "Jan 2, 2006, 3:04:05 PM -07",
            "Jan 2, 2006 3:04:05 PM -07",
            "2006-01-02 15:04:05 -07",
        } {
            v, err = time.ParseInLocation(f, s, time.Local)
            if err == nil {
                break
            }
        }
        if err == nil {
            break
        }
    }
    
    if err != nil {
        return err
    }
    *t = Time(v)
    return nil
}

优势分析

  • 外层循环遍历两种时间串:bs(原始串)和 bs + " +08"(追加时区)
  • 先尝试解析原始串,如果成功则说明时间串自带时区
  • 如果失败,再尝试追加 +08 后解析,保证向后兼容
  • 完美处理自带时区和不带时区两种情况

2. 时间格式模板

PR #2918 的模板

"Jan 2, 2006 15:04:05 PM -07"   // ❌ 错误!
"Jan 2, 2006 15:04:05 PM -07"   // ❌ 重复且错误
"2006-01-02 15:04:05 -07"       // ✅ 正确(数字格式)

问题分析

  • 15:04:05 PM 组合是技术上错误的

  • Go 的 time.Parse 格式规范:

    • 3 = 12 小时制(0-12),应配合 AM/PM 使用
    • 15 = 24 小时制(0-23),不应配合 AM/PM 使用
  • 违反了 Go 时间格式的标准约定

  • 可能在某些边界情况下导致解析错误或结果不符合预期

PR #2919 的模板

"Jan 2, 2006, 3:04:05 PM -07"   // ✅ 正确!带逗号 + 12小时制 + AM/PM
"Jan 2, 2006 3:04:05 PM -07"    // ✅ 正确!不带逗号(向后兼容)
"2006-01-02 15:04:05 -07"       // ✅ 正确!数字格式

优势分析

  • 使用 3(12 小时制)正确配合 PM 使用
  • 三种模板各司其职,覆盖所有已知格式变体
  • 严格遵循 Go 时间解析规范

3. 测试用例质量

PR #2918 的测试

func TestTimeUnmarshal(t *testing.T) {
    tests := []struct {
        name     string
        input    string
        expected string
    }{
        {"numeric date", "2026-08-11 10:37:18", "2026-08-11 18:37:18 +0800 CST"},
        {"legacy month date", "Aug 12, 2026 15:32:44 PM", "2026-08-12 23:32:44 +0800 CST"},  // ❌ 无效数据
        // ...
    }
    // ...
}

问题分析

  • "Aug 12, 2026 15:32:44 PM" 不是合法的 12 小时制时间表示
  • 15:32:44 是 24 小时制格式,不应该配合 PM 使用
  • 这个测试用例本身就是错误的,无法验证正确性
  • 缺少对自带时区格式的测试

PR #2919 的测试

func TestTimeUnmarshal(t *testing.T) {
    tests := []struct {
        name     string
        input    string
        expected string
    }{
        {"numeric date", "2026-08-11 10:37:18", "2026-08-11 18:37:18 +0800 CST"},
        {"legacy month date", "Aug 11, 2026 10:37:18 PM", "2026-08-11 22:37:18 +0800 CST"},  // ✅ 正确
        {"new format with tz", "Aug 11, 2026, 10:37:18 PM +08", "2026-08-11 22:37:18 +0800 CST"},  // ✅ 测试自带时区
        {"new format no tz", "Aug 11, 2026, 10:37:18 PM", "2026-08-11 22:37:18 +0800 CST"},  // ✅ 测试不带时区
        {"narrow no-break space (U+202F)", "Aug 12, 2026, 12:35:41\u202fAM +08", "2026-08-12 00:35:41 +0800 CST"},
        {"no-break space (U+00A0)", "Aug 12, 2026, 12:35:41\u00a0PM +08", "2026-08-12 12:35:41 +0800 CST"},
    }
    // ...
}

func TestTimeUnmarshalRejectsInvalid(t *testing.T) {
    // 负面测试:验证错误处理
    input := "Aug 11, 2026, 25:37:18 PM +08"
    // ...
}

优势分析

实际场景验证

场景 1:API 返回自带时区的新格式(关键场景)

输入"Aug 11, 2026, 10:37:18 PM +08"
(这正是 issue #2917 中报告的实际格式)

PR
处理流程
结果

#2918

  1. 替换 Unicode 空格2. 执行 bs+" +08"3. 得到 "Aug 11, 2026, 10:37:18 PM +08 +08"4. 尝试用所有模板解析
    解析失败时区重复,无法解析

#2919

  1. 替换 Unicode 空格2. 先尝试原始串 bs3. 用 "Jan 2, 2006, 3:04:05 PM -07" 成功解析
    解析成功正确处理自带时区

场景 2:API 返回不带时区的旧格式

输入"Aug 11, 2026 10:37:18 PM"
(向后兼容测试)

PR
处理流程
结果

#2918

  1. 替换 Unicode 空格2. 执行 bs+" +08"3. 得到 "Aug 11, 2026 10:37:18 PM +08"4. 尝试解析
    ⚠️ 可能成功但使用了错误的模板(15小时制+PM)

#2919

  1. 替换 Unicode 空格2. 先尝试原始串 bs(失败)3. 再尝试 bs+" +08"4. 用 "Jan 2, 2006 3:04:05 PM -07" 成功解析
    解析成功使用正确的模板

场景 3:含 Unicode 空格的时间串

输入"Aug 12, 2026, 12:35:41\u202fAM +08"
(issue #2917 中报告的实际格式)

PR
处理流程
结果

#2918

  1. 替换 \u202f → 空格2. 执行 bs+" +08"3. 得到 "Aug 12, 2026, 12:35:41 AM +08 +08"
    解析失败时区重复

#2919

  1. 替换 \u202f → 空格2. 先尝试原始串 bs3. 用 "Jan 2, 2006, 3:04:05 PM -07" 成功解析
    解析成功正确处理 Unicode 空格 + 自带时区

综合对比总结

评估维度
PR #2918
PR #2919
差异说明

时区处理正确性
❌ 不正确
✅ 正确
#2918 无法处理自带时区的情况

时间格式模板
❌ 包含错误模板
✅ 所有模板正确
#2918 使用了 15:04:05 PM 错误组合

测试覆盖
❌ 测试数据无效
✅ 测试全面且正确
#2918 使用了不合法的测试数据

关键场景验证
❌ 失败
✅ 成功
#2918 在自带时区场景下直接失败

向后兼容性
⚠️ 理论兼容但有缺陷
✅ 完全兼容
#2919 更稳健

代码逻辑
❌ 存在明显缺陷
✅ 逻辑严谨
#2919 使用双层循环处理所有情况

鲁棒性
❌ 低
✅ 高
#2919 能处理更多边界情况

是否能解决 issue #2917
不能

这是最关键的差异

推荐结论

🏆 强烈推荐合并 PR #2919

核心理由

  1. 能够真正解决问题

  2. 技术实现正确

  3. 测试更严谨

  4. 实际风险评估

建议行动

针对项目维护者

  1. 立即合并 PR fix(drivers/189): support updated 189 time format #2919

    • 这是唯一能够真正解决问题的方案
    • 代码质量高,测试覆盖全面
    • 已在本地 Docker 环境使用真实 189 存储验证通过
  2. 关闭或请求修改 PR fix(drivers/189): support updated time format from 189Cloud #2918

  3. 💬 沟通建议

针对贡献者

附录:技术细节说明

Go 时间格式规范

Go 的 time.Parse 使用参考时间 Mon Jan 2 15:04:05 MST 2006 作为格式模板:

  • 小时格式

    • 303:12 小时制(01-12),必须配合 PMpm 使用
    • 15:24 小时制(00-23),不应配合 AM/PM 使用
  • 正确示例

    • "3:04:05 PM" → 12 小时制 + AM/PM ✅
    • "15:04:05" → 24 小时制,无 AM/PM ✅
  • 错误示例

    • "15:04:05 PM" → 24 小时制 + AM/PM ❌(技术上错误)

时区处理最佳实践

当时间串可能自带时区也可能不带时区时,应该:

  1. 先尝试原始串:如果自带时区,直接解析成功
  2. 再尝试追加时区:如果不带时区,追加默认时区后解析
  3. 避免盲目追加:不要假设所有时间串都不带时区

PR #2919 的双层循环实现正是这种最佳实践的体现。

总结

PR #2918 和 PR #2919 都是为了修复同一个问题,但在技术实现上存在关键差异:

最终推荐:合并 PR #2919,关闭或请求修改 PR #2918

请注意,不是谁先提PR就会采纳谁的贡献,#2919在合并的时候已经满足修复问题以及我们评审 我们认可您的贡献和帮助,但肯定会择优选择的,并且这种情况下,不设置Co-authored-by是行业惯例

@PIKACHUIM

这份回复没有回答我最核心的流程问题。更严重的是,你用来解释选择结果的 AI 对比报告,对 #2918 的实际代码和 tests 存在多处可以直接核对的事实错误,我这篇回答是经过 gpt 润色,包括前面我的回答,但是事实核对还有日志包括代码思路都是我自己提供的的,不是把问题丢给 ai 让他替我判断的,我刚才又核对了 #2918 最早的 commit bac76bb。这个正确的 comma + 12-hour layout、expected hour=15 的 legacy test,以及 invalid negative test,在 #2918 第一版里就已经存在。

所以这里甚至不是“AI 报告拿到了 #2918 的旧版本”这么简单。

这份报告描述的代码并不对应 #2918 的第一版,也不对应后来的版本。

那我现在更想知道,这份被用来解释“为什么择优选择 #2919”的 AI 报告,当时到底是基于什么代码生成的?

坦白说,如果这份报告确实参与了当时的选择,那现在的问题就不只是“两个方案判断不同”了,而是用于择优的评估材料本身就没有准确读到被比较 PR 的实际代码。

1. AI 报告引用的 #2918 代码和当时实际代码对不上

报告声称 #2918 的 layouts 是:

"Jan 2, 2006 15:04:05 PM -07",
"Jan 2, 2006 15:04:05 PM -07",
"2006-01-02 15:04:05 -07",

也就是重复了两次 legacy layout,并据此得出 #2918 没有正确新增 comma + 12-hour layout 的结论。

#2918 的实际代码是:

for _, f := range []string{
    "2006-01-02 15:04:05 -07",
    "Jan 2, 2006 15:04:05 PM -07",
    "Jan 2, 2006, 3:04:05 PM -07",
} {
    v, err = time.ParseInLocation(f, bs+" +08", time.Local)
}

其中:

"Jan 2, 2006, 3:04:05 PM -07"

就是这次为新格式新增的正确 comma + 12-hour layout。

所以 AI 报告展示的 #2918 代码并不存在。它重复了旧 layout,同时漏掉了 #2918 实际新增的关键 layout。

这不是对实现优劣的评价差异,而是对被评审代码的事实引用错误。

2. 报告把 parser input 当成 raw API response,而且与我的真实运行日志矛盾

报告反复断言:

issue #2917 中报告的实际格式就是自带时区的

并以此得出:

#2918 无法真正解决 issue #2917

但旧代码本来就是:

bs := strings.Trim(string(b), "\"")

for _, f := range []string{
    "2006-01-02 15:04:05 -07",
    "Jan 2, 2006 15:04:05 PM -07",
} {
    v, err = time.ParseInLocation(f, bs+" +08", time.Local)
}

parser error 展示的是传给 time.ParseInLocation 的完整输入,也就是 OpenList 构造后的:

bs + " +08"

它不是 raw HTTP response。

我此前已经用旧代码独立复现过:当 bs 不带 timezone,例如:

Aug 11, 2026, 7:20:03\u202fPM

旧代码追加 +08 后,就会产生与 #2917 相同形状、只包含一个 +08 的 parser error。

反过来,如果 bs 本来已经带有 +08,旧代码传给 parser 的实际输入应该是:

Aug 11, 2026, 7:20:03\u202fPM +08 +08

相应 error 中也会显示两个 +08

而且现在这不只是 synthetic reproduction。

我找到了提交 #2918 之前真实运行 189Cloud PC 时保存下来的 OpenList 日志。从 18:54 到 19:32,同类 parser error 连续出现了很多次,例如:

2026/08/11 19:32:44.670603 WARN RESTY parsing time "Aug 12, 2026, 3:32:44\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:32:44\xe2\x80\xafAM +08" as " "

这批真实日志中的 parser input 始终只有一个 +08

结合当时确定执行的:

time.ParseInLocation(f, bs+" +08", time.Local)

至少在我实际遇到并修复的这个 189Cloud PC 场景中,追加前的 bs 对应的是:

Aug 12, 2026, 3:32:44\u202fAM

而不是:

Aug 12, 2026, 3:32:44\u202fAM +08

否则旧代码构造出的 parser input 必然包含:

+08 +08

但我保存的所有这些真实请求日志中,都没有出现 +08 +08

这不是 synthetic test,也不只是我根据 #2917 猜测。它是提交 #2918 前真实 189Cloud PC 环境中的连续运行日志。

我不会把这些日志夸张成 raw HTTP dump,也不主张 189Cloud 永远不可能返回自带 timezone 的字符串。#2919 对 raw 自带 timezone 的输入增加兼容,仍然可以算额外 coverage。

但这些日志已经足以反驳 AI 报告中的绝对结论:

实际故障场景的 raw timestamp 已经自带 timezone,因此 #2918 必然形成 +08 +08,无法解决 #2917

至少在我真实遇到并修复的这批请求中,实际 parser input 与这个前提不一致。

#2918 中对应的 comma、12-hour 和 U+202F regression test,也正是根据这批真实运行日志加入的,而不是报告所描述的无依据测试。

提交 #2918 前保存的全部相关 parser 日志
openlist-2-fenshen  | 2026/08/11 18:54:52.832617 WARN RESTY parsing time "Aug 12, 2026, 2:54:52\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 2:54:52\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 18:54:52.833440 ERROR RESTY parsing time "Aug 12, 2026, 2:54:52\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 2:54:52\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 18:55:13.093315 WARN RESTY parsing time "Aug 12, 2026, 2:55:12\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 2:55:12\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 18:55:13.093419 ERROR RESTY parsing time "Aug 12, 2026, 2:55:12\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 2:55:12\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:00:56.432774 WARN RESTY parsing time "Aug 12, 2026, 3:00:56\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:00:56\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:00:56.432800 ERROR RESTY parsing time "Aug 12, 2026, 3:00:56\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:00:56\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:03:25.244986 WARN RESTY parsing time "Aug 12, 2026, 3:03:25\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:03:25\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:03:25.245024 ERROR RESTY parsing time "Aug 12, 2026, 3:03:25\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:03:25\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:03:27.262316 WARN RESTY parsing time "Aug 12, 2026, 3:03:27\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:03:27\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:03:27.262345 ERROR RESTY parsing time "Aug 12, 2026, 3:03:27\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:03:27\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:03:29.007682 WARN RESTY parsing time "Aug 12, 2026, 3:03:28\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:03:28\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:03:29.007854 ERROR RESTY parsing time "Aug 12, 2026, 3:03:28\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:03:28\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:03:45.441475 WARN RESTY parsing time "Aug 12, 2026, 3:03:45\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:03:45\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:03:45.441500 ERROR RESTY parsing time "Aug 12, 2026, 3:03:45\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:03:45\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:04:56.104417 WARN RESTY parsing time "Aug 12, 2026, 3:04:55\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:04:55\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:04:56.104445 ERROR RESTY parsing time "Aug 12, 2026, 3:04:55\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:04:55\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:12:12.234166 WARN RESTY parsing time "Aug 12, 2026, 3:12:11\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:12:11\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:12:12.234332 ERROR RESTY parsing time "Aug 12, 2026, 3:12:11\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:12:11\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:14:40.828908 WARN RESTY parsing time "Aug 12, 2026, 3:14:40\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:14:40\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:14:40.828935 ERROR RESTY parsing time "Aug 12, 2026, 3:14:40\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:14:40\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:15:09.999210 WARN RESTY parsing time "Aug 12, 2026, 3:15:09\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:15:09\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:15:09.999236 ERROR RESTY parsing time "Aug 12, 2026, 3:15:09\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:15:09\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:15:37.929920 WARN RESTY parsing time "Aug 12, 2026, 3:15:37\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:15:37\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:15:37.929945 ERROR RESTY parsing time "Aug 12, 2026, 3:15:37\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:15:37\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:24:30.771322 WARN RESTY parsing time "Aug 12, 2026, 3:24:30\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:24:30\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:24:30.771347 ERROR RESTY parsing time "Aug 12, 2026, 3:24:30\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:24:30\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:26:46.490934 WARN RESTY parsing time "Aug 12, 2026, 3:26:46\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:26:46\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:26:46.490961 ERROR RESTY parsing time "Aug 12, 2026, 3:26:46\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:26:46\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:27:03.409356 WARN RESTY parsing time "Aug 12, 2026, 3:27:03\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:27:03\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:27:03.409455 ERROR RESTY parsing time "Aug 12, 2026, 3:27:03\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:27:03\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:27:09.046425 WARN RESTY parsing time "Aug 12, 2026, 3:27:08\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:27:08\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:27:09.046578 ERROR RESTY parsing time "Aug 12, 2026, 3:27:08\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:27:08\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:27:36.064864 WARN RESTY parsing time "Aug 12, 2026, 3:27:35\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:27:35\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:27:36.064891 ERROR RESTY parsing time "Aug 12, 2026, 3:27:35\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:27:35\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:27:53.645019 WARN RESTY parsing time "Aug 12, 2026, 3:27:53\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:27:53\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:27:53.645044 ERROR RESTY parsing time "Aug 12, 2026, 3:27:53\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:27:53\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:32:32.508687 WARN RESTY parsing time "Aug 12, 2026, 3:32:32\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:32:32\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:32:32.508719 ERROR RESTY parsing time "Aug 12, 2026, 3:32:32\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:32:32\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:32:41.332585 WARN RESTY parsing time "Aug 12, 2026, 3:32:41\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:32:41\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:32:41.332613 ERROR RESTY parsing time "Aug 12, 2026, 3:32:41\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:32:41\xe2\x80\xafAM +08" as " "
openlist-2-fenshen  | 2026/08/11 19:32:44.670603 WARN RESTY parsing time "Aug 12, 2026, 3:32:44\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:32:44\xe2\x80\xafAM +08" as " ", Attempt 1
openlist-2-fenshen  | 2026/08/11 19:32:44.670622 ERROR RESTY parsing time "Aug 12, 2026, 3:32:44\xe2\x80\xafAM +08" as "Jan 2, 2006 15:04:05 PM -07": cannot parse ", 3:32:44\xe2\x80\xafAM +08" as " "

3. AI 报告错误引用了 #2918 的测试

报告把 #2918 的 legacy test 写成:

"Aug 12, 2026 15:32:44 PM" → "2026-08-12 23:32:44"

#2918 实际测试的 expected hour 是 15,不是 23

{
    name:  "legacy month date",
    input: "Aug 12, 2026 15:32:44 PM",
    want:  time.Date(2026, time.August, 12, 15, 32, 44, 0, time.Local),
},

这个输入语义上确实不规范,但对应 layout 来自共同 base 中原本就存在的:

"Jan 2, 2006 15:04:05 PM -07"

#2918 保留这个 case,是为了验证既有 parser 接受范围没有被无意改变;这与本次新增的正确 comma + 12-hour layout 是两个不同问题。

可以讨论是否应该顺便删除或修改这个 legacy layout,但不能把实际 expected value从 15 改写成 23,再据此评价 #2918 的测试行为。

另外,报告只列出了 #2919 的 invalid negative test,却遗漏了 #2918 的 189pc 和 189_tv tests 中同样存在:

func TestTimeUnmarshalRejectsInvalidTime(t *testing.T) {
    var got Time
    if err := got.Unmarshal([]byte("Aug 12, 2026, 25:32:44 AM")); err == nil {
        t.Fatal("Time.Unmarshal accepted an invalid time")
    }
}

所以这份对比报告在评价双方 negative test 覆盖时并不完整。

4. 技术偏好仍然没有回答 review 路径问题

即使暂时不考虑上述事实错误,回答:

我们认为 #2919 修复更合理

最多只能解释为什么你更喜欢 #2919 当时的实现。

它仍然没有回答我真正问的问题:

实现偏好和 review 流程是否对等,是两个不同问题。重复说明“#2919 更优”,不能代替对上述流程问题的回答。

5. 我没有要求在无代码复用证据的情况下追加 Co-authored-by

你最后说:

这种情况下,不设置 Co-authored-by 是行业惯例

但我此前已经明确说明:在没有代码复用证据的情况下,我没有要求追加 Co-authored-by

我要求的是 cross-reference 和准确的公开记录。

我也再次明确贡献先后的范围:

我要的不是把不存在的共同作者关系写进 commit,而是让最终记录准确反映:#2918 更早公开、独立完成了 189pc 的核心修复和 tests,而不是让这项贡献在最终记录中只剩一句 Close by #2919

我此前没有在缺乏证据的情况下直接要求 Co-authored-by,不代表永久排除这一问题;如果后续确认 #2919 或其 AI workflow 实际参考、采用或实质复用了 #2918 的代码或 tests,attribution 应当根据实际复用情况重新讨论。

请正面回答以下问题

  1. 你是否确认,这份 AI 对比报告引用的 fix(drivers/189): support updated time format from 189Cloud #2918 layouts 与实际代码不一致,错误遗漏了 fix(drivers/189): support updated time format from 189Cloud #2918 新增的 comma + 12-hour layout?

  2. 你是否确认,报告错误改写了 fix(drivers/189): support updated time format from 189Cloud #2918 legacy test 的 expected value,并遗漏了 fix(drivers/189): support updated time format from 189Cloud #2918 已有的 invalid negative test?

  3. 对于我提交 fix(drivers/189): support updated time format from 189Cloud #2918 前保存的这批真实运行日志,你是否同意:在当时无条件执行 bs + " +08" 的代码路径下,parser input 始终只有一个 +08,因此至少这批真实请求并不支持“追加前的 bs 已经自带 +08”这个判断?

  4. [BUG] crypt驱动挂载天翼云盘客户端时上传文件报错parsing time #2917 的 raw API response 已经自带 timezone”这一判断,是否还有其他独立证据?

    除了 parser error 和 synthetic unit test,如果有 raw response、HTTP body 或其他证据,请直接提供。

    如果没有,报告中“fix(drivers/189): support updated time format from 189Cloud #2918 无法真正解决 [BUG] crypt驱动挂载天翼云盘客户端时上传文件报错parsing time #2917”这个绝对结论是否应该纠正?

  5. 你 10:21 告诉我可以继续修改时,fix(drivers/189): support updated time format from 189Cloud #2918 到底是不是一个仍有实际可能被选择的候选?如果是,为什么没有给出修改和复审时间;如果不是,那句邀请修改到底有什么实际意义?

  6. 既然我没有要求无依据的 Co-authored-by,项目是否愿意至少在 fix(drivers/189): support updated 189 time format #2919 或其他公开记录中 cross-reference fix(drivers/189): support updated time format from 189Cloud #2918,准确记录更早的 189pc 核心实现和 regression tests?

我接受维护者有权选择最终实现,也没有主张“谁先提交就必须合谁”。

但我不能接受用一份错误引用 #2918 代码、错误引用 tests,并把未经证明的 raw timezone 推断当成事实的 AI 报告,作为这次选择已经得到充分解释的依据。

请基于两个 PR 的实际代码、我提交前保存的真实运行日志和公开时间线,逐条回答上面的问题,而不是再次用“#2919 更优”或“不设置 Co-authored-by 是行业惯例”带过。

@OpenListTeam OpenListTeam locked and limited conversation to collaborators Aug 14, 2026
@PIKACHUIM

Copy link
Copy Markdown
Member

如果AI的评审有误,你可以友好的指出;如果您认为您的代码更优,也可以阐述原因
但我们绝不会欢迎仅仅是因为自己的PR没有采纳就来指责项目组成员的人

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] crypt驱动挂载天翼云盘客户端时上传文件报错parsing time

2 participants