Fix DateTime/DateTimeOffset mismatch on AutomationProposal.ExpiresAt (#1191)#1201
Fix DateTime/DateTimeOffset mismatch on AutomationProposal.ExpiresAt (#1191)#1201Chris0Jeky wants to merge 2 commits into
Conversation
Add EF Core value converters to normalize DateTime properties (ExpiresAt, DecidedAt, AppliedAt) to DateTimeKind.Utc on materialization from SQLite. SQLite stores DateTime as TEXT without timezone info, and EF Core reads these as DateTimeKind.Unspecified. This caused incorrect comparisons with DateTime.UtcNow because Unspecified != Utc in .NET DateTime comparison semantics, leading to wrong expiry behavior. The value converter approach is the safest fix: no schema migration needed, no entity type changes, no LINQ query translation issues, and all 177 related tests pass.
There was a problem hiding this comment.
Code Review
This pull request introduces UTC DateTime converters for specific properties in the AutomationProposal entity configuration to resolve timezone issues when materializing dates from SQLite. The reviewer suggests a more robust, global approach using EF Core's Pre-convention Model Configuration to automatically apply UTC normalization to all DateTime properties across the application, or at least extending the local converters to other DateTime properties like CreatedAt and UpdatedAt.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| var utcConverter = new ValueConverter<DateTime, DateTime>( | ||
| v => v, | ||
| v => DateTime.SpecifyKind(v, DateTimeKind.Utc)); | ||
| var nullableUtcConverter = new ValueConverter<DateTime?, DateTime?>( | ||
| v => v, | ||
| v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v); |
There was a problem hiding this comment.
Architectural Improvement: Global UTC DateTime Conversion
While applying these converters to ExpiresAt, DecidedAt, and AppliedAt fixes the immediate issue for these specific properties, other DateTime properties on AutomationProposal (such as CreatedAt and UpdatedAt) and other entities in the system will still suffer from the same DateTimeKind.Unspecified issue when materialized from SQLite.
To solve this robustly and globally, you can use EF Core's Pre-convention Model Configuration to automatically apply UTC normalization to all DateTime and DateTime? properties across the entire application.
Recommended Solution (Global)
- Define the converters in the infrastructure layer:
public class UtcDateTimeConverter : ValueConverter<DateTime, DateTime>
{
public UtcDateTimeConverter() : base(
v => v,
v => DateTime.SpecifyKind(v, DateTimeKind.Utc))
{
}
}
public class NullableUtcDateTimeConverter : ValueConverter<DateTime?, DateTime?>
{
public NullableUtcDateTimeConverter() : base(
v => v,
v => v.HasValue ? DateTime.SpecifyKind(v.Value, DateTimeKind.Utc) : v)
{
}
}- Override
ConfigureConventionsinTaskdeckDbContext.cs:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<DateTime>()
.HaveConversion<UtcDateTimeConverter>();
configurationBuilder.Properties<DateTime?>()
.HaveConversion<NullableUtcDateTimeConverter>();
}This completely eliminates the need for manual property-by-property configuration and ensures that CreatedAt, UpdatedAt, and all other DateTime fields across all entities are always materialized as UTC.
Alternative Solution (Local to this configuration)
If you prefer to keep this change localized to this configuration for now, please ensure that CreatedAt and UpdatedAt also use the utcConverter:
builder.Property(ap => ap.CreatedAt)
.IsRequired()
.HasConversion(utcConverter);
builder.Property(ap => ap.UpdatedAt)
.IsRequired()
.IsConcurrencyToken()
.HasConversion(utcConverter);There was a problem hiding this comment.
Adopted the global ConfigureConventions approach in commit 8d16202. The per-entity converters have been removed and replaced with a convention-based solution in TaskdeckDbContext that covers all DateTime/DateTime? properties across every entity. Note that CreatedAt/UpdatedAt are DateTimeOffset (from the base Entity class), not DateTime, so they already materialize correctly.
Self-Review FindingsChange SummarySingle file changed: ReviewLOW: Other entities have the same DateTime/SQLite pattern (out of scope) Verification
ConclusionThe fix is minimal, correct, and fully verified. The value converter approach is the standard pattern for SQLite + EF Core DateTime normalization. No findings require action in this PR. |
Move the DateTime UTC normalization from per-entity converters in AutomationProposalConfiguration to a global ConfigureConventions override in TaskdeckDbContext. This ensures all DateTime and DateTime? properties across every entity are materialized with DateTimeKind.Utc, preventing the same SQLite/Unspecified kind bug in ArchiveItem, CommandRun, and any future entities. Addresses Gemini review feedback on PR #1201.
Fix Evidence (addressing Gemini review feedback)Finding: Gemini suggested promoting the UTC DateTime converters from per-entity to a global Action: Implemented in commit 8d16202. The converters are now global via Note on CreatedAt/UpdatedAt: These are Verification after fix:
|
Summary
AutomationProposalConfigurationthat normalizeDateTimeproperties (ExpiresAt,DecidedAt,AppliedAt) toDateTimeKind.Utcon materialization from SQLiteDateTimeas TEXT without timezone info; EF Core reads these asDateTimeKind.Unspecified, causing incorrect comparisons withDateTime.UtcNowand wrong expiry behaviorDateTimeOffsetbecause EF Core SQLite cannot translateDateTimeOffsetcomparisons in LINQ expressions -- the converter is zero-blast-radius (no migration, no entity type changes, no test changes)Closes #1191
Test plan
dotnet build backend/Taskdeck.sln -c Release-- 0 errors)