DailyQuantitySchedule.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. //
  2. // DailyQuantitySchedule.swift
  3. // Naterade
  4. //
  5. // Created by Nathan Racklyeft on 2/12/16.
  6. // Copyright © 2016 Nathan Racklyeft. All rights reserved.
  7. //
  8. import Foundation
  9. import HealthKit
  10. public struct DailyQuantitySchedule<T: RawRepresentable>: DailySchedule {
  11. public typealias RawValue = [String: Any]
  12. public let unit: HKUnit
  13. var valueSchedule: DailyValueSchedule<T>
  14. public init?(unit: HKUnit, dailyItems: [RepeatingScheduleValue<T>], timeZone: TimeZone? = nil) {
  15. guard let valueSchedule = DailyValueSchedule<T>(dailyItems: dailyItems, timeZone: timeZone) else {
  16. return nil
  17. }
  18. self.unit = unit
  19. self.valueSchedule = valueSchedule
  20. }
  21. init(unit: HKUnit, valueSchedule: DailyValueSchedule<T>) {
  22. self.unit = unit
  23. self.valueSchedule = valueSchedule
  24. }
  25. public init?(rawValue: RawValue) {
  26. guard let rawUnit = rawValue["unit"] as? String,
  27. let valueSchedule = DailyValueSchedule<T>(rawValue: rawValue)
  28. else
  29. {
  30. return nil
  31. }
  32. self.unit = HKUnit(from: rawUnit)
  33. self.valueSchedule = valueSchedule
  34. }
  35. public var items: [RepeatingScheduleValue<T>] {
  36. return valueSchedule.items
  37. }
  38. public var timeZone: TimeZone {
  39. get {
  40. return valueSchedule.timeZone
  41. }
  42. set {
  43. valueSchedule.timeZone = newValue
  44. }
  45. }
  46. public var rawValue: RawValue {
  47. var rawValue = valueSchedule.rawValue
  48. rawValue["unit"] = unit.unitString
  49. return rawValue
  50. }
  51. public func between(start startDate: Date, end endDate: Date) -> [AbsoluteScheduleValue<T>] {
  52. return valueSchedule.between(start: startDate, end: endDate)
  53. }
  54. public func value(at time: Date) -> T {
  55. return valueSchedule.value(at: time)
  56. }
  57. }
  58. extension DailyQuantitySchedule: Codable where T: Codable {
  59. public init(from decoder: Decoder) throws {
  60. let container = try decoder.container(keyedBy: CodingKeys.self)
  61. self.unit = HKUnit(from: try container.decode(String.self, forKey: .unit))
  62. self.valueSchedule = try container.decode(DailyValueSchedule<T>.self, forKey: .valueSchedule)
  63. }
  64. public func encode(to encoder: Encoder) throws {
  65. var container = encoder.container(keyedBy: CodingKeys.self)
  66. try container.encode(unit.unitString, forKey: .unit)
  67. try container.encode(valueSchedule, forKey: .valueSchedule)
  68. }
  69. private enum CodingKeys: String, CodingKey {
  70. case unit
  71. case valueSchedule
  72. }
  73. }
  74. extension DailyQuantitySchedule: CustomDebugStringConvertible {
  75. public var debugDescription: String {
  76. return String(reflecting: rawValue)
  77. }
  78. }
  79. public typealias SingleQuantitySchedule = DailyQuantitySchedule<Double>
  80. public extension DailyQuantitySchedule where T == Double {
  81. func quantity(at time: Date) -> HKQuantity {
  82. return HKQuantity(unit: unit, doubleValue: valueSchedule.value(at: time))
  83. }
  84. func averageValue() -> Double {
  85. var total: Double = 0
  86. for (index, item) in valueSchedule.items.enumerated() {
  87. var endTime = valueSchedule.maxTimeInterval
  88. if index < valueSchedule.items.endIndex - 1 {
  89. endTime = valueSchedule.items[index + 1].startTime
  90. }
  91. total += (endTime - item.startTime) * item.value
  92. }
  93. return total / valueSchedule.repeatInterval
  94. }
  95. func averageQuantity() -> HKQuantity {
  96. return HKQuantity(unit: unit, doubleValue: averageValue())
  97. }
  98. func lowestValue() -> Double? {
  99. return valueSchedule.items.min(by: { $0.value < $1.value } )?.value
  100. }
  101. var quantities: [RepeatingScheduleValue<HKQuantity>] {
  102. return self.items.map {
  103. RepeatingScheduleValue<HKQuantity>(startTime: $0.startTime,
  104. value: HKQuantity(unit: unit, doubleValue: $0.value))
  105. }
  106. }
  107. func quantities(using unit: HKUnit) -> [RepeatingScheduleValue<HKQuantity>] {
  108. return self.items.map {
  109. RepeatingScheduleValue<HKQuantity>(startTime: $0.startTime,
  110. value: HKQuantity(unit: unit, doubleValue: $0.value))
  111. }
  112. }
  113. init?(unit: HKUnit,
  114. dailyQuantities: [RepeatingScheduleValue<HKQuantity>],
  115. timeZone: TimeZone? = nil)
  116. {
  117. guard let valueSchedule = DailyValueSchedule(
  118. dailyItems: dailyQuantities.map {
  119. RepeatingScheduleValue(startTime: $0.startTime, value: $0.value.doubleValue(for: unit))
  120. },
  121. timeZone: timeZone) else
  122. {
  123. return nil
  124. }
  125. self.unit = unit
  126. self.valueSchedule = valueSchedule
  127. }
  128. }
  129. public extension DailyQuantitySchedule where T == DoubleRange {
  130. init?(unit: HKUnit,
  131. dailyQuantities: [RepeatingScheduleValue<ClosedRange<HKQuantity>>],
  132. timeZone: TimeZone? = nil)
  133. {
  134. guard let valueSchedule = DailyValueSchedule(
  135. dailyItems: dailyQuantities.map {
  136. RepeatingScheduleValue(startTime: $0.startTime, value: $0.value.doubleRange(for: unit))
  137. },
  138. timeZone: timeZone) else
  139. {
  140. return nil
  141. }
  142. self.unit = unit
  143. self.valueSchedule = valueSchedule
  144. }
  145. }
  146. extension DailyQuantitySchedule: Equatable where T: Equatable {
  147. public static func == (lhs: DailyQuantitySchedule<T>, rhs: DailyQuantitySchedule<T>) -> Bool {
  148. return lhs.valueSchedule == rhs.valueSchedule && lhs.unit.unitString == rhs.unit.unitString
  149. }
  150. }
  151. extension DailyQuantitySchedule where T: Numeric {
  152. public static func * (lhs: DailyQuantitySchedule, rhs: DailyQuantitySchedule) -> DailyQuantitySchedule {
  153. let unit = lhs.unit.unitMultiplied(by: rhs.unit)
  154. let schedule = DailyValueSchedule.zip(lhs.valueSchedule, rhs.valueSchedule).map(*)
  155. return DailyQuantitySchedule(unit: unit, valueSchedule: schedule)
  156. }
  157. }
  158. extension DailyQuantitySchedule where T: FloatingPoint {
  159. public static func / (lhs: DailyQuantitySchedule, rhs: DailyQuantitySchedule) -> DailyQuantitySchedule {
  160. let unit = lhs.unit.unitDivided(by: rhs.unit)
  161. let schedule = DailyValueSchedule.zip(lhs.valueSchedule, rhs.valueSchedule).map(/)
  162. return DailyQuantitySchedule(unit: unit, valueSchedule: schedule)
  163. }
  164. }