Skip to content

fix(bpm-core-client): keep the local calendar day for zoned date picker values - #2

Merged
fantasywind merged 1 commit into
Rytass:mainfrom
coollugi:fix/date-picker-zoned-value
Aug 3, 2026
Merged

fix(bpm-core-client): keep the local calendar day for zoned date picker values#2
fantasywind merged 1 commit into
Rytass:mainfrom
coollugi:fix/date-picker-zoned-value

Conversation

@coollugi

@coollugi coollugi commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

問題

在表單的日期欄位選日期,UTC 以東的使用者會被存成前一天,而且被寫進簽核案件的就是這個位移過的值

Asia/Taipei 重現:

日曆上選的 實際存下的
2026-08-20 2026-08-19
2026-01-01 2025-12-31

datetime 欄位也中招,只是比較不明顯 —— 存下的時刻是整個偏移一個 UTC offset(這裡是 8 小時),而不是差一天

根因

CalendarMethodsMoment 回傳的是帶時區的 ISO 字串,每一個操作最後都收在 moment(...).toISOString(),也就是 UTC
所以在 Asia/Taipei 選 20 號,傳進 parseDatePickerValue 的是 2026-08-19T16:00:00.000Z,這個值本身是正確的

問題出在 parseDatePickerValue 接著比對 DATE_TIME_VALUE_PATTERN/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/,這個 pattern 對結尾的 Z 照樣命中),用 T 切開之後,把 UTC 的日曆日期當成本地日期重建:

const [datePart = '', timePart = '00:00'] = value.split('T');   // '2026-08-19'
return new Date(year, month - 1, day, hour, minute);            // 本地時間 2026-08-19

等於拿 UTC 的那一頁日曆當成本地的日曆在用

修法

偵測時區後綴(Z±HH:MM),有的話就交給 runtime 自己解析 offset
沒有時區的輸入維持原本的手動解析,所以 '2026-08-20''2026-08-20T09:30' 仍然代表本地時間

測試

新增 libs/bpm-core-client/src/lib/form/form-rendering.spec.ts,共 10 個案例
涵蓋 UTC 輸入、UTC 兩側的帶 offset 輸入、無時區輸入的迴歸防護,以及 formatDatePickerValueformatDateTimePickerValue 兩者的無效輸入

測試套件釘住 TZ=Asia/Taipei —— 這個 bug 在 UTC 的 runner 上看不見,不釘時區的測試不管有沒有 fix 都會過

驗證過測試真的抓得到:只還原 source 的改動,其中 3 個會紅

nx test bpm-core-client(6 suites/22 tests)/nx lint bpm-core-clientnx typecheck bpm-core-client 全部通過

🤖 Generated with Claude Code

https://claude.ai/code/session_01BVBYyU83KfmAeJGj6gGsVm

…er values

Calendar adapters emit zoned ISO strings — CalendarMethodsMoment returns
moment(...).toISOString(), i.e. UTC. parseDatePickerValue split those on "T"
and rebuilt the UTC calendar date as a local date, so every user east of UTC
saw the picked day shift back by one, and the shifted value was persisted.

Route zone-qualified input through new Date() so the runtime resolves the
offset, and keep the manual parse for zone-less values.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVBYyU83KfmAeJGj6gGsVm
@coollugi
coollugi force-pushed the fix/date-picker-zoned-value branch from 29f2771 to 6fe126d Compare August 3, 2026 12:23
@coollugi
coollugi changed the base branch from main to staging August 3, 2026 12:23
@coollugi

coollugi commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

補充一下 base 的調整:這個 PR 已經從 main 改成 staging

原因是 main 目前落後 staging 52 顆 commit,而且 staging..main 是空的,也就是說 mainstaging 的嚴格祖先
已發布的套件版本跟 staging 對得上(bpm-core-react@0.7.6bpm-core / bpm-core-client / shared@0.5.1),而這三個 PR 修的問題在 staging 上也都還重現得出來,所以 staging 看起來才是實際出貨的那一條

也因為 main 是嚴格祖先,把 main 快轉到 staging 會是無損而且零衝突的
順帶一提這也會解掉一個副作用:Dependabot 只會對 default branch 開 PR,所以 #1 現在是開在落後的 main 上,它的安全性更新不會進到任何一版

如果你們的流程其實是要進 main,跟我說一聲我再改回去就好

@coollugi
coollugi changed the base branch from staging to main August 3, 2026 13:03
@fantasywind
fantasywind merged commit 7e814df into Rytass:main Aug 3, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants