From fe00a0146291a7012c7fd6843c1a5084d5c3f160 Mon Sep 17 00:00:00 2001 From: rex Date: Fri, 25 Oct 2024 21:05:34 +0800 Subject: [PATCH 1/2] add array extension for deadline sorting --- .../ToDoList/Helper/ToDoArrayExtensions.swift | 23 +++++++++++++++++++ .../ToDoList/Model/ToDoLocalService.swift | 10 +++----- .../ToDoList/Model/ToDoRemoteService.swift | 6 +---- .../ToDoListTests/ToDoReducerTests.swift | 8 +++---- 4 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 mobile-client/ToDoList/Helper/ToDoArrayExtensions.swift diff --git a/mobile-client/ToDoList/Helper/ToDoArrayExtensions.swift b/mobile-client/ToDoList/Helper/ToDoArrayExtensions.swift new file mode 100644 index 0000000..fb2711a --- /dev/null +++ b/mobile-client/ToDoList/Helper/ToDoArrayExtensions.swift @@ -0,0 +1,23 @@ +import Foundation + +// Extensions for ToDoItemData +extension [ToDoItemData] { + mutating func sortedByDeadline() { + sort { first, second in + let firstDeadline = ToDoDateFormatter.isoDateFormatter.date(from: first.deadline ?? "") ?? Date.distantFuture + let secondDeadline = ToDoDateFormatter.isoDateFormatter.date(from: second.deadline ?? "") ?? Date.distantFuture + return firstDeadline < secondDeadline + } + } +} + +// Extensions for ToDoItem +extension [ToDoItem] { + mutating func sortedByDeadline() { + sort { first, second in + let firstDeadline = ToDoDateFormatter.isoDateFormatter.date(from: first.deadline ?? "") ?? Date.distantFuture + let secondDeadline = ToDoDateFormatter.isoDateFormatter.date(from: second.deadline ?? "") ?? Date.distantFuture + return firstDeadline < secondDeadline + } + } +} diff --git a/mobile-client/ToDoList/Model/ToDoLocalService.swift b/mobile-client/ToDoList/Model/ToDoLocalService.swift index 3d54f74..1343218 100644 --- a/mobile-client/ToDoList/Model/ToDoLocalService.swift +++ b/mobile-client/ToDoList/Model/ToDoLocalService.swift @@ -17,13 +17,9 @@ class ToDoLocalService: ToDoLocalServiceProtocol { func fetchTodos() throws -> [ToDoItemData] { let fetchDescriptor = FetchDescriptor() - let todos = try context.fetch(fetchDescriptor) - let sortedTodos = todos.sorted { first, second in - let firstDeadline = ToDoDateFormatter.isoDateFormatter.date(from: first.deadline ?? "") ?? Date.distantFuture - let secondDeadline = ToDoDateFormatter.isoDateFormatter.date(from: second.deadline ?? "") ?? Date.distantFuture - return firstDeadline < secondDeadline - } - return sortedTodos + var todos = try context.fetch(fetchDescriptor) + todos.sortedByDeadline() + return todos } func save(todo: ToDoItemData) throws { diff --git a/mobile-client/ToDoList/Model/ToDoRemoteService.swift b/mobile-client/ToDoList/Model/ToDoRemoteService.swift index f7d42eb..7112606 100644 --- a/mobile-client/ToDoList/Model/ToDoRemoteService.swift +++ b/mobile-client/ToDoList/Model/ToDoRemoteService.swift @@ -75,11 +75,7 @@ final class ToDoRemoteService: ToDoRemoteServiceProtocol { var remoteTodos = decodedResponse.data // Sort remote todos by deadline (nil values will be placed at the end) - remoteTodos.sort { first, second in - let firstDeadline = ToDoDateFormatter.isoDateFormatter.date(from: first.deadline ?? "") ?? Date.distantFuture - let secondDeadline = ToDoDateFormatter.isoDateFormatter.date(from: second.deadline ?? "") ?? Date.distantFuture - return firstDeadline < secondDeadline - } + remoteTodos.sortedByDeadline() // Sync local data with remote data: // 1. For each remote ToDo, update the matching local item if IDs match, replacing its properties. diff --git a/mobile-client/ToDoListTests/ToDoReducerTests.swift b/mobile-client/ToDoListTests/ToDoReducerTests.swift index 52d929c..26e582b 100644 --- a/mobile-client/ToDoListTests/ToDoReducerTests.swift +++ b/mobile-client/ToDoListTests/ToDoReducerTests.swift @@ -137,7 +137,7 @@ struct ToDoReducerTests { /// Tests the performance of inserting a new ToDo item into the list. @Test - func testInsertingNewToDoItemPerformance() async { + func testNewToDoItemInsertionPerformance() async { let totalAdditionDay = 6000 // Generate a list of ToDo items to be added. @@ -154,10 +154,8 @@ struct ToDoReducerTests { } // Sort ToDo items by their deadline. - todos.sort { - ($0.deadline.flatMap(ToDoDateFormatter.isoDateFormatter.date(from:)) ?? Date.distantFuture) < - ($1.deadline.flatMap(ToDoDateFormatter.isoDateFormatter.date(from:)) ?? Date.distantFuture) - } + todos.sortedByDeadline() + // Select a random deadline for the new ToDo item to be inserted between two existing items. let inBetweenDeadline = randomDeadlineInBetween(between: todos[4217].deadline!, and: todos[4218].deadline!) From b55c8365f03a0c87ddbf11f232f84e8637478090 Mon Sep 17 00:00:00 2001 From: rex Date: Sat, 26 Oct 2024 00:09:29 +0800 Subject: [PATCH 2/2] fix date formatter issues --- .../ToDoList/Helper/ToDoDateFormatter.swift | 49 ++++++++++++++----- .../ToDoList/View/ToDoListView.swift | 4 +- .../ToDoDateFormatterTests.swift | 37 ++++++++++++++ 3 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 mobile-client/ToDoListTests/ToDoDateFormatterTests.swift diff --git a/mobile-client/ToDoList/Helper/ToDoDateFormatter.swift b/mobile-client/ToDoList/Helper/ToDoDateFormatter.swift index 29f3ce1..e319d9e 100644 --- a/mobile-client/ToDoList/Helper/ToDoDateFormatter.swift +++ b/mobile-client/ToDoList/Helper/ToDoDateFormatter.swift @@ -7,25 +7,50 @@ enum ToDoDateFormatter { return formatter }() + /// Formats an ISO 8601 deadline string into a user-friendly date string + /// while preserving the timezone information. + /// + /// Returns a format like "Jan 1, 2023, 10:35:00 PM (+08:00)" or + /// "Apr 8, 2024, 11:46:14 AM (+0)". + /// + /// - Parameter deadline: An ISO 8601 formatted deadline string, which may include + /// a timezone offset (e.g., "2024-04-08T11:46:14.349Z" or "2024-12-14T12:34:56.789+05:30"). + /// - Returns: A formatted string representing the deadline, or an empty string if + /// the input is nil or invalid. static func formattedDeadline(deadline: String?) -> String { - guard let deadline, let date = isoDateFormatter.date(from: deadline) else { + guard let deadline else { return "" } - // - dateStyle: .medium formats a date transforming "2024-07-31" to "Jul 31, 2024" - // - timeStyle: .medium formats the time to something like "12:34 PM" - let displayFormatter = DateFormatter() - displayFormatter.dateStyle = .medium - displayFormatter.timeStyle = .medium - - let formattedDate = displayFormatter.string(from: date) + let hasTimezone = deadline.hasSuffix("Z") || deadline.contains("+") || deadline.contains("-") + let timezoneString: String + let deadlineWithoutTimezone: String - let timeZoneString = if deadline.hasSuffix("Z") { - "" + if hasTimezone { + if deadline.hasSuffix("Z") { + timezoneString = "(+0)" + deadlineWithoutTimezone = String(deadline.dropLast(1)) // Remove the "Z" + } else { + let timezoneIndex = deadline.index(deadline.endIndex, offsetBy: -6) + timezoneString = "(\(deadline[timezoneIndex...]))" + deadlineWithoutTimezone = String(deadline[..