Christopher Hotchkiss Christopher Hotchkiss
Login

Code

remindwall / RWPureSwift / Sources / AppTypes / ScreenOffSchedule.swift

Back to remindwall — Current RemindWall

Download this file

import Tagged

public enum ScreenOffScheduleTag {}
public typealias ScreenOffSchedule = Tagged<ScreenOffScheduleTag, String>

extension ScreenOffSchedule {
    /// Creates a schedule from start and end times.
    /// - Parameters:
    ///   - startHour: 0-23
    ///   - startMinute: 0-59
    ///   - endHour: 0-23
    ///   - endMinute: 0-59
    public init(startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) {
        self.init(rawValue: "\(startHour):\(startMinute)-\(endHour):\(endMinute)")
    }

    /// Default schedule: 10:00 PM to 6:00 AM
    public static let `default` = ScreenOffSchedule(startHour: 22, startMinute: 0, endHour: 6, endMinute: 0)

    public var startHour: Int {
        let parts = rawValue.split(separator: "-")
        guard parts.count == 2 else { return 22 }
        let timeParts = parts[0].split(separator: ":")
        guard timeParts.count == 2, let hour = Int(timeParts[0]) else { return 22 }
        return hour
    }

    public var startMinute: Int {
        let parts = rawValue.split(separator: "-")
        guard parts.count == 2 else { return 0 }
        let timeParts = parts[0].split(separator: ":")
        guard timeParts.count == 2, let minute = Int(timeParts[1]) else { return 0 }
        return minute
    }

    public var endHour: Int {
        let parts = rawValue.split(separator: "-")
        guard parts.count == 2 else { return 6 }
        let timeParts = parts[1].split(separator: ":")
        guard timeParts.count == 2, let hour = Int(timeParts[0]) else { return 6 }
        return hour
    }

    public var endMinute: Int {
        let parts = rawValue.split(separator: "-")
        guard parts.count == 2 else { return 0 }
        let timeParts = parts[1].split(separator: ":")
        guard timeParts.count == 2, let minute = Int(timeParts[1]) else { return 0 }
        return minute
    }

    /// Formatted display string for the start time (e.g., "10:00 PM")
    public var startTimeDisplay: String {
        formatTime(hour: startHour, minute: startMinute)
    }

    /// Formatted display string for the end time (e.g., "6:00 AM")
    public var endTimeDisplay: String {
        formatTime(hour: endHour, minute: endMinute)
    }

    /// Total minutes from midnight for start time (0-1439)
    public var startTotalMinutes: Int {
        startHour * 60 + startMinute
    }

    /// Total minutes from midnight for end time (0-1439)
    public var endTotalMinutes: Int {
        endHour * 60 + endMinute
    }

    /// Returns `true` when the given time (total minutes from midnight, 0-1439)
    /// falls within the screen-off window.
    ///
    /// Handles overnight schedules where start > end (e.g., 22:00 to 06:00).
    public func isInOffWindow(currentTotalMinutes: Int) -> Bool {
        let start = startTotalMinutes
        let end = endTotalMinutes

        if start == end {
            // Degenerate case: 0-length window, never off
            return false
        } else if start < end {
            // Same-day window (e.g., 02:00 to 14:00)
            return currentTotalMinutes >= start && currentTotalMinutes < end
        } else {
            // Overnight window (e.g., 22:00 to 06:00)
            return currentTotalMinutes >= start || currentTotalMinutes < end
        }
    }

    private func formatTime(hour: Int, minute: Int) -> String {
        let hour12 = hour % 12
        let displayHour = hour12 == 0 ? 12 : hour12
        let period = hour < 12 ? "AM" : "PM"
        return String(format: "%d:%02d %@", displayHour, minute, period)
    }
}