1
0
forked from firka/firka

Refactor timetable day selection and parsing

Add robust date parsing and simplify lesson selection logic. Introduces ISO8601 formatters (with and without fractional seconds) and updates parseNextSchoolDayDate to try both variants, plus existing yyyy-MM-dd fallback. Adds LessonCandidate, startOfDay and nextSchoolDay helpers and builds a sorted candidate list (today, tomorrow, next school day) to pick the appropriate lesson set and correctly set isNextDay/isNextSchoolDay flags. Cleans up previous branching-heavy logic and improves handling of edge cases (lessons finished, next-school-day resolution).
This commit is contained in:
Horváth Gergely
2026-02-24 14:38:06 +01:00
committed by 4831c0
parent 146124228a
commit 70213e376c

View File

@@ -28,6 +28,11 @@ struct TimetableProvider: AppIntentTimelineProvider {
typealias Entry = TimetableEntry typealias Entry = TimetableEntry
typealias Intent = TimetableWidgetIntent typealias Intent = TimetableWidgetIntent
private struct LessonCandidate {
let lessons: [WidgetLesson]
let day: Date
}
private static let dateFormatter: DateFormatter = { private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter() let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd" formatter.dateFormat = "yyyy-MM-dd"
@@ -36,8 +41,26 @@ struct TimetableProvider: AppIntentTimelineProvider {
return formatter return formatter
}() }()
private static let isoFormatterWithFractional: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter
}()
private static let isoFormatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime]
return formatter
}()
private func parseNextSchoolDayDate(_ dateString: String?) -> Date? { private func parseNextSchoolDayDate(_ dateString: String?) -> Date? {
guard let dateString = dateString else { return nil } guard let dateString = dateString else { return nil }
if let date = Self.isoFormatterWithFractional.date(from: dateString) {
return date
}
if let date = Self.isoFormatter.date(from: dateString) {
return date
}
if let date = Self.dateFormatter.date(from: dateString) { if let date = Self.dateFormatter.date(from: dateString) {
return date return date
} }
@@ -46,6 +69,23 @@ struct TimetableProvider: AppIntentTimelineProvider {
return Self.dateFormatter.date(from: trimmed) return Self.dateFormatter.date(from: trimmed)
} }
private func startOfDay(for lessons: [WidgetLesson], calendar: Calendar) -> Date? {
guard let first = lessons.first else { return nil }
return calendar.startOfDay(for: first.start)
}
private func nextSchoolDay(from data: WidgetData, calendar: Calendar) -> Date? {
if let firstNextLesson = data.timetable.nextSchoolDay?.first {
return calendar.startOfDay(for: firstNextLesson.start)
}
if let parsedDate = parseNextSchoolDayDate(data.timetable.nextSchoolDayDate) {
return calendar.startOfDay(for: parsedDate)
}
return nil
}
func placeholder(in context: Context) -> TimetableEntry { func placeholder(in context: Context) -> TimetableEntry {
TimetableEntry( TimetableEntry(
date: Date(), date: Date(),
@@ -181,9 +221,8 @@ struct TimetableProvider: AppIntentTimelineProvider {
let midnight = calendar.startOfDay(for: now.addingTimeInterval(86400)) let midnight = calendar.startOfDay(for: now.addingTimeInterval(86400))
entries.append(createEntry(for: configuration, date: midnight)) entries.append(createEntry(for: configuration, date: midnight))
if let nextSchoolDayDateString = data?.timetable.nextSchoolDayDate, if let data = data,
let nextSchoolDayDate = parseNextSchoolDayDate(nextSchoolDayDateString) { let nextSchoolDay = nextSchoolDay(from: data, calendar: calendar) {
let nextSchoolDay = calendar.startOfDay(for: nextSchoolDayDate)
let dayBeforeNextSchoolDay = calendar.date(byAdding: .day, value: -1, to: nextSchoolDay)! let dayBeforeNextSchoolDay = calendar.date(byAdding: .day, value: -1, to: nextSchoolDay)!
if dayBeforeNextSchoolDay > now { if dayBeforeNextSchoolDay > now {
@@ -255,93 +294,47 @@ struct TimetableProvider: AppIntentTimelineProvider {
} }
let entryDay = calendar.startOfDay(for: date) let entryDay = calendar.startOfDay(for: date)
let tomorrowOfEntryDay = calendar.date(byAdding: .day, value: 1, to: entryDay)!
var lessons = data.timetable.today let todayLessons = data.timetable.today
var isNextDay = false let tomorrowLessons = data.timetable.tomorrow
let nextSchoolDayLessons = data.timetable.nextSchoolDay ?? []
if let firstTodayLesson = lessons.first { var candidates: [LessonCandidate] = []
let todayLessonDay = calendar.startOfDay(for: firstTodayLesson.start)
if entryDay > todayLessonDay { if let todayDay = startOfDay(for: todayLessons, calendar: calendar), !todayLessons.isEmpty {
lessons = data.timetable.tomorrow candidates.append(LessonCandidate(lessons: todayLessons, day: todayDay))
if let firstTomorrowLesson = lessons.first {
let tomorrowLessonDay = calendar.startOfDay(for: firstTomorrowLesson.start)
isNextDay = entryDay < tomorrowLessonDay
}
} else {
let lastLesson = lessons.last
if let last = lastLesson, date > last.end {
lessons = data.timetable.tomorrow
isNextDay = true
}
}
} else {
lessons = data.timetable.tomorrow
if !lessons.isEmpty {
isNextDay = true
}
} }
if lessons.isEmpty { if let tomorrowDay = startOfDay(for: tomorrowLessons, calendar: calendar), !tomorrowLessons.isEmpty {
if let nextSchoolDayLessons = data.timetable.nextSchoolDay, !nextSchoolDayLessons.isEmpty { candidates.append(LessonCandidate(lessons: tomorrowLessons, day: tomorrowDay))
if let nextSchoolDayDate = parseNextSchoolDayDate(data.timetable.nextSchoolDayDate) { }
let nextSchoolDay = calendar.startOfDay(for: nextSchoolDayDate)
let dayBeforeNextSchoolDay = calendar.date(byAdding: .day, value: -1, to: nextSchoolDay)!
if entryDay == nextSchoolDay { if !nextSchoolDayLessons.isEmpty,
let currentLesson = nextSchoolDayLessons.first { lesson in let resolvedNextSchoolDay = nextSchoolDay(from: data, calendar: calendar)
return date >= lesson.start && date <= lesson.end ?? startOfDay(for: nextSchoolDayLessons, calendar: calendar) {
} candidates.append(LessonCandidate(lessons: nextSchoolDayLessons, day: resolvedNextSchoolDay))
let nextLesson = nextSchoolDayLessons.first { $0.start > date } }
return TimetableEntry( candidates.sort { $0.day < $1.day }
date: date,
configuration: configuration,
data: data,
lessons: nextSchoolDayLessons,
currentLesson: currentLesson,
nextLesson: nextLesson,
isNextDay: false,
isNextSchoolDay: false,
nextSchoolDayDateString: nil,
breakInfo: nil,
state: .normal,
debugInfo: WidgetData.lastError
)
}
if entryDay == dayBeforeNextSchoolDay { // Pick the closest candidate that still has lessons ahead relative to this entry date.
return TimetableEntry( let selectedCandidate = candidates.first { candidate in
date: date, if candidate.day > entryDay {
configuration: configuration, return true
data: data, }
lessons: nextSchoolDayLessons,
currentLesson: nil,
nextLesson: nextSchoolDayLessons.first,
isNextDay: true,
isNextSchoolDay: false,
nextSchoolDayDateString: nil,
breakInfo: nil,
state: .normal,
debugInfo: WidgetData.lastError
)
}
}
return TimetableEntry( if candidate.day == entryDay, let lastLesson = candidate.lessons.last {
date: date, return date <= lastLesson.end
configuration: configuration, }
data: data,
lessons: nextSchoolDayLessons, return false
currentLesson: nil, }
nextLesson: nextSchoolDayLessons.first,
isNextDay: false, guard let selectedCandidate = selectedCandidate else {
isNextSchoolDay: true, let hadLessonsTodayButFinished = candidates.contains { candidate in
nextSchoolDayDateString: data.timetable.nextSchoolDayDate, guard candidate.day == entryDay, let lastLesson = candidate.lessons.last else { return false }
breakInfo: nil, return date > lastLesson.end
state: .normal,
debugInfo: WidgetData.lastError
)
} }
return TimetableEntry( return TimetableEntry(
@@ -351,15 +344,23 @@ struct TimetableProvider: AppIntentTimelineProvider {
lessons: [], lessons: [],
currentLesson: nil, currentLesson: nil,
nextLesson: nil, nextLesson: nil,
isNextDay: isNextDay, isNextDay: false,
isNextSchoolDay: false, isNextSchoolDay: false,
nextSchoolDayDateString: nil, nextSchoolDayDateString: nil,
breakInfo: nil, breakInfo: nil,
state: isNextDay ? .noMoreLessons : .unavailable, state: hadLessonsTodayButFinished ? .noMoreLessons : .unavailable,
debugInfo: WidgetData.lastError debugInfo: WidgetData.lastError
) )
} }
let lessons = selectedCandidate.lessons
let isToday = selectedCandidate.day == entryDay
let isNextDay = selectedCandidate.day == tomorrowOfEntryDay
let isNextSchoolDay = !isToday && !isNextDay
let nextSchoolDayDateString = isNextSchoolDay
? (data.timetable.nextSchoolDayDate ?? Self.dateFormatter.string(from: selectedCandidate.day))
: nil
let currentLesson = lessons.first { lesson in let currentLesson = lessons.first { lesson in
return date >= lesson.start && date <= lesson.end return date >= lesson.start && date <= lesson.end
} }
@@ -373,8 +374,8 @@ struct TimetableProvider: AppIntentTimelineProvider {
currentLesson: currentLesson, currentLesson: currentLesson,
nextLesson: nextLesson, nextLesson: nextLesson,
isNextDay: isNextDay, isNextDay: isNextDay,
isNextSchoolDay: false, isNextSchoolDay: isNextSchoolDay,
nextSchoolDayDateString: nil, nextSchoolDayDateString: nextSchoolDayDateString,
breakInfo: nil, breakInfo: nil,
state: .normal, state: .normal,
debugInfo: WidgetData.lastError debugInfo: WidgetData.lastError