Added support for Gracie's multiple open times

This commit is contained in:
Campbell 2025-09-01 11:21:38 -04:00
parent f12f12bac8
commit 3f812495b0
Signed by: NinjaCheetah
GPG Key ID: 39C2500E1778B156
2 changed files with 108 additions and 71 deletions

View File

@ -9,7 +9,7 @@ import SwiftUI
struct Location: Hashable {
let name: String
let todaysHours: String
let todaysHours: [String]
let isOpen: openStatus
}
@ -17,6 +17,7 @@ struct ContentView: View {
@State private var isLoading = true
@State private var rotationDegrees: Double = 0
@State private var diningLocations: [Location] = []
@State private var lastRefreshed: Date?
private var animation: Animation {
.linear
@ -43,14 +44,15 @@ struct ContentView: View {
// Parse the open status and times to create the hours string. If either time is missing, assume it has no openings
// and use "Not Open Today". If there are times, then set those to be displayed.
let todaysHours: String
if diningInfo.openTime == .none || diningInfo.closeTime == .none {
todaysHours = "Not Open Today"
var todaysHours: [String] = []
if diningInfo.diningTimes == .none {
todaysHours = ["Not Open Today"]
} else {
print("Open:", display.string(from: diningInfo.openTime!),
"Close:", display.string(from: diningInfo.closeTime!))
todaysHours = "\(display.string(from: diningInfo.openTime!)) - \(display.string(from: diningInfo.closeTime!))"
for time in diningInfo.diningTimes! {
print("Open:", display.string(from: time.openTime),
"Close:", display.string(from: time.closeTime))
todaysHours.append("\(display.string(from: time.openTime)) - \(display.string(from: time.closeTime))")
}
}
DispatchQueue.global().sync {
newDiningLocations.append(
@ -60,6 +62,7 @@ struct ContentView: View {
isOpen: diningInfo.open
)
)
lastRefreshed = Date()
}
}
DispatchQueue.global().sync {
@ -91,26 +94,40 @@ struct ContentView: View {
}
.padding()
} else {
Form {
ForEach(diningLocations, id: \.self) { location in
VStack(alignment: .leading) {
Text(location.name)
Text(location.todaysHours)
switch location.isOpen {
case .open:
Text("Open")
.foregroundStyle(.green)
case .closed:
Text("Closed")
.foregroundStyle(.red)
case .openingSoon:
Text("Opening Soon")
.foregroundStyle(.orange)
case .closingSoon:
Text("Closing Soon")
.foregroundStyle(.orange)
VStack() {
List {
Section(content: {
ForEach(diningLocations, id: \.self) { location in
VStack(alignment: .leading) {
Text(location.name)
ForEach(location.todaysHours, id: \.self) { hours in
Text(hours)
}
switch location.isOpen {
case .open:
Text("Open")
.foregroundStyle(.green)
case .closed:
Text("Closed")
.foregroundStyle(.red)
case .openingSoon:
Text("Opening Soon")
.foregroundStyle(.orange)
case .closingSoon:
Text("Closing Soon")
.foregroundStyle(.orange)
}
}
}
}
}, footer: {
if let lastRefreshed {
VStack(alignment: .center) {
Text("Last refreshed: \(lastRefreshed.formatted())")
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity)
}
}
})
}
}
.navigationTitle("RIT Dining")

View File

@ -79,11 +79,15 @@ enum openStatus {
case closingSoon
}
struct DiningTimes: Equatable {
let openTime: Date
let closeTime: Date
}
struct DiningInfo {
let id: Int
let name: String
let openTime: Date?
let closeTime: Date?
let diningTimes: [DiningTimes]?
let open: openStatus
}
@ -95,13 +99,12 @@ func getLocationInfo(location: DiningLocation) -> DiningInfo {
return DiningInfo(
id: location.id,
name: location.name,
openTime: .none,
closeTime: .none,
diningTimes: .none,
open: .closed)
}
let openString: String
let closeString: String
var openStrings: [String] = []
var closeStrings: [String] = []
// Dining locations have a regular schedule, but then they also have exceptions listed for days like weekends or holidays. If there
// are exceptions, use those times for the day, otherwise we can just use the default times.
@ -111,67 +114,84 @@ func getLocationInfo(location: DiningLocation) -> DiningInfo {
return DiningInfo(
id: location.id,
name: location.name,
openTime: .none,
closeTime: .none,
diningTimes: .none,
open: .closed)
}
openString = location.events[0].exceptions![0].startTime
closeString = location.events[0].exceptions![0].endTime
openStrings.append(location.events[0].exceptions![0].startTime)
closeStrings.append(location.events[0].exceptions![0].endTime)
} else {
openString = location.events[0].startTime
closeString = location.events[0].endTime
for event in location.events {
openStrings.append(event.startTime)
closeStrings.append(event.endTime)
}
}
// I hate all of this date component nonsense.
let openParts = openString.split(separator: ":").map { Int($0) ?? 0 }
let openTimeComponents = DateComponents(hour: openParts[0], minute: openParts[1], second: openParts[2])
let closeParts = closeString.split(separator: ":").map { Int($0) ?? 0 }
let closeTimeComponents = DateComponents(hour: closeParts[0], minute: closeParts[1], second: closeParts[2])
var openDates: [Date] = []
var closeDates: [Date] = []
let calendar = Calendar.current
let now = Date()
let openDate = calendar.date(
bySettingHour: openTimeComponents.hour!,
minute: openTimeComponents.minute!,
second: openTimeComponents.second!,
of: now)!
for i in 0..<openStrings.count {
let openParts = openStrings[i].split(separator: ":").map { Int($0) ?? 0 }
let openTimeComponents = DateComponents(hour: openParts[0], minute: openParts[1], second: openParts[2])
var closeDate = calendar.date(
bySettingHour: closeTimeComponents.hour!,
minute: closeTimeComponents.minute!,
second: closeTimeComponents.second!,
of: now)!
let closeParts = closeStrings[i].split(separator: ":").map { Int($0) ?? 0 }
let closeTimeComponents = DateComponents(hour: closeParts[0], minute: closeParts[1], second: closeParts[2])
openDates.append(calendar.date(
bySettingHour: openTimeComponents.hour!,
minute: openTimeComponents.minute!,
second: openTimeComponents.second!,
of: now)!)
closeDates.append(calendar.date(
bySettingHour: closeTimeComponents.hour!,
minute: closeTimeComponents.minute!,
second: closeTimeComponents.second!,
of: now)!)
}
// If the closing time is less than or equal to the opening time, it's probably midnight and means either open until midnight
// or open 24/7, in the case of Bytes.
if closeDate <= openDate {
closeDate = calendar.date(byAdding: .day, value: 1, to: closeDate)!
for i in 0..<closeDates.count {
if closeDates[i] <= openDates[i] {
closeDates[i] = calendar.date(byAdding: .day, value: 1, to: closeDates[i])!
}
}
// This can probably be done in a cleaner way but it's okay for now. If the location is open but the close date is within the next
// 30 minutes, label it as closing soon, and do the opposite if it's closed but the open date is within the next 30 minutes.
let isOpen = (now >= openDate && now <= closeDate)
let openStatus: openStatus
if isOpen {
if closeDate < calendar.date(byAdding: .minute, value: 30, to: now)! {
openStatus = .closingSoon
var openStatus: openStatus = .closed
for i in 0..<openDates.count {
let isOpen = (now >= openDates[i] && now <= closeDates[i])
if isOpen {
if closeDates[i] < calendar.date(byAdding: .minute, value: 30, to: now)! {
openStatus = .closingSoon
break
} else {
openStatus = .open
break
}
} else {
openStatus = .open
}
} else {
if openDate < calendar.date(byAdding: .minute, value: 30, to: now)! {
openStatus = .openingSoon
} else {
openStatus = .closed
if openDates[i] < calendar.date(byAdding: .minute, value: 30, to: now)! {
openStatus = .openingSoon
break
} else {
openStatus = .closed
}
}
}
var diningTimes: [DiningTimes] = []
for i in 0..<openDates.count {
diningTimes.append(DiningTimes(openTime: openDates[i], closeTime: closeDates[i]))
}
return DiningInfo(
id: location.id,
name: location.name,
openTime: openDate,
closeTime: closeDate,
diningTimes: diningTimes,
open: openStatus)
}