import Foundation import SwiftData @Model final class Habit { var uuid: UUID = UUID() var name: String var symbolName: String var colorHex: String var createdAt: Date /// Time of day for the daily reminder; nil means no reminder. var reminderTime: Date? @Relationship(deleteRule: .cascade, inverse: \HabitEntry.habit) var entries: [HabitEntry] = [] init( name: String, symbolName: String = "star.fill", colorHex: String = "#34C759", createdAt: Date = .now, reminderTime: Date? = nil ) { self.name = name self.symbolName = symbolName self.colorHex = colorHex self.createdAt = createdAt self.reminderTime = reminderTime } } extension Habit { /// Whether this habit was checked off on the given day. func isCompleted(on day: Date, calendar: Calendar = .current) -> Bool { entries.contains { calendar.isDate($0.date, inSameDayAs: day) } } var isCompletedToday: Bool { isCompleted(on: .now) } /// Number of consecutive days (counting back from today) the habit was done. var currentStreak: Int { let calendar = Calendar.current let doneDays = Set(entries.map { calendar.startOfDay(for: $0.date) }) var day = calendar.startOfDay(for: .now) // A streak stays alive if today isn't done yet but yesterday was. if !doneDays.contains(day) { day = calendar.date(byAdding: .day, value: -1, to: day)! } var streak = 0 while doneDays.contains(day) { streak += 1 day = calendar.date(byAdding: .day, value: -1, to: day)! } return streak } /// Longest run of consecutive completed days, ever. var longestStreak: Int { let calendar = Calendar.current let days = Set(entries.map { calendar.startOfDay(for: $0.date) }).sorted() guard !days.isEmpty else { return 0 } var longest = 1 var run = 1 for i in 1..