| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- //
- // GlucoseRangeSchedule.swift
- // Naterade
- //
- // Created by Nathan Racklyeft on 2/13/16.
- // Copyright © 2016 Nathan Racklyeft. All rights reserved.
- //
- import Foundation
- import HealthKit
- public struct DoubleRange {
- public let minValue: Double
- public let maxValue: Double
- public init(minValue: Double, maxValue: Double) {
- self.minValue = minValue
- self.maxValue = maxValue
- }
- public var isZero: Bool {
- return abs(minValue) < .ulpOfOne && abs(maxValue) < .ulpOfOne
- }
- }
- extension DoubleRange: RawRepresentable {
- public typealias RawValue = [Double]
- public init?(rawValue: RawValue) {
- guard rawValue.count == 2 else {
- return nil
- }
- minValue = rawValue[0]
- maxValue = rawValue[1]
- }
- public var rawValue: RawValue {
- return [minValue, maxValue]
- }
- }
- extension DoubleRange: Equatable {
- public static func ==(lhs: DoubleRange, rhs: DoubleRange) -> Bool {
- return abs(lhs.minValue - rhs.minValue) < .ulpOfOne &&
- abs(lhs.maxValue - rhs.maxValue) < .ulpOfOne
- }
- }
- extension DoubleRange: Hashable {}
- extension DoubleRange: Codable {}
- /// Defines a daily schedule of glucose ranges
- public struct GlucoseRangeSchedule: DailySchedule, Equatable {
- public typealias RawValue = [String: Any]
- /// A time-based value overriding the rangeSchedule
- public struct Override: Equatable {
- public let start: Date
- public let end: Date
- public let value: DoubleRange
- /// Initializes a new override
- ///
- /// - Parameters:
- /// - value: The value to return when active
- /// - start: The date at which the override starts
- /// - end: The date at which the override ends, or nil for an indefinite override
- public init(value: DoubleRange, start: Date, end: Date? = nil) {
- self.value = value
- self.start = start
- self.end = end ?? .distantFuture
- }
- public var activeDates: DateInterval {
- return DateInterval(start: start, end: end)
- }
- public func isActive(at date: Date = Date()) -> Bool {
- return activeDates.contains(date) && !value.isZero
- }
- }
- /// An enabled override of the range schedule; only "active" between start and end, but when
- /// active, it overrides the entire schedule. Not persisted
- public private(set) var override: Override?
- var rangeSchedule: DailyQuantitySchedule<DoubleRange>
- public init(rangeSchedule: DailyQuantitySchedule<DoubleRange>, override: Override? = nil) {
- self.rangeSchedule = rangeSchedule
- self.override = override
- }
- public init?(unit: HKUnit, dailyItems: [RepeatingScheduleValue<DoubleRange>], timeZone: TimeZone? = nil) {
- guard let rangeSchedule = DailyQuantitySchedule<DoubleRange>(unit: unit, dailyItems: dailyItems, timeZone: timeZone) else {
- return nil
- }
- self.rangeSchedule = rangeSchedule
- }
- public init?(rawValue: RawValue) {
- guard let rangeSchedule = DailyQuantitySchedule<DoubleRange>(rawValue: rawValue) else {
- return nil
- }
- self.rangeSchedule = rangeSchedule
- }
- public func between(start startDate: Date, end endDate: Date) -> [AbsoluteScheduleValue<DoubleRange>] {
- return rangeSchedule.between(start: startDate, end: endDate)
- }
- public func quantityBetween(start: Date, end: Date) -> [AbsoluteScheduleValue<ClosedRange<HKQuantity>>] {
- var quantitySchedule = [AbsoluteScheduleValue<ClosedRange<HKQuantity>>]()
- for schedule in between(start: start, end: end) {
- quantitySchedule.append(AbsoluteScheduleValue(
- startDate: schedule.startDate,
- endDate: schedule.endDate,
- value: schedule.value.quantityRange(for: unit)
- ))
- }
- return quantitySchedule
- }
- /// Returns the underlying values in `unit`
- /// Consider using quantity(at:) instead
- public func value(at time: Date) -> DoubleRange {
- if let override = override, time >= override.start && Date() < override.end {
- return override.value
- }
- return rangeSchedule.value(at: time)
- }
- public func quantityRange(at time: Date) -> ClosedRange<HKQuantity> {
- return value(at: time).quantityRange(for: unit)
- }
- public var items: [RepeatingScheduleValue<DoubleRange>] {
- return rangeSchedule.items
- }
- public var timeZone: TimeZone {
- get {
- return rangeSchedule.timeZone
- }
- set {
- rangeSchedule.timeZone = newValue
- }
- }
- public var unit: HKUnit {
- return rangeSchedule.unit
- }
- public var rawValue: RawValue {
- return rangeSchedule.rawValue
- }
- public func minLowerBound() -> HKQuantity {
- let minDoubleValue = items.lazy.map { $0.value.minValue }.min()!
- return HKQuantity(unit: unit, doubleValue: minDoubleValue)
- }
- public func scheduleRange() -> ClosedRange<HKQuantity> {
- let minDoubleValue = items.lazy.map { $0.value.minValue }.min()!
- let lowerBound = HKQuantity(unit: unit, doubleValue: minDoubleValue)
- let maxDoubleValue = items.lazy.map { $0.value.maxValue }.max()!
- let upperBound = HKQuantity(unit: unit, doubleValue: maxDoubleValue)
- return lowerBound...upperBound
- }
- }
- extension GlucoseRangeSchedule: Codable {}
- extension GlucoseRangeSchedule.Override: Codable {}
- extension DoubleRange {
- public func quantityRange(for unit: HKUnit) -> ClosedRange<HKQuantity> {
- let lowerBound = HKQuantity(unit: unit, doubleValue: minValue)
- let upperBound = HKQuantity(unit: unit, doubleValue: maxValue)
- return lowerBound...upperBound
- }
- }
- extension ClosedRange where Bound == HKQuantity {
- public func doubleRange(for unit: HKUnit) -> DoubleRange {
- return DoubleRange(minValue: lowerBound.doubleValue(for: unit), maxValue: upperBound.doubleValue(for: unit))
- }
- }
- public extension DoubleRange {
- init(_ val: ClosedRange<Double>) {
- self.init(minValue: val.lowerBound, maxValue: val.upperBound)
- }
- }
|