ggbond268--mactools
108 行
3.8 KiB
Swift
108 行
3.8 KiB
Swift
import Foundation
|
|
import MacToolsPluginKit
|
|
|
|
enum CalendarEventGrouper {
|
|
static func group(
|
|
events: [CalendarEventInput],
|
|
visibleDates: [Date],
|
|
calendar: Calendar,
|
|
localization: PluginLocalization = PluginLocalization(bundle: .main)
|
|
) -> [Date: [CalendarEventSummary]] {
|
|
let dayStarts = visibleDates.map { calendar.startOfDay(for: $0) }
|
|
var grouped = Dictionary(uniqueKeysWithValues: dayStarts.map { ($0, [CalendarEventSummary]()) })
|
|
|
|
for event in events {
|
|
for dayStart in dayStarts where overlaps(event: event, dayStart: dayStart, calendar: calendar) {
|
|
grouped[dayStart, default: []].append(
|
|
summary(
|
|
for: event,
|
|
dayStart: dayStart,
|
|
calendar: calendar,
|
|
localization: localization
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
return grouped.mapValues { summaries in
|
|
summaries.sorted { lhs, rhs in
|
|
if lhs.isAllDay != rhs.isAllDay {
|
|
return lhs.isAllDay && !rhs.isAllDay
|
|
}
|
|
|
|
if lhs.startDate != rhs.startDate {
|
|
return lhs.startDate < rhs.startDate
|
|
}
|
|
|
|
return lhs.title.localizedCompare(rhs.title) == .orderedAscending
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func overlaps(event: CalendarEventInput, dayStart: Date, calendar: Calendar) -> Bool {
|
|
guard let nextDayStart = calendar.date(byAdding: .day, value: 1, to: dayStart) else {
|
|
return false
|
|
}
|
|
|
|
let eventEnd = normalizedEndDate(for: event, calendar: calendar)
|
|
return event.startDate < nextDayStart && eventEnd > dayStart
|
|
}
|
|
|
|
private static func summary(
|
|
for event: CalendarEventInput,
|
|
dayStart: Date,
|
|
calendar: Calendar,
|
|
localization: PluginLocalization
|
|
) -> CalendarEventSummary {
|
|
CalendarEventSummary(
|
|
id: "\(event.id)-\(CalendarComponentCalendars.dayID(for: dayStart, calendar: calendar))",
|
|
title: event.title.isEmpty
|
|
? localization.string("event.untitled", defaultValue: "未命名日程")
|
|
: event.title,
|
|
timeText: timeText(for: event, dayStart: dayStart, calendar: calendar, localization: localization),
|
|
startDate: event.startDate,
|
|
endDate: normalizedEndDate(for: event, calendar: calendar),
|
|
isAllDay: event.isAllDay,
|
|
color: event.color
|
|
)
|
|
}
|
|
|
|
private static func normalizedEndDate(for event: CalendarEventInput, calendar: Calendar) -> Date {
|
|
if event.endDate > event.startDate {
|
|
return event.endDate
|
|
}
|
|
|
|
return calendar.date(byAdding: .minute, value: 1, to: event.startDate) ?? event.startDate
|
|
}
|
|
|
|
private static func timeText(
|
|
for event: CalendarEventInput,
|
|
dayStart: Date,
|
|
calendar: Calendar,
|
|
localization: PluginLocalization
|
|
) -> String {
|
|
guard !event.isAllDay else {
|
|
return localization.string("event.allDay", defaultValue: "全天")
|
|
}
|
|
|
|
let endDate = normalizedEndDate(for: event, calendar: calendar)
|
|
let nextDayStart = calendar.date(byAdding: .day, value: 1, to: dayStart) ?? dayStart
|
|
let visibleStart = max(event.startDate, dayStart)
|
|
let visibleEnd = min(endDate, nextDayStart)
|
|
|
|
if visibleStart >= visibleEnd {
|
|
return formatTime(event.startDate)
|
|
}
|
|
|
|
return "\(formatTime(visibleStart))–\(formatTime(visibleEnd))"
|
|
}
|
|
|
|
private static func formatTime(_ date: Date) -> String {
|
|
let formatter = DateFormatter()
|
|
formatter.locale = PluginRuntimeLocalization.locale
|
|
formatter.dateStyle = .none
|
|
formatter.timeStyle = .short
|
|
return formatter.string(from: date)
|
|
}
|
|
}
|