diff --git a/LoopFollow/Controllers/Nightscout/Treatments/Basals.swift b/LoopFollow/Controllers/Nightscout/Treatments/Basals.swift index d0acc268..3ff9c939 100644 --- a/LoopFollow/Controllers/Nightscout/Treatments/Basals.swift +++ b/LoopFollow/Controllers/Nightscout/Treatments/Basals.swift @@ -7,200 +7,148 @@ // import Foundation + extension MainViewController { + // NS Temp Basal Response Processor func processNSBasals(entries: [[String:AnyObject]]) { infoManager.clearInfoData(type: .basal) - if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: Basal") } - // due to temp basal durations, we're going to destroy the array and load everything each cycle for the time being. basalData.removeAll() - + var lastEndDot = 0.0 - + var tempArray = entries tempArray.reverse() + for i in 0.. 0 { let priorEntry = tempArray[i - 1] as [String : AnyObject]? - var priorBasalDate: String - if priorEntry?["timestamp"] != nil { - priorBasalDate = priorEntry?["timestamp"] as! String - } else if currentEntry?["created_at"] != nil { - priorBasalDate = priorEntry?["created_at"] as! String - } else { - continue - } - var priorStrippedZone = String(priorBasalDate.dropLast()) - priorStrippedZone = priorStrippedZone.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression) - let priorDateFormatter = DateFormatter() - priorDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" - priorDateFormatter.locale = Locale(identifier: "en_US") - priorDateFormatter.timeZone = TimeZone(abbreviation: "UTC") - guard let priorDateString = dateFormatter.date(from: priorStrippedZone) else { continue } - let priorDateTimeStamp = priorDateString.timeIntervalSince1970 - let priorDuration = priorEntry?["duration"] as? Double ?? 0.0 - // if difference between time stamps is greater than the duration of the last entry, there is a gap. Give a 15 second leeway on the timestamp - if Double( dateTimeStamp - priorDateTimeStamp ) > Double( (priorDuration * 60) + 15 ) { - - var scheduled = 0.0 - var midGap = false - var midGapTime: TimeInterval = 0 - var midGapValue: Double = 0 - // cycle through basal profiles. - // TODO figure out how to deal with profile changes that happen mid-gap - for b in 0..= basalScheduleData[b].date { - scheduled = basalScheduleData[b].basalRate - - // deal with mid-gap scheduled basal change - // don't do it on the last scheudled basal entry - if b < self.basalScheduleData.count - 1 { - if dateTimeStamp > self.basalScheduleData[b + 1].date { - // midGap = true - // TODO: finish this to handle mid-gap items without crashing from overlapping entries - midGapTime = self.basalScheduleData[b + 1].date - midGapValue = self.basalScheduleData[b + 1].basalRate + let priorDateStr = priorEntry?["timestamp"] as? String + ?? priorEntry?["created_at"] as? String + if let rawPrior = priorDateStr, + let priorDateParsed = NightscoutUtils.parseDate(rawPrior) { + + let priorDateTimeStamp = priorDateParsed.timeIntervalSince1970 + let priorDuration = priorEntry?["duration"] as? Double ?? 0.0 + + if (dateTimeStamp - priorDateTimeStamp) > (priorDuration * 60) + 15 { + var scheduled = 0.0 + var midGap = false + var midGapTime: TimeInterval = 0 + var midGapValue: Double = 0 + + for b in 0..= basalScheduleData[b].date { + scheduled = basalScheduleData[b].basalRate + if b < basalScheduleData.count - 1 { + if dateTimeStamp > basalScheduleData[b + 1].date { + midGap = true + midGapTime = basalScheduleData[b + 1].date + midGapValue = basalScheduleData[b + 1].basalRate + } } } - } - - } - - // Make the starting dot at the last ending dot - let startDot = basalGraphStruct(basalRate: scheduled, date: Double(priorDateTimeStamp + (priorDuration * 60))) - basalData.append(startDot) - - - if midGap { - // Make the ending dot at the new scheduled basal - let endDot1 = basalGraphStruct(basalRate: scheduled, date: Double(midGapTime)) - basalData.append(endDot1) - // Make the starting dot at the scheduled Time - let startDot2 = basalGraphStruct(basalRate: midGapValue, date: Double(midGapTime)) - basalData.append(startDot2) - // Make the ending dot at the new basal value - let endDot2 = basalGraphStruct(basalRate: midGapValue, date: Double(dateTimeStamp)) - basalData.append(endDot2) - - } else { - // Make the ending dot at the new starting dot - let endDot = basalGraphStruct(basalRate: scheduled, date: Double(dateTimeStamp)) - basalData.append(endDot) + + let startDot = basalGraphStruct(basalRate: scheduled, + date: priorDateTimeStamp + (priorDuration * 60)) + basalData.append(startDot) + + if midGap { + let endDot1 = basalGraphStruct(basalRate: scheduled, date: midGapTime) + basalData.append(endDot1) + let startDot2 = basalGraphStruct(basalRate: midGapValue, date: midGapTime) + basalData.append(startDot2) + let endDot2 = basalGraphStruct(basalRate: midGapValue, date: dateTimeStamp) + basalData.append(endDot2) + } else { + let endDot = basalGraphStruct(basalRate: scheduled, date: dateTimeStamp) + basalData.append(endDot) + } } - - } } - - // Make the starting dot - let startDot = basalGraphStruct(basalRate: basalRate, date: Double(dateTimeStamp)) + + // Start dot + let startDot = basalGraphStruct(basalRate: basalRate, date: dateTimeStamp) basalData.append(startDot) - - // Make the ending dot - // If it's the last one and has no duration, extend it for 30 minutes past the start. Otherwise set ending at duration - // duration is already set to 0 if there is no duration set on it. - //if i == tempArray.count - 1 && dateTimeStamp + duration <= dateTimeUtils.getNowTimeIntervalUTC() { - if i == tempArray.count - 1 && duration == 0.0 { - lastEndDot = dateTimeStamp + (30 * 60) - } else { - lastEndDot = dateTimeStamp + (duration * 60) + + // End dot + var lastDot = dateTimeStamp + (duration * 60) + if i == tempArray.count - 1, duration == 0.0 { + lastDot = dateTimeStamp + (30 * 60) } latestBasal = Localizer.formatToLocalizedString(basalRate, maxFractionDigits: 2, minFractionDigits: 0) - // Double check for overlaps of incorrectly ended TBRs and sent it to end when the next one starts if it finds a discrepancy + // Overlap check if i < tempArray.count - 1 { let nextEntry = tempArray[i + 1] as [String : AnyObject]? - var nextBasalDate: String - if nextEntry?["timestamp"] != nil { - nextBasalDate = nextEntry?["timestamp"] as! String - } else if currentEntry?["created_at"] != nil { - nextBasalDate = nextEntry?["created_at"] as! String - } else { - continue - } - var nextStrippedZone = String(nextBasalDate.dropLast()) - nextStrippedZone = nextStrippedZone.replacingOccurrences(of: "\\.\\d+", with: "", options: .regularExpression) - let nextDateFormatter = DateFormatter() - nextDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" - nextDateFormatter.locale = Locale(identifier: "en_US") - nextDateFormatter.timeZone = TimeZone(abbreviation: "UTC") - guard let nextDateString = dateFormatter.date(from: nextStrippedZone) else { continue } - let nextDateTimeStamp = nextDateString.timeIntervalSince1970 - if nextDateTimeStamp < (dateTimeStamp + (duration * 60)) { - lastEndDot = nextDateTimeStamp + let nextDateStr = nextEntry?["timestamp"] as? String + ?? nextEntry?["created_at"] as? String + if let rawNext = nextDateStr, + let nextDateParsed = NightscoutUtils.parseDate(rawNext) { + + let nextDateTimeStamp = nextDateParsed.timeIntervalSince1970 + if nextDateTimeStamp < (dateTimeStamp + (duration * 60)) { + lastDot = nextDateTimeStamp + } } } - - let endDot = basalGraphStruct(basalRate: basalRate, date: Double(lastEndDot)) + + let endDot = basalGraphStruct(basalRate: basalRate, date: lastDot) basalData.append(endDot) - - + lastEndDot = lastDot } - - // If last basal was prior to right now, we need to create one last scheduled entry + + // If last basal was prior to right now, we need to create one last scheduled entry if lastEndDot <= dateTimeUtils.getNowTimeIntervalUTC() { var scheduled = 0.0 - // cycle through basal profiles. - // TODO figure out how to deal with profile changes that happen mid-gap - for b in 0..= scheduleTimeToday { scheduled = basalProfile[b].value } } - - latestBasal = Localizer.formatToLocalizedString(scheduled, maxFractionDigits: 2, minFractionDigits: 0) - // Make the starting dot at the last ending dot - let startDot = basalGraphStruct(basalRate: scheduled, date: Double(lastEndDot)) + + latestBasal = Localizer.formatToLocalizedString(scheduled, + maxFractionDigits: 2, + minFractionDigits: 0) + + let startDot = basalGraphStruct(basalRate: scheduled, date: lastEndDot) basalData.append(startDot) - - // Make the ending dot 10 minutes after now - let endDot = basalGraphStruct(basalRate: scheduled, date: Double(Date().timeIntervalSince1970 + (60 * 10))) + + let endDot = basalGraphStruct(basalRate: scheduled, + date: Date().timeIntervalSince1970 + (60 * 10)) basalData.append(endDot) - } + if UserDefaultsRepository.graphBasal.value { updateBasalGraph() } - if let profileBasal = profileManager.currentBasal(), profileBasal != latestBasal { + if let profileBasal = profileManager.currentBasal(), + profileBasal != latestBasal { latestBasal = "\(profileBasal) → \(latestBasal)" } infoManager.updateInfoData(type: .basal, value: latestBasal) diff --git a/LoopFollow/Controllers/Nightscout/Treatments/Carbs.swift b/LoopFollow/Controllers/Nightscout/Treatments/Carbs.swift index 988fe3e5..16a70816 100644 --- a/LoopFollow/Controllers/Nightscout/Treatments/Carbs.swift +++ b/LoopFollow/Controllers/Nightscout/Treatments/Carbs.swift @@ -74,7 +74,6 @@ extension MainViewController { } guard let date = NightscoutUtils.parseDate(carbDate) else { - print("Unable to parse date from: \(carbDate)") continue } diff --git a/LoopFollow/Helpers/NightscoutUtils.swift b/LoopFollow/Helpers/NightscoutUtils.swift index baf6b074..05a58467 100644 --- a/LoopFollow/Helpers/NightscoutUtils.swift +++ b/LoopFollow/Helpers/NightscoutUtils.swift @@ -234,24 +234,33 @@ class NightscoutUtils { task.resume() } - static func parseDate(_ dateString: String) -> Date? { - let dateFormatterWithMilliseconds = DateFormatter() - dateFormatterWithMilliseconds.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" - dateFormatterWithMilliseconds.timeZone = TimeZone(abbreviation: "UTC") - dateFormatterWithMilliseconds.locale = Locale(identifier: "en_US_POSIX") - - let dateFormatterWithoutMilliseconds = DateFormatter() - dateFormatterWithoutMilliseconds.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" - dateFormatterWithoutMilliseconds.timeZone = TimeZone(abbreviation: "UTC") - dateFormatterWithoutMilliseconds.locale = Locale(identifier: "en_US_POSIX") - - if let date = dateFormatterWithMilliseconds.date(from: dateString) { - return date - } else if let date = dateFormatterWithoutMilliseconds.date(from: dateString) { - return date + static func parseDate(_ rawString: String) -> Date? { + var mutableDate = rawString + + if mutableDate.hasSuffix("Z") { + mutableDate = String(mutableDate.dropLast()) + } + else if let offsetRange = mutableDate.range(of: "[\\+\\-]\\d{2}:\\d{2}$", + options: .regularExpression) { + mutableDate.removeSubrange(offsetRange) } - return nil + mutableDate = mutableDate.replacingOccurrences( + of: "\\.\\d+", + with: "", + options: .regularExpression + ) + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + dateFormatter.locale = Locale(identifier: "en_US") + dateFormatter.timeZone = TimeZone(abbreviation: "UTC") + + let result = dateFormatter.date(from: mutableDate) + if result == nil { + print("Unable to parse string: '\(mutableDate)'") + } + return result } static func retrieveJWTToken() async throws -> String {