Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add array extension for deadline sorting #5

Merged
merged 2 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
23 changes: 23 additions & 0 deletions mobile-client/ToDoList/Helper/ToDoArrayExtensions.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
}
49 changes: 37 additions & 12 deletions mobile-client/ToDoList/Helper/ToDoDateFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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[..<timezoneIndex])
}
} else {
" (\(String(deadline.suffix(5))))"
timezoneString = ""
deadlineWithoutTimezone = deadline
}

let displayFormatter = DateFormatter()
displayFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
displayFormatter.timeZone = TimeZone(secondsFromGMT: 0)

guard let date = displayFormatter.date(from: deadlineWithoutTimezone) else {
return ""
}

return "\(formattedDate)\(timeZoneString)"
displayFormatter.dateFormat = "MMM d, yyyy, h:mm:ss a"
let formattedDate = displayFormatter.string(from: date)

return "\(formattedDate) \(timezoneString)"
}
}
10 changes: 3 additions & 7 deletions mobile-client/ToDoList/Model/ToDoLocalService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@ class ToDoLocalService: ToDoLocalServiceProtocol {

func fetchTodos() throws -> [ToDoItemData] {
let fetchDescriptor = FetchDescriptor<ToDoItemData>()
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 {
Expand Down
6 changes: 1 addition & 5 deletions mobile-client/ToDoList/Model/ToDoRemoteService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion mobile-client/ToDoList/View/ToDoListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ struct TodoRow: View {
.font(.headline)
Spacer()
Text(todo.status)
.padding(6)
.font(.footnote)
.fontWeight(.semibold)
.padding(7)
.background(statusColor(for: todo.status))
.foregroundColor(.white)
.cornerRadius(5)
Expand Down
37 changes: 37 additions & 0 deletions mobile-client/ToDoListTests/ToDoDateFormatterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Foundation
import Testing
@testable import ToDoList

struct ToDoDateFormatterTests {
/// Tests formatting of deadlines provided in UTC time zone.
@Test
func testDeadlineWithUTCTimeZone() {
let utcDeadline1 = "2024-04-08T11:46:14.349Z"
let formattedUtcDeadline1 = ToDoDateFormatter.formattedDeadline(deadline: utcDeadline1)
#expect(formattedUtcDeadline1 == "Apr 8, 2024, 11:46:14 AM (+0)")

let utcDeadline2 = "2022-11-08T23:46:00.119Z"
let formattedUtcDeadline2 = ToDoDateFormatter.formattedDeadline(deadline: utcDeadline2)
#expect(formattedUtcDeadline2 == "Nov 8, 2022, 11:46:00 PM (+0)")
}

/// Tests formatting of deadlines provided in specific time zones.
@Test
func testDeadlineWithSpecificTimeZone() {
let taipeiDeadline = "2023-01-01T22:35:00.000+08:00"
let formattedTaipeiDeadline = ToDoDateFormatter.formattedDeadline(deadline: taipeiDeadline)
#expect(formattedTaipeiDeadline == "Jan 1, 2023, 10:35:00 PM (+08:00)")

let indiaDeadline1 = "2024-12-14T12:34:56.789+05:30"
let formattedIndiaDeadline1 = ToDoDateFormatter.formattedDeadline(deadline: indiaDeadline1)
#expect(formattedIndiaDeadline1 == "Dec 14, 2024, 12:34:56 PM (+05:30)")

let indiaDeadline2 = "2024-12-14T11:59:59.999+05:30"
let formattedIndiaDeadline2 = ToDoDateFormatter.formattedDeadline(deadline: indiaDeadline2)
#expect(formattedIndiaDeadline2 == "Dec 14, 2024, 11:59:59 AM (+05:30)")

let vancouverDeadline = "2024-05-02T18:19:11.199-08:00"
let formattedVancouverDeadline = ToDoDateFormatter.formattedDeadline(deadline: vancouverDeadline)
#expect(formattedVancouverDeadline == "May 2, 2024, 6:19:11 PM (-08:00)")
}
}
8 changes: 3 additions & 5 deletions mobile-client/ToDoListTests/ToDoReducerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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!)

Expand Down
Loading