From 3ad9e48fcd62d8cdb70bbdc0c66c5ad34c551a03 Mon Sep 17 00:00:00 2001 From: Claudio Schaad Date: Wed, 3 Sep 2025 21:12:10 +0200 Subject: [PATCH] Change strings to date time offset --- Services/ITransportService.cs | 4 +-- Services/IcsExportService.cs | 9 +++--- Services/PdfExportService.cs | 9 +++--- Services/TransportApiService.cs | 10 ++++--- Services/TransportConnectionCalculator.cs | 27 +++++++---------- Services/TransportService.cs | 2 +- Shared/ShiftWithTransport.cs | 6 ++-- Shared/TransportModels.cs | 8 ++--- .../TransportApiServiceTests.cs | 21 +++++++++----- .../TransportConnectionCalculatorTests.cs | 29 +++++++++++-------- .../TransportServiceTests.cs | 25 +++++++++------- 11 files changed, 79 insertions(+), 71 deletions(-) diff --git a/Services/ITransportService.cs b/Services/ITransportService.cs index ee3d593..0d84930 100644 --- a/Services/ITransportService.cs +++ b/Services/ITransportService.cs @@ -4,11 +4,11 @@ namespace ShiftScheduler.Services { public interface ITransportService { - Task GetConnectionAsync(DateTime shiftStartTime); + Task GetConnectionAsync(DateTimeOffset shiftStartTime); } public interface ITransportApiService { - Task GetConnectionAsync(DateTime shiftStartTime); + Task GetConnectionAsync(DateTimeOffset shiftStartTime); } } \ No newline at end of file diff --git a/Services/IcsExportService.cs b/Services/IcsExportService.cs index 21cab7d..8ab3ab4 100644 --- a/Services/IcsExportService.cs +++ b/Services/IcsExportService.cs @@ -26,7 +26,7 @@ namespace ShiftScheduler.Services var summary = $"{shift.Name} (Morning)"; var description = ""; - if (shiftWithTransport.MorningTransport != null && !string.IsNullOrEmpty(shiftWithTransport.MorningTransport.DepartureTime)) + if (shiftWithTransport.MorningTransport != null) { var transportSummary = FormatTransportInfo(shiftWithTransport.MorningTransport); description = $"Transport: {transportSummary}"; @@ -47,7 +47,7 @@ namespace ShiftScheduler.Services var summary = $"{shift.Name} (Afternoon)"; var description = ""; - if (shiftWithTransport.AfternoonTransport != null && !string.IsNullOrEmpty(shiftWithTransport.AfternoonTransport.DepartureTime)) + if (shiftWithTransport.AfternoonTransport != null) { var transportSummary = FormatTransportInfo(shiftWithTransport.AfternoonTransport); description = $"Transport: {transportSummary}"; @@ -68,9 +68,8 @@ namespace ShiftScheduler.Services private string FormatTransportInfo(TransportConnection transport) { - var departure = DateTime.TryParse(transport.DepartureTime, out var dep) ? dep.ToString("HH:mm") : transport.DepartureTime; - var arrival = DateTime.TryParse(transport.ArrivalTime, out var arr) ? arr.ToString("HH:mm") : transport.ArrivalTime; - + var departure =transport.DepartureTime.ToString("HH:mm"); + var arrival = transport.ArrivalTime.ToString("HH:mm"); return $"{departure} → {arrival}"; } } diff --git a/Services/PdfExportService.cs b/Services/PdfExportService.cs index c0ab460..ae81441 100644 --- a/Services/PdfExportService.cs +++ b/Services/PdfExportService.cs @@ -176,13 +176,13 @@ namespace ShiftScheduler.Services var transportLines = new List(); - if (shiftWithTransport.MorningTransport != null && !string.IsNullOrEmpty(shiftWithTransport.MorningTransport.DepartureTime)) + if (shiftWithTransport.MorningTransport != null) { var morningInfo = FormatTransportConnection(shiftWithTransport.MorningTransport); transportLines.Add(morningInfo); } - if (shiftWithTransport.AfternoonTransport != null && !string.IsNullOrEmpty(shiftWithTransport.AfternoonTransport.DepartureTime)) + if (shiftWithTransport.AfternoonTransport != null) { var afternoonInfo = FormatTransportConnection(shiftWithTransport.AfternoonTransport); transportLines.Add(afternoonInfo); @@ -193,9 +193,8 @@ namespace ShiftScheduler.Services private static string FormatTransportConnection(TransportConnection transport) { - var departure = DateTime.TryParse(transport.DepartureTime, out var dep) ? dep.ToString("HH:mm") : transport.DepartureTime; - var arrival = DateTime.TryParse(transport.ArrivalTime, out var arr) ? arr.ToString("HH:mm") : transport.ArrivalTime; - + var departure = transport.DepartureTime.ToString("HH:mm"); + var arrival = transport.ArrivalTime.ToString("HH:mm"); return $"🚂 {departure}→{arrival}"; } diff --git a/Services/TransportApiService.cs b/Services/TransportApiService.cs index b0422e5..c06e959 100644 --- a/Services/TransportApiService.cs +++ b/Services/TransportApiService.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Text.Json; using Microsoft.Extensions.Logging; using ShiftScheduler.Shared; @@ -6,7 +7,7 @@ namespace ShiftScheduler.Services { public class TransportApiService(HttpClient httpClient, IConfigurationService configurationService, ILogger logger) : ITransportApiService { - public async Task GetConnectionAsync(DateTime shiftStartTime) + public async Task GetConnectionAsync(DateTimeOffset shiftStartTime) { var config = configurationService.GetTransportConfiguration(); @@ -45,11 +46,12 @@ namespace ShiftScheduler.Services if (apiConnection == null) return new TransportConnection(); + var formats = new[] { @"dd'd'hh\:mm\:ss", @"d'd'hh\:mm\:ss" }; return new TransportConnection { - DepartureTime = apiConnection.From?.Departure ?? string.Empty, - ArrivalTime = apiConnection.To?.Arrival ?? string.Empty, - Duration = apiConnection.Duration, + DepartureTime = DateTimeOffset.Parse(apiConnection.From!.Departure), + ArrivalTime = DateTimeOffset.Parse(apiConnection.To!.Arrival), + Duration = TimeSpan.ParseExact(apiConnection.Duration, formats, CultureInfo.InvariantCulture), Platform = apiConnection.From?.Platform ?? string.Empty, }; } diff --git a/Services/TransportConnectionCalculator.cs b/Services/TransportConnectionCalculator.cs index 2b2ba27..c8bebf2 100644 --- a/Services/TransportConnectionCalculator.cs +++ b/Services/TransportConnectionCalculator.cs @@ -1,11 +1,10 @@ -using System.Globalization; using Microsoft.Extensions.Logging; using ShiftScheduler.Shared; namespace ShiftScheduler.Services; public record ConnectionPickArgument( - DateTime ShiftStartTime, + DateTimeOffset ShiftStartTime, int SafetyBufferMinutes, int MaxEarlyArrivalMinutes, int MaxLateArrivalMinutes) @@ -35,16 +34,13 @@ public static class TransportConnectionCalculator foreach (var connection in connections) { - if (DateTime.TryParse(connection.ArrivalTime, out var arrivalTime)) + if (connection.ArrivalTime <= latestArrivalTime) { - if (arrivalTime <= latestArrivalTime) - { - validConnections.Add(connection); - } - else if (arrivalTime <= latestAcceptableTime) - { - lateValidConnections.Add(connection); - } + validConnections.Add(connection); + } + else if (connection.ArrivalTime <= latestAcceptableTime) + { + lateValidConnections.Add(connection); } } @@ -52,18 +48,17 @@ public static class TransportConnectionCalculator if (validConnections.Count > 0) { var sortedValid = validConnections - .OrderBy(c => DateTime.Parse(c.ArrivalTime ?? "00:00")) + .OrderBy(c => c.ArrivalTime) .ToList(); var bestValidConnection = sortedValid.Last(); - var bestValidArrivalTime = DateTime.Parse(bestValidConnection.ArrivalTime!); // Check if the best valid connection arrives too early (more than maxEarlyArrivalMinutes before shift) - if (bestValidArrivalTime < earliestAcceptableTime && lateValidConnections.Count > 0) + if (bestValidConnection.ArrivalTime < earliestAcceptableTime && lateValidConnections.Count > 0) { // Return the earliest connection that arrives after latest arrival time but within acceptable range var sortedLateValid = lateValidConnections - .OrderBy(c => DateTime.Parse(c.ArrivalTime ?? "23:59")) + .OrderBy(c => c.ArrivalTime) .ToList(); return sortedLateValid.First(); @@ -76,7 +71,7 @@ public static class TransportConnectionCalculator if (lateValidConnections.Count > 0) { var sortedLateValid = lateValidConnections - .OrderBy(c => DateTime.Parse(c.ArrivalTime ?? "23:59")) + .OrderBy(c => c.ArrivalTime) .ToList(); return sortedLateValid.First(); diff --git a/Services/TransportService.cs b/Services/TransportService.cs index 91dd0f0..5605014 100644 --- a/Services/TransportService.cs +++ b/Services/TransportService.cs @@ -5,7 +5,7 @@ namespace ShiftScheduler.Services { public class TransportService(ITransportApiService transportService, IConfigurationService configurationService, IMemoryCache cache) : ITransportService { - public async Task GetConnectionAsync(DateTime shiftStartTime) + public async Task GetConnectionAsync(DateTimeOffset shiftStartTime) { var config = configurationService.GetTransportConfiguration(); var searchDate = shiftStartTime.ToString("yyyy-MM-dd"); diff --git a/Shared/ShiftWithTransport.cs b/Shared/ShiftWithTransport.cs index e7ab5d0..1cf620c 100644 --- a/Shared/ShiftWithTransport.cs +++ b/Shared/ShiftWithTransport.cs @@ -19,10 +19,8 @@ namespace ShiftScheduler.Shared private string FormatTransportSummary(TransportConnection connection) { - if (string.IsNullOrEmpty(connection.DepartureTime)) return string.Empty; - - var departure = DateTime.TryParse(connection.DepartureTime, out var dep) ? dep.ToString("HH:mm") : connection.DepartureTime; - var arrival = DateTime.TryParse(connection.ArrivalTime, out var arr) ? arr.ToString("HH:mm") : connection.ArrivalTime; + var departure = connection.DepartureTime.ToString("HH:mm"); + var arrival = connection.ArrivalTime.ToString("HH:mm"); return $"{departure}→{arrival}"; } } diff --git a/Shared/TransportModels.cs b/Shared/TransportModels.cs index 078c17a..d4ccafd 100644 --- a/Shared/TransportModels.cs +++ b/Shared/TransportModels.cs @@ -20,12 +20,12 @@ namespace ShiftScheduler.Shared public TransportConnection(string arrivalTime) { - ArrivalTime = arrivalTime; + ArrivalTime = DateTimeOffset.Parse(arrivalTime); } - public string DepartureTime { get; set; } = string.Empty; - public string ArrivalTime { get; set; } = string.Empty; - public string Duration { get; set; } = string.Empty; + public DateTimeOffset DepartureTime { get; set; } + public DateTimeOffset ArrivalTime { get; set; } + public TimeSpan Duration { get; set; } public string? Platform { get; set; } = string.Empty; public override string ToString() diff --git a/ShiftScheduler.Services.Tests/TransportApiServiceTests.cs b/ShiftScheduler.Services.Tests/TransportApiServiceTests.cs index 29257b5..dbe0a32 100644 --- a/ShiftScheduler.Services.Tests/TransportApiServiceTests.cs +++ b/ShiftScheduler.Services.Tests/TransportApiServiceTests.cs @@ -58,9 +58,9 @@ public class TransportApiServiceTests // Assert result.ShouldNotBeNull(); - result.DepartureTime.ShouldBe("2023-12-15T06:45:00"); - result.ArrivalTime.ShouldBe("2023-12-15T07:30:00"); - result.Duration.ShouldBe("00:45:00"); + result.DepartureTime.ShouldBe(T("2023-12-15T06:45:00")); + result.ArrivalTime.ShouldBe(T("2023-12-15T07:30:00")); + result.Duration.ShouldBe(TimeSpan.Parse("00:45:00")); result.Platform.ShouldBe("5"); } @@ -115,7 +115,7 @@ public class TransportApiServiceTests // Latest acceptable arrival: 8:00 - 30 min = 7:30 // Valid connections: 07:15 and 07:25 (both arrive before 7:30) // Algorithm should return the latest valid: 07:25 - result.ArrivalTime.ShouldBe("2023-12-15T07:25:00"); + result.ArrivalTime.ShouldBe(T("2023-12-15T07:25:00")); } private void SetupHttpMockResponse(HttpStatusCode statusCode, string content) @@ -151,7 +151,7 @@ public class TransportApiServiceTests Arrival = "2023-12-15T07:30:00", Platform = "3" }, - Duration = "00:45:00", + Duration = "00d00:45:00", Sections = new List { new TransportApiSection @@ -202,7 +202,7 @@ public class TransportApiServiceTests Arrival = "2023-12-15T07:15:00", Platform = "3" }, - Duration = "00:45:00" + Duration = "00d00:45:00" }, // Later valid connection - should be selected as best new TransportApiConnection @@ -219,7 +219,7 @@ public class TransportApiServiceTests Arrival = "2023-12-15T07:25:00", Platform = "2" }, - Duration = "00:45:00" + Duration = "00d00:45:00" }, // Too late connection - invalid (arrives after 07:30 which is shift start - 30 min buffer) new TransportApiConnection @@ -236,9 +236,14 @@ public class TransportApiServiceTests Arrival = "2023-12-15T07:35:00", Platform = "1" }, - Duration = "00:45:00" + Duration = "00d00:45:00" } } }; } + + private DateTimeOffset T(string dateTimeString) + { + return DateTimeOffset.Parse(dateTimeString); + } } \ No newline at end of file diff --git a/ShiftScheduler.Services.Tests/TransportConnectionCalculatorTests.cs b/ShiftScheduler.Services.Tests/TransportConnectionCalculatorTests.cs index 2f1e2c7..26724dd 100644 --- a/ShiftScheduler.Services.Tests/TransportConnectionCalculatorTests.cs +++ b/ShiftScheduler.Services.Tests/TransportConnectionCalculatorTests.cs @@ -20,7 +20,7 @@ public class TransportConnectionCalculatorTests new(arrivalTime: "2023-12-15T07:25:00"), // Should be selected (latest valid) new(arrivalTime: "2023-12-15T08:05:00") // Too late }; - var shiftStartTime = new DateTime(2023, 12, 15, 8, 0, 0); + var shiftStartTime = T("2023-12-15T08:00:00"); // Act var result = TransportConnectionCalculator.FindBestConnection( @@ -30,7 +30,7 @@ public class TransportConnectionCalculatorTests // Assert result.ShouldNotBeNull(); - result.ArrivalTime.ShouldBe("2023-12-15T07:25:00"); + result.ArrivalTime.ShouldBe(T("2023-12-15T07:25:00")); } [Fact] @@ -42,7 +42,7 @@ public class TransportConnectionCalculatorTests new(arrivalTime: "2023-12-15T06:45:00"), // Too early (more than 60 min before 8:00) new(arrivalTime: "2023-12-15T08:05:00") // Within acceptable late range (15 min after 8:00) }; - var shiftStartTime = new DateTime(2023, 12, 15, 8, 0, 0); + var shiftStartTime = T("2023-12-15T08:00:00"); // Act var result = TransportConnectionCalculator.FindBestConnection( @@ -52,7 +52,7 @@ public class TransportConnectionCalculatorTests // Assert result.ShouldNotBeNull(); - result.ArrivalTime.ShouldBe("2023-12-15T08:05:00"); + result.ArrivalTime.ShouldBe(T("2023-12-15T08:05:00")); } [Fact] @@ -65,7 +65,7 @@ public class TransportConnectionCalculatorTests new(arrivalTime: "2023-12-15T08:05:00"), // Should be selected (earliest late) new(arrivalTime: "2023-12-15T08:12:00") }; - var shiftStartTime = new DateTime(2023, 12, 15, 8, 0, 0); + var shiftStartTime = T("2023-12-15T08:00:00"); // Act var result = TransportConnectionCalculator.FindBestConnection( @@ -75,7 +75,7 @@ public class TransportConnectionCalculatorTests // Assert result.ShouldNotBeNull(); - result.ArrivalTime.ShouldBe("2023-12-15T08:05:00"); + result.ArrivalTime.ShouldBe(T("2023-12-15T08:05:00")); } [Fact] @@ -88,7 +88,7 @@ public class TransportConnectionCalculatorTests new(arrivalTime: "2023-12-15T08:25:00"), new(arrivalTime: "2023-12-15T08:30:00") }; - var shiftStartTime = new DateTime(2023, 12, 15, 8, 0, 0); + var shiftStartTime = T("2023-12-15T08:00:00"); // Act var result = TransportConnectionCalculator.FindBestConnection( @@ -109,7 +109,7 @@ public class TransportConnectionCalculatorTests new(arrivalTime: "2023-12-15T07:20:00"), // 40 min before shift - acceptable new(arrivalTime: "2023-12-15T08:05:00") // 5 min after shift }; - var shiftStartTime = new DateTime(2023, 12, 15, 8, 0, 0); + var shiftStartTime = T("2023-12-15T08:00:00"); // Act var result = TransportConnectionCalculator.FindBestConnection( @@ -119,14 +119,14 @@ public class TransportConnectionCalculatorTests // Assert result.ShouldNotBeNull(); - result.ArrivalTime.ShouldBe("2023-12-15T07:20:00"); + result.ArrivalTime.ShouldBe(T("2023-12-15T07:20:00")); } [Fact] public void FindBestConnectionEnhanced_WithEmptyConnections_ShouldReturnNull() { // Arrange - var shiftStartTime = new DateTime(2023, 12, 15, 8, 0, 0); + var shiftStartTime = T("2023-12-15T08:00:00"); // Act var result = TransportConnectionCalculator.FindBestConnection( @@ -149,7 +149,7 @@ public class TransportConnectionCalculatorTests new(arrivalTime: "2023-12-15T08:05:00"), // Late but acceptable new(arrivalTime: "2023-12-15T08:20:00") // Too late }; - var shiftStartTime = new DateTime(2023, 12, 15, 8, 0, 0); + var shiftStartTime = T("2023-12-15T08:00:00"); // Act - MaxEarlyArrivalMinutes = 30, so earliest acceptable is 7:30 var result = TransportConnectionCalculator.FindBestConnection( @@ -159,6 +159,11 @@ public class TransportConnectionCalculatorTests // Assert - 7:25 arrives at 7:25, which is 35 min before shift (more than 30 min), so should prefer late connection result.ShouldNotBeNull(); - result.ArrivalTime.ShouldBe("2023-12-15T08:05:00"); + result.ArrivalTime.ShouldBe(T("2023-12-15T08:05:00")); + } + + private DateTimeOffset T(string dateTimeString) + { + return DateTimeOffset.Parse(dateTimeString); } } \ No newline at end of file diff --git a/ShiftScheduler.Services.Tests/TransportServiceTests.cs b/ShiftScheduler.Services.Tests/TransportServiceTests.cs index 516b1bc..4d47771 100644 --- a/ShiftScheduler.Services.Tests/TransportServiceTests.cs +++ b/ShiftScheduler.Services.Tests/TransportServiceTests.cs @@ -46,9 +46,9 @@ public class TransportServiceTests var shiftStartTime = new DateTime(2023, 12, 15, 8, 0, 0); var connection = new TransportConnection { - DepartureTime = "2023-12-15T06:45:00", - ArrivalTime = "2023-12-15T07:30:00", - Duration = "00:45:00", + DepartureTime = T("2023-12-15T06:45:00"), + ArrivalTime = T("2023-12-15T07:30:00"), + Duration = TimeSpan.Parse("00:45:00"), Platform = "5" }; @@ -100,11 +100,11 @@ public class TransportServiceTests public async Task GetConnectionAsync_WithDifferentDates_ShouldCreateSeparateCacheEntries() { // Arrange - var shiftStartTime1 = new DateTime(2023, 12, 15, 8, 0, 0); - var shiftStartTime2 = new DateTime(2023, 12, 16, 8, 0, 0); + var shiftStartTime1 = T("2023-12-15T08:00:00"); + var shiftStartTime2 = T("2023-12-16T08:00:00"); - var connection1 = new TransportConnection { ArrivalTime = "2023-12-15T07:30:00" }; - var connection2 = new TransportConnection { ArrivalTime = "2023-12-16T07:30:00" }; + var connection1 = new TransportConnection { ArrivalTime = T("2023-12-15T07:30:00") }; + var connection2 = new TransportConnection { ArrivalTime = T("2023-12-16T07:30:00") }; _transportServiceMock .Setup(x => x.GetConnectionAsync(shiftStartTime1)) @@ -121,10 +121,15 @@ public class TransportServiceTests // Assert result1.ShouldNotBeNull(); result2.ShouldNotBeNull(); - result1.ArrivalTime.ShouldBe("2023-12-15T07:30:00"); - result2.ArrivalTime.ShouldBe("2023-12-16T07:30:00"); + result1.ArrivalTime.ShouldBe(T("2023-12-15T07:30:00")); + result2.ArrivalTime.ShouldBe(T("2023-12-16T07:30:00")); // Verify that transport service was called twice (different cache keys) - _transportServiceMock.Verify(x => x.GetConnectionAsync(It.IsAny()), Times.Exactly(2)); + _transportServiceMock.Verify(x => x.GetConnectionAsync(It.IsAny()), Times.Exactly(2)); + } + + private DateTimeOffset T(string dateTimeString) + { + return DateTimeOffset.Parse(dateTimeString); } } \ No newline at end of file