Skip to content

fix: standardize timestamps to DateTimeOffset UTC (fixes #386)#751

Merged
PureWeen merged 7 commits intomainfrom
fix/issue-386-utc-timestamps
Apr 23, 2026
Merged

fix: standardize timestamps to DateTimeOffset UTC (fixes #386)#751
PureWeen merged 7 commits intomainfrom
fix/issue-386-utc-timestamps

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

Summary

Standardizes all timestamp fields to DateTimeOffset with DateTimeOffset.UtcNow, eliminating the mixed DateTime.Now/DateTime.UtcNow convention that caused cross-timezone comparison bugs in orchestration dispatch paths.

Fixes #386

Problem

ChatMessage.Timestamp used DateTime.Now (local time) while PendingOrchestration.StartedAt used DateTime.UtcNow. When comparing these values (e.g., filtering messages after dispatch time), results were wrong by the UTC offset. PR #375 added ToLocalTime() workarounds, but the root cause remained.

Solution

Replace DateTime with DateTimeOffset for all timestamp fields involved in cross-component comparisons. DateTimeOffset carries timezone offset information, making comparisons safe regardless of which timezone the values were created in.

Production Changes (16 files)

Core Models:

  • ChatMessage.Timestamp: DateTimeDateTimeOffset, all 10 factory methods use DateTimeOffset.UtcNow
  • ToolActivity.StartedAt/CompletedAt: DateTimeDateTimeOffset
  • PendingOrchestration.StartedAt: DateTimeDateTimeOffset
  • ReflectionCycle.StartedAt/CompletedAt: DateTime?DateTimeOffset?
  • AgentSessionInfo.CreatedAt: DateTimeDateTimeOffset
  • ActiveSessionEntry.CreatedAt: DateTime?DateTimeOffset?
  • SessionSummary.CreatedAt: DateTimeDateTimeOffset

Service Layer:

  • Removed ToLocalTime() workarounds in CopilotService.Organization.cs dispatch/resume paths
  • Updated all ChatMessage constructor calls across Events.cs, Bridge.cs, Providers.cs
  • ChatDatabase.cs: Boundary conversion (DateTimeOffsetDateTime for SQLite)
  • ExternalSessionScanner.cs and Utilities.cs: DateTimeOffset.TryParse for events.jsonl

UI:

  • ChatMessageItem.razor: Display uses .LocalDateTime.ToShortTimeString()

Test Changes (13 files)

  • Updated all test DateTime.NowDateTimeOffset.UtcNow
  • 6 new behavioral tests for UTC standardization
  • Updated structural tests for new patterns

Not Changed (by design)

  • AgentSessionInfo.LastUpdatedAt/ProcessingStartedAt — use Interlocked ticks pattern for thread safety, not involved in cross-timezone bugs
  • ScheduledTask timestamps — separate concern, local time is intentional
  • MauiProgram crash logging — display-only

Test Results

  • 3543 passed, 0 failed, 0 skipped

Backward Compatibility

Old persisted sessions with DateTime strings deserialize correctly into DateTimeOffset via System.Text.Json (ISO 8601 without offset assumes local time, with 'Z' assumes UTC).

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • 192.0.2.1

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "192.0.2.1"

See Network Configuration for more information.

Generated by Agent Fix for issue #386 · ● 74.6M ·

Replace DateTime.Now/DateTime.UtcNow with DateTimeOffset.UtcNow across
all timestamp fields in ChatMessage, ToolActivity, PendingOrchestration,
ReflectionCycle, AgentSessionInfo, ActiveSessionEntry, and SessionSummary.

Key changes:
- ChatMessage.Timestamp: DateTime → DateTimeOffset
- All 10 ChatMessage factory methods use DateTimeOffset.UtcNow
- ToolActivity.StartedAt/CompletedAt: DateTime → DateTimeOffset
- PendingOrchestration.StartedAt: DateTime → DateTimeOffset
- ReflectionCycle timestamps: DateTime? → DateTimeOffset?
- AgentSessionInfo.CreatedAt: DateTime → DateTimeOffset
- Remove ToLocalTime() workarounds in dispatch/resume paths
- UI display uses .LocalDateTime.ToShortTimeString()
- ChatDatabase boundary: DateTimeOffset ↔ DateTime for SQLite
- 6 new behavioral tests for UTC standardization

Not changed (by design):
- AgentSessionInfo.LastUpdatedAt/ProcessingStartedAt (Interlocked ticks)
- ScheduledTask timestamps (separate concern, local time intentional)

Fixes #386

Co-authored-by: copilot-agentic-workflow[bot] <224017+copilot-agentic-workflow[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen PureWeen marked this pull request as ready for review April 23, 2026 19:37
@github-actions
Copy link
Copy Markdown
Contributor Author

Cross-Platform Verification — PR #751

Build Results

Platform Status
Tests (macOS) ✅ success
Mac Catalyst build ✅ success
Windows build ✅ success

✅ All platforms verified


Triggered by: verify-build run

@github-actions
Copy link
Copy Markdown
Contributor Author

🧪 Integration Test Report — PR #751

Platform Build Launch DevFlow Smoke
Linux/GTK (xvfb)
Mac Catalyst
Windows ⚠️ ⚠️

✅ All platforms passed

View full run

@github-actions
Copy link
Copy Markdown
Contributor Author

Design-Level Findings (outside diff hunks)

🟡 MODERATE — Incomplete migration: DateTime.UtcNowDateTimeOffset.UtcNow in CopilotService.Organization.cs (Flagged by 2/3 reviewers)

Two PendingOrchestration object-initializer sites in the dispatch paths still assign StartedAt = DateTime.UtcNow instead of DateTimeOffset.UtcNow. These compile silently due to the implicit DateTimeDateTimeOffset conversion, and since DateTime.UtcNow has Kind=Utc, the resulting value is numerically identical to DateTimeOffset.UtcNow. No functional bug today — but the PR's stated goal is to eliminate mixed DateTime/DateTimeOffset usage, and leaving these creates precedent for the convention to drift back. Recommend changing both to DateTimeOffset.UtcNow for consistency.


🟢 MINOR — Mixed DateTime/DateTimeOffset arithmetic in CopilotService.Organization.cs (Flagged by 2/3 reviewers)

Expression DateTime.UtcNow - pending.StartedAt (where StartedAt is now DateTimeOffset) compiles via implicit promotion. The result is correct, but for auditability, prefer DateTimeOffset.UtcNow - pending.StartedAt to make the types explicit.

Generated by Expert Code Review (auto) for issue #751 · ● 13.8M ·

Copy link
Copy Markdown
Contributor Author

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Review Summary

Good direction — standardizing on DateTimeOffset with UTC eliminates the mixed-timezone comparison bugs and the ToLocalTime() workarounds. The scope is well-considered (correctly excluding Interlocked ticks patterns and display-only paths).

Findings

Severity File Issue
🔴 ChatDatabase.cs:52 new DateTimeOffset(Timestamp, TimeSpan.Zero) misinterprets pre-existing local-time ticks as UTC — old message timestamps shift by the user's UTC offset on read. Also affects the LoadBestHistoryAsync DB-vs-events comparison.
🟡 ExternalSessionScanner.cs:391, Utilities.cs:661 DateTimeOffset.TryParse out parameter clobbers the UtcNow fallback on parse failure (sets to 0001-01-01). Pre-existing bug carried forward — worth fixing since this code is touched.
🟡 Organization.cs:2041,4119 Two SavePendingOrchestration calls still use DateTime.UtcNow for the StartedAt field. Works via implicit conversion but inconsistent with the PR's goal.
🟢 Organization.cs:3232,3263 DateTime.UtcNow - pending.StartedAt mixes types. Works but should be DateTimeOffset.UtcNow.

What looks good

  • Factory methods in ChatMessage consistently use DateTimeOffset.UtcNow
  • ChatMessageItem.razor correctly uses .LocalDateTime for display ✅
  • Thread-safe Interlocked patterns correctly left as DateTime
  • JSON backward compat for active-sessions.json is fine (System.Text.Json handles ISO 8601 without offset by assuming local) ✅
  • default(DateTimeOffset) comparison in ClearPendingOrchestrationAndResetState is semantically equivalent to the old default(DateTime)

The ChatDatabase backward compatibility issue is the main concern — recommend addressing it before merge.

Generated by Expert Code Review (auto) for issue #751 · ● 13.8M

- ChatDatabase: use local offset for old data instead of assuming UTC
  (prevents timestamp shift for pre-migration local-time data)
- ExternalSessionScanner/Utilities: fix TryParse clobbering fallback
  (parse failure no longer overwrites UtcNow default with epoch)
- Organization: use DateTimeOffset.UtcNow for PendingOrchestration
  StartedAt (consistent with type, not implicit DateTime conversion)
- Organization: use DateTimeOffset.UtcNow for elapsed time math
  (avoid mixed-type subtraction)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

🧪 Integration Test Report — PR #751

Platform Build Launch DevFlow Smoke
Linux/GTK (xvfb)
Mac Catalyst
Windows ⚠️ ⚠️

✅ All platforms passed

View full run

@github-actions
Copy link
Copy Markdown
Contributor Author

Cross-Platform Verification — PR #751

Build Results

Platform Status
Tests (macOS) ✅ success
Mac Catalyst build ✅ success
Windows build ✅ success

✅ All platforms verified

Previous Review History

Found 3 automated review(s) on this PR. Build verification validates that all review-driven fixes compile and pass tests across platforms.


Triggered by: verify-build run

@github-actions

This comment has been minimized.

….UtcNow sites

- CopilotService.Utilities.cs: DateTime.MinValue → DateTimeOffset.MinValue
  (prevents ArgumentOutOfRangeException in UTC+ timezones)
- CopilotService.cs: CreatedAt = DateTime.UtcNow → DateTimeOffset.UtcNow
  (2 sites in session creation paths)

Addresses second expert review findings for #386.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

Cross-Platform Verification — PR #751

Build Results

Platform Status
Tests (macOS) ✅ success
Mac Catalyst build ✅ success
Windows build ✅ success

✅ All platforms verified

Previous Review History

Found 3 automated review(s) on this PR. Build verification validates that all review-driven fixes compile and pass tests across platforms.


Triggered by: verify-build run

@github-actions
Copy link
Copy Markdown
Contributor Author

🧪 Integration Test Report — PR #751

Platform Build Launch DevFlow Smoke
Linux/GTK (xvfb)
Mac Catalyst
Windows ⚠️ ⚠️

✅ All platforms passed

View full run

@github-actions

This comment has been minimized.

github-actions Bot and others added 2 commits April 23, 2026 16:13
)

feat: add quota usage display and rate limit warnings

- Color-coded quota indicator pill in dashboard header (green/yellow/red)
- Warning banner when quota drops below 20%
- Enhanced /usage command with reset countdown
- 22 new unit tests

Fixes #691

Generated by agent-fix workflow. Passed integration tests + expert code review.
DateTime.MaxValue (Kind=Unspecified) → DateTimeOffset conversion overflows
in UTC- timezones. Use DateTimeOffset.MaxValue for all 3 sort branches.

Addresses third expert review finding for #386.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor Author

Cross-Platform Verification — PR #751

Build Results

Platform Status
Tests (macOS) ✅ success
Mac Catalyst build ✅ success
Windows build ✅ success

✅ All platforms verified

Previous Review History

Found 3 automated review(s) on this PR. Build verification validates that all review-driven fixes compile and pass tests across platforms.


Triggered by: verify-build run

@github-actions
Copy link
Copy Markdown
Contributor Author

🧪 Integration Test Report — PR #751

Platform Build Launch DevFlow Smoke
Linux/GTK (xvfb)
Mac Catalyst
Windows ⚠️ ⚠️

✅ All platforms passed

View full run

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor Author

Finding E: Quota UI negative values when entitlement data is missing — DISAGREE (not a real issue)

Verdict: Theoretical concern with negligible real-world risk.

Analysis

The reviewer correctly identified that the parser defaults entitlementRequests to -1 when the JSON property is absent (CopilotService.Events.cs:1214), and that three UI locations perform unguarded subtraction (EntitlementRequests - UsedRequests):

  1. Dashboard header tooltip — via QuotaDisplayHelper.FormatCompactSummary() (line 57 of QuotaDisplayHelper.cs)
  2. Dashboard warning banner — inline at Dashboard.razor:332
  3. ExpandedSessionView — inline at ExpandedSessionView.razor:125

However, the premise — a limited (non-unlimited) plan missing its entitlementRequests field — does not occur in practice. The Copilot API's premium_interactions quota snapshot always includes entitlementRequests for finite plans; it is a core field, not an optional extension. isUnlimitedEntitlement and entitlementRequests are co-dependent in the API contract.

A defensive guard (e.g., hiding the quota display when EntitlementRequests < 0) would be reasonable hardening, but this is not a bug that users would encounter.

Generated by Expert Code Review · ● 35.7M ·

Last two DateTime.Now sites in migrated ToolActivity properties.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen PureWeen merged commit bd880f7 into main Apr 23, 2026
@PureWeen PureWeen deleted the fix/issue-386-utc-timestamps branch April 23, 2026 21:44
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.

Standardize timestamps to UTC across ChatMessage and dispatch paths

1 participant