@interval-health/capacitor-health 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -351,9 +351,14 @@ final class Health {
|
|
|
351
351
|
|
|
352
352
|
// For all other data types, use the standard query approach
|
|
353
353
|
let sampleType = try dataType.sampleType()
|
|
354
|
-
|
|
354
|
+
// Use .strictStartDate to include samples that start within the range (even if they end after endDate)
|
|
355
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
355
356
|
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)
|
|
356
|
-
|
|
357
|
+
|
|
358
|
+
// Use no limit by default to ensure all samples are retrieved
|
|
359
|
+
// This is important for data types like sleep, heart rate, steps, etc.
|
|
360
|
+
// where a single day or long date ranges can have many samples
|
|
361
|
+
let queryLimit = limit ?? HKObjectQueryNoLimit
|
|
357
362
|
|
|
358
363
|
let query = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: queryLimit, sortDescriptors: [sortDescriptor]) { [weak self] _, samples, error in
|
|
359
364
|
guard let self = self else { return }
|
|
@@ -842,16 +847,23 @@ final class Health {
|
|
|
842
847
|
}
|
|
843
848
|
|
|
844
849
|
private func processSleepSamples(_ samples: [HKCategorySample]) -> [[String: Any]] {
|
|
845
|
-
// Filter for
|
|
850
|
+
// Filter for sleep stage data - include both detailed stages (iOS 16+) and legacy values
|
|
851
|
+
// This ensures we capture data from all sources (Apple Watch, third-party apps, etc.)
|
|
846
852
|
let detailedSamples = samples.filter { sample in
|
|
847
853
|
if #available(iOS 16.0, *) {
|
|
854
|
+
// Include all sleep-related values:
|
|
855
|
+
// - Detailed stages: asleepDeep, asleepREM, asleepCore, awake (iOS 16+)
|
|
856
|
+
// - Legacy/basic: asleepUnspecified, asleep (for older data or third-party apps)
|
|
857
|
+
// - Exclude: inBed (not actual sleep, just time in bed)
|
|
848
858
|
return sample.value == HKCategoryValueSleepAnalysis.asleepDeep.rawValue ||
|
|
849
859
|
sample.value == HKCategoryValueSleepAnalysis.asleepREM.rawValue ||
|
|
850
860
|
sample.value == HKCategoryValueSleepAnalysis.asleepCore.rawValue ||
|
|
851
|
-
sample.value == HKCategoryValueSleepAnalysis.awake.rawValue
|
|
861
|
+
sample.value == HKCategoryValueSleepAnalysis.awake.rawValue ||
|
|
862
|
+
sample.value == HKCategoryValueSleepAnalysis.asleepUnspecified.rawValue
|
|
852
863
|
} else {
|
|
853
|
-
// For older iOS,
|
|
854
|
-
return
|
|
864
|
+
// For older iOS, include asleep and awake, exclude inBed
|
|
865
|
+
return sample.value == HKCategoryValueSleepAnalysis.asleep.rawValue ||
|
|
866
|
+
sample.value == HKCategoryValueSleepAnalysis.awake.rawValue
|
|
855
867
|
}
|
|
856
868
|
}
|
|
857
869
|
|
|
@@ -927,6 +939,7 @@ final class Health {
|
|
|
927
939
|
var remMinutes: Double
|
|
928
940
|
var coreMinutes: Double
|
|
929
941
|
var awakeMinutes: Double
|
|
942
|
+
var unspecifiedMinutes: Double // For legacy/third-party sleep data without stage details
|
|
930
943
|
}
|
|
931
944
|
|
|
932
945
|
var sleepByDate: [String: DayData] = [:]
|
|
@@ -944,7 +957,8 @@ final class Health {
|
|
|
944
957
|
deepMinutes: 0,
|
|
945
958
|
remMinutes: 0,
|
|
946
959
|
coreMinutes: 0,
|
|
947
|
-
awakeMinutes: 0
|
|
960
|
+
awakeMinutes: 0,
|
|
961
|
+
unspecifiedMinutes: 0
|
|
948
962
|
)
|
|
949
963
|
}
|
|
950
964
|
|
|
@@ -962,6 +976,10 @@ final class Health {
|
|
|
962
976
|
sleepByDate[dateString]!.coreMinutes += minutes
|
|
963
977
|
} else if segment.stage == "HKCategoryValueSleepAnalysisAwake" {
|
|
964
978
|
sleepByDate[dateString]!.awakeMinutes += minutes
|
|
979
|
+
} else if segment.stage == "HKCategoryValueSleepAnalysisAsleepUnspecified" ||
|
|
980
|
+
segment.stage == "HKCategoryValueSleepAnalysisAsleep" {
|
|
981
|
+
// Legacy sleep data or third-party apps that don't provide stage details
|
|
982
|
+
sleepByDate[dateString]!.unspecifiedMinutes += minutes
|
|
965
983
|
}
|
|
966
984
|
}
|
|
967
985
|
}
|
|
@@ -974,14 +992,15 @@ final class Health {
|
|
|
974
992
|
let remHours = data.remMinutes / 60.0
|
|
975
993
|
let coreHours = data.coreMinutes / 60.0
|
|
976
994
|
let awakeHours = data.awakeMinutes / 60.0
|
|
977
|
-
let
|
|
995
|
+
let unspecifiedHours = data.unspecifiedMinutes / 60.0
|
|
996
|
+
// Include unspecified sleep in total (this captures legacy/third-party app data)
|
|
997
|
+
let totalSleepHours = deepHours + remHours + coreHours + unspecifiedHours
|
|
978
998
|
|
|
979
|
-
// Calculate time in bed
|
|
999
|
+
// Calculate time in bed by summing each session's duration
|
|
1000
|
+
// This avoids counting gaps between sessions (e.g., nap + main sleep)
|
|
980
1001
|
var timeInBed = 0.0
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
let wakeTime = data.sessions.last!.end
|
|
984
|
-
timeInBed = wakeTime.timeIntervalSince(bedtime) / 3600.0
|
|
1002
|
+
for session in data.sessions {
|
|
1003
|
+
timeInBed += session.end.timeIntervalSince(session.start) / 3600.0
|
|
985
1004
|
}
|
|
986
1005
|
|
|
987
1006
|
// Calculate efficiency
|
|
@@ -1011,6 +1030,7 @@ final class Health {
|
|
|
1011
1030
|
"deepSleep": round(deepHours * 10) / 10,
|
|
1012
1031
|
"remSleep": round(remHours * 10) / 10,
|
|
1013
1032
|
"coreSleep": round(coreHours * 10) / 10,
|
|
1033
|
+
"unspecifiedSleep": round(unspecifiedHours * 10) / 10, // Legacy/third-party sleep without stage details
|
|
1014
1034
|
"awakeTime": round(awakeHours * 10) / 10,
|
|
1015
1035
|
"timeInBed": round(timeInBed * 10) / 10,
|
|
1016
1036
|
"efficiency": efficiency,
|
|
@@ -1366,6 +1386,12 @@ final class Health {
|
|
|
1366
1386
|
private func processMobilityData(startDate: Date, endDate: Date, completion: @escaping (Result<[[String: Any]], Error>) -> Void) {
|
|
1367
1387
|
let calendar = Calendar.current
|
|
1368
1388
|
|
|
1389
|
+
// Calculate the local date range that corresponds to the input UTC date range
|
|
1390
|
+
let startDateComponents = calendar.dateComponents([.year, .month, .day], from: startDate)
|
|
1391
|
+
let endDateComponents = calendar.dateComponents([.year, .month, .day], from: endDate)
|
|
1392
|
+
let localStartDateString = String(format: "%04d-%02d-%02d", startDateComponents.year!, startDateComponents.month!, startDateComponents.day!)
|
|
1393
|
+
let localEndDateString = String(format: "%04d-%02d-%02d", endDateComponents.year!, endDateComponents.month!, endDateComponents.day!)
|
|
1394
|
+
|
|
1369
1395
|
// Define all mobility quantity types
|
|
1370
1396
|
let mobilityTypes: [(HKQuantityTypeIdentifier, String)] = [
|
|
1371
1397
|
(.walkingSpeed, "walkingSpeed"),
|
|
@@ -1397,7 +1423,7 @@ final class Health {
|
|
|
1397
1423
|
|
|
1398
1424
|
group.enter()
|
|
1399
1425
|
|
|
1400
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1426
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1401
1427
|
let query = HKSampleQuery(sampleType: quantityType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1402
1428
|
defer { group.leave() }
|
|
1403
1429
|
|
|
@@ -1442,7 +1468,7 @@ final class Health {
|
|
|
1442
1468
|
lock.unlock()
|
|
1443
1469
|
|
|
1444
1470
|
case .walkingAsymmetryPercentage:
|
|
1445
|
-
//
|
|
1471
|
+
// HKUnit.percent() returns fractional value (0.05 = 5%), multiply by 100 for percentage
|
|
1446
1472
|
value = sample.quantity.doubleValue(for: HKUnit.percent()) * 100
|
|
1447
1473
|
lock.lock()
|
|
1448
1474
|
if asymmetryMap[dateString] == nil {
|
|
@@ -1452,7 +1478,7 @@ final class Health {
|
|
|
1452
1478
|
lock.unlock()
|
|
1453
1479
|
|
|
1454
1480
|
case .walkingDoubleSupportPercentage:
|
|
1455
|
-
//
|
|
1481
|
+
// HKUnit.percent() returns fractional value (0.30 = 30%), multiply by 100 for percentage
|
|
1456
1482
|
value = sample.quantity.doubleValue(for: HKUnit.percent()) * 100
|
|
1457
1483
|
lock.lock()
|
|
1458
1484
|
if doubleSupportMap[dateString] == nil {
|
|
@@ -1505,10 +1531,15 @@ final class Health {
|
|
|
1505
1531
|
allDates.formUnion(stairSpeedMap.keys)
|
|
1506
1532
|
allDates.formUnion(sixMinWalkMap.keys)
|
|
1507
1533
|
|
|
1534
|
+
// Filter dates to only include those within the requested local date range
|
|
1535
|
+
let filteredDates = allDates.filter { date in
|
|
1536
|
+
return date >= localStartDateString && date <= localEndDateString
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1508
1539
|
// Create mobility data array with aggregated daily averages
|
|
1509
1540
|
var mobilityData: [[String: Any]] = []
|
|
1510
1541
|
|
|
1511
|
-
for date in
|
|
1542
|
+
for date in filteredDates.sorted() {
|
|
1512
1543
|
var result: [String: Any] = ["date": date]
|
|
1513
1544
|
|
|
1514
1545
|
// Average all measurements for the day
|
|
@@ -1556,6 +1587,13 @@ final class Health {
|
|
|
1556
1587
|
let group = DispatchGroup()
|
|
1557
1588
|
let lock = NSLock()
|
|
1558
1589
|
|
|
1590
|
+
// Calculate the local date range that corresponds to the input UTC date range
|
|
1591
|
+
// This ensures we only return data for dates that fall within the requested range
|
|
1592
|
+
let startDateComponents = calendar.dateComponents([.year, .month, .day], from: startDate)
|
|
1593
|
+
let endDateComponents = calendar.dateComponents([.year, .month, .day], from: endDate)
|
|
1594
|
+
let localStartDateString = String(format: "%04d-%02d-%02d", startDateComponents.year!, startDateComponents.month!, startDateComponents.day!)
|
|
1595
|
+
let localEndDateString = String(format: "%04d-%02d-%02d", endDateComponents.year!, endDateComponents.month!, endDateComponents.day!)
|
|
1596
|
+
|
|
1559
1597
|
// Maps to store values by date for each metric
|
|
1560
1598
|
var stepsMap: [String: Double] = [:]
|
|
1561
1599
|
var distanceMap: [String: Double] = [:]
|
|
@@ -1569,7 +1607,7 @@ final class Health {
|
|
|
1569
1607
|
// === STEPS ===
|
|
1570
1608
|
if let stepsType = HKObjectType.quantityType(forIdentifier: .stepCount) {
|
|
1571
1609
|
group.enter()
|
|
1572
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1610
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1573
1611
|
let query = HKSampleQuery(sampleType: stepsType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1574
1612
|
defer { group.leave() }
|
|
1575
1613
|
|
|
@@ -1598,7 +1636,7 @@ final class Health {
|
|
|
1598
1636
|
// === DISTANCE (Walking + Running) ===
|
|
1599
1637
|
if let distanceType = HKObjectType.quantityType(forIdentifier: .distanceWalkingRunning) {
|
|
1600
1638
|
group.enter()
|
|
1601
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1639
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1602
1640
|
let query = HKSampleQuery(sampleType: distanceType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1603
1641
|
defer { group.leave() }
|
|
1604
1642
|
|
|
@@ -1629,7 +1667,7 @@ final class Health {
|
|
|
1629
1667
|
// === FLIGHTS CLIMBED ===
|
|
1630
1668
|
if let flightsType = HKObjectType.quantityType(forIdentifier: .flightsClimbed) {
|
|
1631
1669
|
group.enter()
|
|
1632
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1670
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1633
1671
|
let query = HKSampleQuery(sampleType: flightsType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1634
1672
|
defer { group.leave() }
|
|
1635
1673
|
|
|
@@ -1658,7 +1696,7 @@ final class Health {
|
|
|
1658
1696
|
// === ACTIVE ENERGY ===
|
|
1659
1697
|
if let activeEnergyType = HKObjectType.quantityType(forIdentifier: .activeEnergyBurned) {
|
|
1660
1698
|
group.enter()
|
|
1661
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1699
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1662
1700
|
let query = HKSampleQuery(sampleType: activeEnergyType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1663
1701
|
defer { group.leave() }
|
|
1664
1702
|
|
|
@@ -1687,7 +1725,7 @@ final class Health {
|
|
|
1687
1725
|
// === EXERCISE MINUTES ===
|
|
1688
1726
|
if let exerciseType = HKObjectType.quantityType(forIdentifier: .appleExerciseTime) {
|
|
1689
1727
|
group.enter()
|
|
1690
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1728
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1691
1729
|
let query = HKSampleQuery(sampleType: exerciseType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1692
1730
|
defer { group.leave() }
|
|
1693
1731
|
|
|
@@ -1716,7 +1754,7 @@ final class Health {
|
|
|
1716
1754
|
// === STAND HOURS ===
|
|
1717
1755
|
if let standHourType = HKObjectType.categoryType(forIdentifier: .appleStandHour) {
|
|
1718
1756
|
group.enter()
|
|
1719
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1757
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1720
1758
|
let query = HKSampleQuery(sampleType: standHourType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1721
1759
|
defer { group.leave() }
|
|
1722
1760
|
|
|
@@ -1769,8 +1807,14 @@ final class Health {
|
|
|
1769
1807
|
allDates.formUnion(exerciseMinutesMap.keys)
|
|
1770
1808
|
allDates.formUnion(standHoursMap.keys)
|
|
1771
1809
|
|
|
1810
|
+
// Filter dates to only include those within the requested local date range
|
|
1811
|
+
// This prevents returning data from dates outside the user's intended range
|
|
1812
|
+
let filteredDates = allDates.filter { date in
|
|
1813
|
+
return date >= localStartDateString && date <= localEndDateString
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1772
1816
|
// Create activity data array matching the reference format
|
|
1773
|
-
let activityData: [[String: Any]] =
|
|
1817
|
+
let activityData: [[String: Any]] = filteredDates.sorted().compactMap { date in
|
|
1774
1818
|
var result: [String: Any] = ["date": date]
|
|
1775
1819
|
|
|
1776
1820
|
// Add each metric if available, with proper rounding
|
|
@@ -1812,6 +1856,12 @@ final class Health {
|
|
|
1812
1856
|
let group = DispatchGroup()
|
|
1813
1857
|
let lock = NSLock()
|
|
1814
1858
|
|
|
1859
|
+
// Calculate the local date range that corresponds to the input UTC date range
|
|
1860
|
+
let startDateComponents = calendar.dateComponents([.year, .month, .day], from: startDate)
|
|
1861
|
+
let endDateComponents = calendar.dateComponents([.year, .month, .day], from: endDate)
|
|
1862
|
+
let localStartDateString = String(format: "%04d-%02d-%02d", startDateComponents.year!, startDateComponents.month!, startDateComponents.day!)
|
|
1863
|
+
let localEndDateString = String(format: "%04d-%02d-%02d", endDateComponents.year!, endDateComponents.month!, endDateComponents.day!)
|
|
1864
|
+
|
|
1815
1865
|
// Maps to store values by date for each metric
|
|
1816
1866
|
struct HeartRateStats {
|
|
1817
1867
|
var sum: Double = 0
|
|
@@ -1832,7 +1882,7 @@ final class Health {
|
|
|
1832
1882
|
// === HEART RATE ===
|
|
1833
1883
|
if let heartRateType = HKObjectType.quantityType(forIdentifier: .heartRate) {
|
|
1834
1884
|
group.enter()
|
|
1835
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1885
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1836
1886
|
let query = HKSampleQuery(sampleType: heartRateType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1837
1887
|
defer { group.leave() }
|
|
1838
1888
|
|
|
@@ -1866,7 +1916,7 @@ final class Health {
|
|
|
1866
1916
|
// === RESTING HEART RATE ===
|
|
1867
1917
|
if let restingHRType = HKObjectType.quantityType(forIdentifier: .restingHeartRate) {
|
|
1868
1918
|
group.enter()
|
|
1869
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1919
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1870
1920
|
let query = HKSampleQuery(sampleType: restingHRType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1871
1921
|
defer { group.leave() }
|
|
1872
1922
|
|
|
@@ -1895,7 +1945,7 @@ final class Health {
|
|
|
1895
1945
|
// === VO2 MAX ===
|
|
1896
1946
|
if let vo2MaxType = HKObjectType.quantityType(forIdentifier: .vo2Max) {
|
|
1897
1947
|
group.enter()
|
|
1898
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1948
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1899
1949
|
let query = HKSampleQuery(sampleType: vo2MaxType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1900
1950
|
defer { group.leave() }
|
|
1901
1951
|
|
|
@@ -1924,7 +1974,7 @@ final class Health {
|
|
|
1924
1974
|
// === HRV (Heart Rate Variability SDNN) ===
|
|
1925
1975
|
if let hrvType = HKObjectType.quantityType(forIdentifier: .heartRateVariabilitySDNN) {
|
|
1926
1976
|
group.enter()
|
|
1927
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
1977
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1928
1978
|
let query = HKSampleQuery(sampleType: hrvType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1929
1979
|
defer { group.leave() }
|
|
1930
1980
|
|
|
@@ -1953,7 +2003,7 @@ final class Health {
|
|
|
1953
2003
|
// === SPO2 (Blood Oxygen Saturation) ===
|
|
1954
2004
|
if let spo2Type = HKObjectType.quantityType(forIdentifier: .oxygenSaturation) {
|
|
1955
2005
|
group.enter()
|
|
1956
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
2006
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1957
2007
|
let query = HKSampleQuery(sampleType: spo2Type, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1958
2008
|
defer { group.leave() }
|
|
1959
2009
|
|
|
@@ -1970,7 +2020,7 @@ final class Health {
|
|
|
1970
2020
|
let dateComponents = calendar.dateComponents([.year, .month, .day], from: sample.startDate)
|
|
1971
2021
|
let dateString = String(format: "%04d-%02d-%02d", dateComponents.year!, dateComponents.month!, dateComponents.day!)
|
|
1972
2022
|
|
|
1973
|
-
//
|
|
2023
|
+
// HKUnit.percent() returns fractional value (0.98 = 98%), multiply by 100 for percentage
|
|
1974
2024
|
let value = sample.quantity.doubleValue(for: HKUnit.percent()) * 100
|
|
1975
2025
|
lock.lock()
|
|
1976
2026
|
if spo2Map[dateString] == nil {
|
|
@@ -1986,7 +2036,7 @@ final class Health {
|
|
|
1986
2036
|
// === RESPIRATION RATE ===
|
|
1987
2037
|
if let respirationRateType = HKObjectType.quantityType(forIdentifier: .respiratoryRate) {
|
|
1988
2038
|
group.enter()
|
|
1989
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
2039
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
1990
2040
|
let query = HKSampleQuery(sampleType: respirationRateType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: nil) { _, samples, error in
|
|
1991
2041
|
defer { group.leave() }
|
|
1992
2042
|
|
|
@@ -2040,8 +2090,13 @@ final class Health {
|
|
|
2040
2090
|
allDates.formUnion(spo2Map.keys)
|
|
2041
2091
|
allDates.formUnion(respirationRateMap.keys)
|
|
2042
2092
|
|
|
2093
|
+
// Filter dates to only include those within the requested local date range
|
|
2094
|
+
let filteredDates = allDates.filter { date in
|
|
2095
|
+
return date >= localStartDateString && date <= localEndDateString
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2043
2098
|
// Create heart data array matching the TypeScript format
|
|
2044
|
-
let heartData: [[String: Any]] =
|
|
2099
|
+
let heartData: [[String: Any]] = filteredDates.sorted().compactMap { date in
|
|
2045
2100
|
var result: [String: Any] = ["date": date]
|
|
2046
2101
|
|
|
2047
2102
|
// Heart Rate (avg, min, max, count)
|
|
@@ -2090,7 +2145,7 @@ final class Health {
|
|
|
2090
2145
|
|
|
2091
2146
|
private func processWorkoutData(startDate: Date, endDate: Date, limit: Int?, ascending: Bool, completion: @escaping (Result<[[String: Any]], Error>) -> Void) {
|
|
2092
2147
|
let workoutType = HKObjectType.workoutType()
|
|
2093
|
-
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options:
|
|
2148
|
+
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
|
|
2094
2149
|
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending)
|
|
2095
2150
|
let queryLimit = limit ?? HKObjectQueryNoLimit
|
|
2096
2151
|
|
package/package.json
CHANGED