Skip to content

fix: unify timestamp format to ISO 8601 for correct timezone display#194

Merged
coji merged 10 commits intomainfrom
fix/timestamp-format-unification
Mar 17, 2026
Merged

fix: unify timestamp format to ISO 8601 for correct timezone display#194
coji merged 10 commits intomainfrom
fix/timestamp-format-unification

Conversation

@coji
Copy link
Owner

@coji coji commented Mar 17, 2026

Summary

  • timeFormatUTC() was stripping the Z suffix from ISO 8601 strings (2026-03-16T02:56:35Z2026-03-16 02:56:35), causing dayjs() to interpret them as local time. This made .tz('Asia/Tokyo') a no-op — UTC values were displayed as if they were JST
  • Remove timeFormatUTC and pass ISO 8601 strings through to DB as-is
  • Fix timeFormatTz and all display code to use dayjs.utc() for correct parsing
  • Add CLAUDE.md section on datetime/timezone conventions

Changes

File What
batch/db/mutations.ts Remove timeFormatUTC() calls from upsert functions
batch/helper/timeformat.ts Delete timeFormatUTC, fix timeFormatTz to use dayjs.utc()
app/routes/$orgSlug/workload/$login/index.tsx 6x dayjs()dayjs.utc()
app/routes/$orgSlug/+components/pr-block.tsx 2x dayjs()dayjs.utc()
app/routes/$orgSlug/workload/+components/team-stacks-chart.tsx 1x dayjs()dayjs.utc()
app/libs/business-hours.ts 2x dayjs()dayjs.utc()
app/routes/$orgSlug/analysis/reviews/index.tsx Add .utc() to date range filter
app/routes/$orgSlug/analysis/feedbacks/_index/index.tsx Add .utc() to date range filter
batch/github/review-response.ts 2x dayjs()dayjs.utc()
batch/helper/timeformat.test.ts New: tests for timeFormatTz
CLAUDE.md Add datetime/timezone conventions

Test plan

  • pnpm validate passes (lint, format, typecheck, build, 200 tests)
  • Deploy → Settings > Data Management > 「Recalculate Cycle Times」 for each org
  • Verify workload page timestamps display in JST correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新機能

    • 日数差分を算出する共通ユーティリティを追加しました(業務日オプション対応)。
  • バグ修正

    • 日時処理をUTCで正規化してから表示/差分計算するよう統一し、経過表示・集計のタイムゾーン依存を解消しました。
    • ローカル時刻ベースのフォーマット処理を整理しました。
  • テスト

    • 時刻フォーマットとレビュー応答ロジックの単体テストを追加・更新しました。
  • ドキュメント

    • 日時・タイムゾーンの取り扱いガイドラインを追記しました。
  • 雑務

    • 特定ファイルを.gitignoreに追加しました。

…ne display

timeFormatUTC() was stripping the Z suffix from ISO 8601 strings, causing
dayjs() to interpret them as local time. This made .tz('Asia/Tokyo')
a no-op, displaying UTC values as if they were JST.

- Remove timeFormatUTC and pass ISO 8601 strings through to DB as-is
- Fix timeFormatTz to use dayjs.utc() for correct parsing
- Update all display code to use dayjs.utc() instead of dayjs()
- Add CLAUDE.md section on datetime/timezone conventions
- Add tests for timeFormatTz

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

アプリ全体で日時解析をUTC基準に統一し、その後目的のタイムゾーンへ変換するように変更しました。ドキュメントに「日時・タイムゾーンの原則」を追加し、timeFormatUTCを削除、diffInDaysを新規公開しました。DB upsert経路からローカル時整形を除去しています。

Changes

