diff --git a/TaskTracker.Api/Pages/Shared/TaskDeadline.cs b/TaskTracker.Api/Pages/Shared/TaskDeadline.cs
new file mode 100644
index 0000000..35e4e83
--- /dev/null
+++ b/TaskTracker.Api/Pages/Shared/TaskDeadline.cs
@@ -0,0 +1,12 @@
+namespace TaskTracker.Api.Pages;
+
+public static class TaskDeadline
+{
+ public static DateTimeOffset? FromLocalInput(DateTime? localValue, TimeZoneInfo timeZone)
+ {
+ if (!localValue.HasValue) return null;
+
+ var unspecified = DateTime.SpecifyKind(localValue.Value, DateTimeKind.Unspecified);
+ return new DateTimeOffset(unspecified, timeZone.GetUtcOffset(unspecified));
+ }
+}
diff --git a/TaskTracker.Tests/TaskDeadlineConversionTests.cs b/TaskTracker.Tests/TaskDeadlineConversionTests.cs
new file mode 100644
index 0000000..8965d46
--- /dev/null
+++ b/TaskTracker.Tests/TaskDeadlineConversionTests.cs
@@ -0,0 +1,109 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
+using Microsoft.Extensions.DependencyInjection;
+using TaskTracker.Api.Pages;
+using TaskTracker.Core.Entities;
+using TaskTracker.Core.Enums;
+using TaskTracker.Core.Interfaces;
+using Xunit;
+
+namespace TaskTracker.Tests;
+
+public class TaskDeadlineConversionTests
+{
+ [Fact]
+ public void FromLocalInput_uses_the_supplied_time_zone_offset_for_an_unspecified_value()
+ {
+ var timeZone = TimeZoneInfo.CreateCustomTimeZone("TestZone", TimeSpan.FromHours(-4), "Test", "Test");
+ var localValue = new DateTime(2026, 8, 14, 11, 30, 0, DateTimeKind.Utc);
+
+ var dueAt = TaskDeadline.FromLocalInput(localValue, timeZone);
+
+ Assert.Equal(new DateTimeOffset(2026, 8, 14, 11, 30, 0, TimeSpan.FromHours(-4)), dueAt);
+ }
+
+ [Fact]
+ public void FromLocalInput_returns_null_for_a_blank_local_deadline()
+ {
+ var timeZone = TimeZoneInfo.CreateCustomTimeZone("TestZone", TimeSpan.FromHours(2), "Test", "Test");
+
+ var dueAt = TaskDeadline.FromLocalInput(null, timeZone);
+
+ Assert.Null(dueAt);
+ }
+
+ [Fact]
+ public async Task Board_create_assigns_due_at_from_local_deadline()
+ {
+ var repository = new InMemoryTaskRepository();
+ var board = CreateBoard(repository);
+ var localDeadline = new DateTime(2026, 8, 14, 11, 30, 0);
+
+ await board.OnPostCreateTaskAsync("deadline", null, localDeadline);
+
+ var created = Assert.Single(repository.Created);
+ var expected = TaskDeadline.FromLocalInput(localDeadline, TimeZoneInfo.Local);
+ Assert.Equal(expected, created.DueAt);
+ }
+
+ [Fact]
+ public async Task Board_update_explicit_clear_takes_precedence_over_a_supplied_deadline()
+ {
+ var existing = new WorkTask
+ {
+ Id = 1,
+ Title = "existing",
+ Status = WorkTaskStatus.Pending,
+ DueAt = new DateTimeOffset(2026, 8, 14, 11, 30, 0, TimeSpan.FromHours(-4)),
+ };
+ var repository = new InMemoryTaskRepository(existing);
+ var board = CreateBoard(repository);
+
+ var result = await board.OnPutUpdateTaskAsync(1, null, null, null, null, new DateTime(2026, 8, 15, 9, 0, 0), true);
+
+ Assert.IsType
(result);
+ Assert.Null(existing.DueAt);
+ }
+
+ private static BoardModel CreateBoard(ITaskRepository repository)
+ {
+ var board = new BoardModel(repository);
+ var services = new ServiceCollection()
+ .AddLogging()
+ .AddMvcCore()
+ .AddViews()
+ .Services
+ .BuildServiceProvider();
+ var httpContext = new DefaultHttpContext { RequestServices = services };
+ var provider = services.GetRequiredService();
+ board.PageContext = new PageContext
+ {
+ HttpContext = httpContext,
+ ViewData = new ViewDataDictionary(provider, new ModelStateDictionary()),
+ };
+ return board;
+ }
+
+ private sealed class InMemoryTaskRepository(params WorkTask[] tasks) : ITaskRepository
+ {
+ private readonly List _tasks = [.. tasks];
+ public List Created { get; } = [];
+
+ public Task> GetAllAsync(WorkTaskStatus? status = null, int? parentId = null, bool includeSubTasks = false) =>
+ Task.FromResult(_tasks);
+ public Task GetByIdAsync(int id) => Task.FromResult(_tasks.SingleOrDefault(task => task.Id == id));
+ public Task GetActiveTaskAsync() => Task.FromResult(_tasks.SingleOrDefault(task => task.Status == WorkTaskStatus.Active));
+ public Task> GetSubTasksAsync(int parentId) => Task.FromResult(_tasks.Where(task => task.ParentTaskId == parentId).ToList());
+ public Task CreateAsync(WorkTask task)
+ {
+ Created.Add(task);
+ _tasks.Add(task);
+ return Task.FromResult(task);
+ }
+ public Task UpdateAsync(WorkTask task) => Task.CompletedTask;
+ public Task DeleteAsync(int id) => Task.CompletedTask;
+ }
+}