Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ internal static PersistedScheduledCommand ToPersistable(this ScheduledCommand in
return result;
}

private static DateTime EnsureUtc(DateTime dateTime)
{
if (dateTime.Kind == DateTimeKind.Local)
return dateTime.ToUniversalTime();
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

EnsureUtc currently calls DateTime.SpecifyKind(dateTime, DateTimeKind.Utc) for inputs that are already DateTimeKind.Utc. Returning the original value when dateTime.Kind == DateTimeKind.Utc would make the intent clearer and avoid an unnecessary copy.

Suggested change
return dateTime.ToUniversalTime();
return dateTime.ToUniversalTime();
if (dateTime.Kind == DateTimeKind.Utc)
return dateTime;

Copilot uses AI. Check for mistakes.
return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
}
Comment on lines +140 to +147
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

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

The new EnsureUtc behavior (converting DateTimeKind.Local via ToUniversalTime) isn’t covered by an automated regression test, so it could regress back to SpecifyKind without being detected—especially since most CI environments run with TimeZoneInfo.Local set to UTC. Consider adding a test (e.g., in the EF/PostgreSQL persistence tests) that forces a non-UTC local zone for the process (on Linux, setting TZ + calling TimeZoneInfo.ClearCachedData()), then verifies that a persisted SleepUntil coming back as Kind=Local is converted to the expected UTC instant.

Copilot uses AI. Check for mistakes.

internal static WorkflowInstance ToWorkflowInstance(this PersistedWorkflow instance)
{
WorkflowInstance result = new WorkflowInstance();
Expand All @@ -148,9 +155,9 @@ internal static WorkflowInstance ToWorkflowInstance(this PersistedWorkflow insta
result.Version = instance.Version;
result.WorkflowDefinitionId = instance.WorkflowDefinitionId;
result.Status = instance.Status;
result.CreateTime = DateTime.SpecifyKind(instance.CreateTime, DateTimeKind.Utc);
result.CreateTime = EnsureUtc(instance.CreateTime);
if (instance.CompleteTime.HasValue)
result.CompleteTime = DateTime.SpecifyKind(instance.CompleteTime.Value, DateTimeKind.Utc);
result.CompleteTime = EnsureUtc(instance.CompleteTime.Value);

result.ExecutionPointers = new ExecutionPointerCollection(instance.ExecutionPointers.Count + 8);

Expand All @@ -163,15 +170,15 @@ internal static WorkflowInstance ToWorkflowInstance(this PersistedWorkflow insta
pointer.Active = ep.Active;

if (ep.SleepUntil.HasValue)
pointer.SleepUntil = DateTime.SpecifyKind(ep.SleepUntil.Value, DateTimeKind.Utc);
pointer.SleepUntil = EnsureUtc(ep.SleepUntil.Value);

pointer.PersistenceData = JsonConvert.DeserializeObject(ep.PersistenceData ?? string.Empty, SerializerSettings);

if (ep.StartTime.HasValue)
pointer.StartTime = DateTime.SpecifyKind(ep.StartTime.Value, DateTimeKind.Utc);
pointer.StartTime = EnsureUtc(ep.StartTime.Value);

if (ep.EndTime.HasValue)
pointer.EndTime = DateTime.SpecifyKind(ep.EndTime.Value, DateTimeKind.Utc);
pointer.EndTime = EnsureUtc(ep.EndTime.Value);

pointer.StepName = ep.StepName;

Expand Down Expand Up @@ -212,7 +219,7 @@ internal static EventSubscription ToEventSubscription(this PersistedSubscription
result.StepId = instance.StepId;
result.ExecutionPointerId = instance.ExecutionPointerId;
result.WorkflowId = instance.WorkflowId;
result.SubscribeAsOf = DateTime.SpecifyKind(instance.SubscribeAsOf, DateTimeKind.Utc);
result.SubscribeAsOf = EnsureUtc(instance.SubscribeAsOf);
result.SubscriptionData = JsonConvert.DeserializeObject(instance.SubscriptionData, SerializerSettings);
result.ExternalToken = instance.ExternalToken;
result.ExternalTokenExpiry = instance.ExternalTokenExpiry;
Expand All @@ -227,7 +234,7 @@ internal static Event ToEvent(this PersistedEvent instance)
result.Id = instance.EventId.ToString();
result.EventKey = instance.EventKey;
result.EventName = instance.EventName;
result.EventTime = DateTime.SpecifyKind(instance.EventTime, DateTimeKind.Utc);
result.EventTime = EnsureUtc(instance.EventTime);
result.IsProcessed = instance.IsProcessed;
result.EventData = JsonConvert.DeserializeObject(instance.EventData, SerializerSettings);

Expand Down