Cohort / File(s) Summary
ドキュメント
CLAUDE.md
「日時・タイムゾーンの原則」を追加(DBはISO 8601 Zで保存、解析はUTC→tz変換など)。同内容が2箇所に挿入。
日付解析のUTC標準化(表示・相対時刻)
app/routes/$orgSlug/+components/pr-block.tsx, app/components/size-badge-popover.tsx, app/routes/$orgSlug/settings/repositories.add/+components/repository-item.tsx, app/routes/$orgSlug/settings/data-management/index.tsx, app/routes/$orgSlug/analysis/feedbacks/_index/+components/feedback-columns.tsx, app/routes/$orgSlug/workload/$login/index.tsx, app/routes/$orgSlug/workload/+components/team-stacks-chart.tsx
表示・相対時刻・年齢計算でdayjs.utc(...)を使うよう統一。
分析ページのUTC集計基準
app/routes/$orgSlug/analysis/feedbacks/_index/index.tsx, app/routes/$orgSlug/analysis/reviews/index.tsx
sinceDate等の算出をUTCベース(dayjs.utc().subtract(...).startOf('day'))に変更。
業務時間ユーティリティ
app/libs/business-hours.ts
日付構築をdayjs.utc(...).tz(timezone)で行うよう変更。新規エクスポートdiffInDays(start,end,businessDaysOnly,timezone)を追加。
スループット算出の差分集約化
app/routes/$orgSlug/throughput/deployed/+functions/queries.server.ts, app/routes/$orgSlug/throughput/merged/+functions/queries.server.ts, app/routes/$orgSlug/throughput/ongoing/+functions/queries.server.ts
各所の時間差計算をdiffInDays(...)に統一(以前の複数分岐を置換)。
バッチ:DB upsert と時間整形削除
batch/db/mutations.ts
timeFormatUTC経由のローカル時整形を廃止し、挿入/更新に元のデータオブジェクトをそのまま使用。
バッチ:時間フォーマットとテスト
batch/helper/timeformat.ts, batch/helper/timeformat.test.ts
timeFormatUTCを削除。timeFormatTzdayjs.utc(date).tz(tz)経路に変更し、ユニットテストを追加。
バッチ:レビュー応答ロジックのUTC化とテスト追加
batch/github/review-response.ts, batch/github/review-response.test.ts
コメントフィルタリング(90日)や応答時間計算をdayjs.utc(...)で行うよう統一し、テストを追加。
バッチ:ビジネスロジックのUTC化・テスト更新
batch/bizlogic/cycletime.ts, batch/bizlogic/*.test.ts
サイクルタイム算出をUTCベースに変更。テストの日時リテラルをISO 8601 Z形式に更新。
その他軽微
.gitignore
opensrc/AGENTS.mdをignoreに追加。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 UTCの野原で跳ねるよ
時計は一列に並んでる
時差の葉っぱは風に払って
にんじんコードで踊る夜
ぽん、とウサギは笑うよ

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトルは PR の主な変更内容であるタイムスタンプ形式の ISO 8601 への統一と UTC ベースのパース処理への変更を正確に反映しており、変更内容と明確に関連している。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/timestamp-format-unification
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coji and others added 2 commits March 17, 2026 11:08
AGENTS.md is auto-generated by opensrc, so it should not be tracked.
Its content (source code reference docs) is now in CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
batch/helper/timeformat.test.ts (1)

5-15: 旧フォーマットの回帰テストも追加したいです。

移行期間は YYYY-MM-DD HH:mm:ss 形式の既存データが残り得るので、それも UTC として解釈できることをここで固定しておくと安心です。

💡 追加イメージ
 describe('timeFormatTz', () => {
   it('converts ISO 8601 UTC to Asia/Tokyo', () => {
     expect(timeFormatTz('2026-03-16T02:56:35Z', 'Asia/Tokyo')).toBe(
       '2026-03-16 11:56:35',
     )
   })
+
+  it('treats legacy UTC strings without Z as UTC', () => {
+    expect(timeFormatTz('2026-03-16 02:56:35', 'Asia/Tokyo')).toBe(
+      '2026-03-16 11:56:35',
+    )
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@batch/helper/timeformat.test.ts` around lines 5 - 15, 既存のISOテストに加えて旧フォーマット
'YYYY-MM-DD HH:mm:ss' を UTC として解釈する回帰テストを追加してください:
batch/helper/timeformat.test.ts にある timeFormatTz を使い、例えば
timeFormatTz('2026-03-16 02:56:35', 'Asia/Tokyo') が '2026-03-16 11:56:35'
を返すケースと timeFormatTz('2026-03-16 02:56:35', 'US/Pacific') が '2026-03-15
19:56:35' を返すケースを追加して、旧フォーマットを明示的に UTC として扱うことを固定化してください.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/routes/`$orgSlug/analysis/reviews/index.tsx:
- Around line 68-72: sinceDate is computed by subtracting months in local time
then converting to UTC, which makes the day boundary depend on the environment
timezone; change the computation order so the subtraction happens in UTC. Update
the expression that builds sinceDate to call utc() before subtract (e.g. use
dayjs().utc().subtract(periodMonths, 'month').startOf('day').toISOString()) and
ensure the revised sinceDate is what you pass into getQueueHistoryRawData,
getWipCycleRawData, and getPRSizeDistribution.

---

Nitpick comments:
In `@batch/helper/timeformat.test.ts`:
- Around line 5-15: 既存のISOテストに加えて旧フォーマット 'YYYY-MM-DD HH:mm:ss' を UTC
として解釈する回帰テストを追加してください: batch/helper/timeformat.test.ts にある timeFormatTz を使い、例えば
timeFormatTz('2026-03-16 02:56:35', 'Asia/Tokyo') が '2026-03-16 11:56:35'
を返すケースと timeFormatTz('2026-03-16 02:56:35', 'US/Pacific') が '2026-03-15
19:56:35' を返すケースを追加して、旧フォーマットを明示的に UTC として扱うことを固定化してください.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 677f3239-7031-4129-aa45-ae20635e4f35

📥 Commits

Reviewing files that changed from the base of the PR and between ac2381b and cb835bc.

📒 Files selected for processing (11)
  • CLAUDE.md
  • app/libs/business-hours.ts
  • app/routes/$orgSlug/+components/pr-block.tsx
  • app/routes/$orgSlug/analysis/feedbacks/_index/index.tsx
  • app/routes/$orgSlug/analysis/reviews/index.tsx
  • app/routes/$orgSlug/workload/$login/index.tsx
  • app/routes/$orgSlug/workload/+components/team-stacks-chart.tsx
  • batch/db/mutations.ts
  • batch/github/review-response.ts
  • batch/helper/timeformat.test.ts
  • batch/helper/timeformat.ts

coji and others added 6 commits March 17, 2026 11:16
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ency

dayjs().subtract().utc() subtracts in local time then converts, making
the day boundary environment-dependent. dayjs.utc().subtract() is correct.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…amps

- batch/bizlogic/cycletime.ts: all diff calculations
- throughput queries (merged, deployed, ongoing): hour diff calculations
- size-badge-popover, feedback-columns: .fromNow() display
- data-management: refreshRequestedAt display
- repository-item: pushedAt display
- Update all cycletime test fixtures to ISO 8601 format

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Grep-based CI check that detects dayjs(variable) calls that should
use dayjs.utc(). Legitimate local-date usages (Date objects, formatted
strings) are allowlisted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract diffInDays(start, end, businessDaysOnly) to business-hours.ts
- Use it in throughput merged/deployed/ongoing queries (removes duplication)
- Change new Date().toISOString() to dayjs.utc().toISOString() in ongoing
- Remove CI dayjs grep check (CLAUDE.md conventions + code review is enough)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
batch/github/review-response.test.ts (1)

50-56: 固定日付によるテストは意図が伝わりにくい可能性があります。

2025-01-01という固定日付では「90日より古い」というテストの意図がコードから読み取りにくいです。相対日付を使用すると、テストの意図が明確になり、将来の保守性が向上します。

♻️ 相対日付を使用した改善案
+import dayjs from 'dayjs'
+import utc from 'dayjs/plugin/utc'
+
+dayjs.extend(utc)
+
 // ...
 
   it('filters out comments older than 90 days', () => {
+    const oldDate = dayjs.utc().subtract(91, 'days').toISOString()
     const result = analyzeReviewResponse([
-      comment('alice', '2025-01-01T10:00:00Z'),
-      comment('bob', '2025-01-01T12:00:00Z'),
+      comment('alice', oldDate),
+      comment('bob', dayjs.utc(oldDate).add(2, 'hours').toISOString()),
     ])
     expect(result).toEqual([])
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@batch/github/review-response.test.ts` around lines 50 - 56, The test using a
fixed date makes the "older than 90 days" intent unclear; update the test around
analyzeReviewResponse and the test helper comment(...) to use relative dates
(e.g., compute Date.now() or mock current time and create timestamps like now
minus 91 days and now minus 89 days) so the expectation remains stable over
time; alternatively mock the global Date/Date.now in the test and generate
comment timestamps based on that mocked "now" to clearly express "older than 90
days" vs "within 90 days."
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@batch/github/review-response.test.ts`:
- Around line 50-56: The test using a fixed date makes the "older than 90 days"
intent unclear; update the test around analyzeReviewResponse and the test helper
comment(...) to use relative dates (e.g., compute Date.now() or mock current
time and create timestamps like now minus 91 days and now minus 89 days) so the
expectation remains stable over time; alternatively mock the global
Date/Date.now in the test and generate comment timestamps based on that mocked
"now" to clearly express "older than 90 days" vs "within 90 days."

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1d131d4d-6fb4-48bc-b012-dee0e871cde3

📥 Commits

Reviewing files that changed from the base of the PR and between cb835bc and 9b04750.

📒 Files selected for processing (21)
  • .gitignore
  • CLAUDE.md
  • app/components/size-badge-popover.tsx
  • app/libs/business-hours.ts
  • app/routes/$orgSlug/analysis/feedbacks/_index/+components/feedback-columns.tsx
  • app/routes/$orgSlug/analysis/feedbacks/_index/index.tsx
  • app/routes/$orgSlug/analysis/reviews/index.tsx
  • app/routes/$orgSlug/settings/data-management/index.tsx
  • app/routes/$orgSlug/settings/repositories.add/+components/repository-item.tsx
  • app/routes/$orgSlug/throughput/deployed/+functions/queries.server.ts
  • app/routes/$orgSlug/throughput/merged/+functions/queries.server.ts
  • app/routes/$orgSlug/throughput/ongoing/+functions/queries.server.ts
  • batch/bizlogic/cycletime.ts
  • batch/bizlogic/cycletime_codingTime.test.ts
  • batch/bizlogic/cycletime_deployTime.test.ts
  • batch/bizlogic/cycletime_pickupTime.test.ts
  • batch/bizlogic/cycletime_reviewTime.test.ts
  • batch/bizlogic/cycletime_totalTime.test.ts
  • batch/db/mutations.ts
  • batch/github/review-response.test.ts
  • batch/github/review-response.ts
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/routes/$orgSlug/analysis/feedbacks/_index/index.tsx
  • batch/github/review-response.ts
  • app/routes/$orgSlug/analysis/reviews/index.tsx

Fixed timestamps would eventually expire past the 90-day filter window.
Now uses vi.useFakeTimers() with relative daysAgo() helper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coji coji merged commit 86ff7d8 into main Mar 17, 2026
6 checks passed
@coji coji deleted the fix/timestamp-format-unification branch March 17, 2026 03:33
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.

1 participant