For ages I’ve always wanted to export HealthKit data so that I can visualise it. I was incredibly happy to find that Scriptable has a Health API that lets you retrieve all your data points. This script loops over several HealthKit measurement types and exports them to either json, csv, or sql and writes them to your iCloud storage.
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-blue; icon-glyph: beaker;
const config = {
sampleLimit: 100000, // The maximum number of samples pulled back from each category
startDate: new Date("2018-01-01"), // The starting date to provide samples from
outputFormat: "json", // One of "json", "csv", "sql"
outputFileName: "health", // Filename to use for export
debug: false, // Output debug data
measurements: {
height: 'cm',
bodyMass: 'kg',
bodyMassIndex: 'count',
leanBodyMass: 'kg',
bodyFatPercentage: '%',
heartRate: 'count/min',
bodyTemperature: 'degC',
bloodPressureSystolic: 'cmAq',
bloodPressureDiastolic: 'cmAq',
bloodGlucose: 'mmol/L',
insulinDelivery: 'mg',
respiratoryRate: 'count/min',
stepCount: 'count',
distanceWalkingRunning: 'm',
distanceCycling: 'm',
pushCount: 'count',
distanceWheelchair: 'm',
swimmingStrokeCount: 'count',
distanceSwimming: 'm',
basalEnergyBurned: 'cal',
activeEnergyBurned: 'cal',
flightsClimbed: 'count',
nikeFuel: 'count',
appleExerciseTime: 'min',
basalBodyTemperature: 'degC'
}
}
const health = new Health()
function leftPad(string, character, length) {
let output = `${string}`
while (output.length < length) {
output = `${character}${output}`
}
return output
}
//YYYY-MM-DD H:i:s
function formatSqlDate(date) {
let year = date.getFullYear()
let month = leftPad(date.getMonth() + 1, '0', 2)
let day = leftPad(date.getDate(), '0', 2)
let hour = leftPad(date.getHours(), '0', 2)
let minute = leftPad(date.getMinutes(), '0', 2)
let second = leftPad(date.getSeconds(), '0', 2)
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
}
const debugLog = (message) => {
if (config.debug) {
console.log(`[DEBUG] ${message}`)
}
}
const output = {}
for (const measurement in config.measurements) {
const unit = config.measurements[measurement]
if (unit === '' || unit === null) {
continue
}
console.log(`Gathering ${measurement} in ${unit}`)
health.setTypeIdentifier(measurement)
health.setUnit(unit)
health.setLimit(config.sampleLimit)
health.setDescendingSorting()
let samples = await health.quantitySamples()
for (const sample of samples) {
sample.startDate = new Date(sample.startDate)
sample.endDate = new Date(sample.endDate)
}
debugLog(`Found ${samples.length} samples`)
if (samples.length > 0) {
debugLog(`First sample starts at ${samples[0].startDate}`)
debugLog(`Last sample starts at ${samples[samples.length-1].startDate}`)
}
if (config.startDate !== null) {
samples = samples.filter(sample => sample.startDate >= config.startDate)
}
output[measurement] = {
unit: unit,
samples: samples
}
}
// Format and write file
console.log(`Writing to ${config.outputFileName}.${config.outputFormat}`)
const dataFormatters = {};
dataFormatters.json = function (data) {
return JSON.stringify(data)
}
dataFormatters.csv = function (data) {
let output = "measurement,unit,startDate,endDate,value\n"
for (const measurementType in data) {
const unit = data[measurementType]['unit']
for (const measurementData of data[measurementType]['samples']) {
output += `${measurementType},${unit},${formatSqlDate(measurementData.startDate)},${formatSqlDate(measurementData.endDate)},${measurementData.value}\n`
}
}
return output
}
dataFormatters.sql = function (data) {
let output =
`CREATE TABLE IF NOT EXISTS measurements (
measurementType VARCHAR(32),
unit VARCHAR(32),
startDate DATETIME,
endDate DATETIME,
value FLOAT
);
INSERT INTO measurements (measurementType, unit, startDate, endDate, value) VALUES \n`
let hasSamples = false
for (const measurementType in data) {
const unit = data[measurementType]['unit']
for (const measurementData of data[measurementType]['samples']) {
hasSamples = true
output += `('${measurementType}','${unit}','${formatSqlDate(measurementData.startDate)}','${formatSqlDate(measurementData.endDate)}',${measurementData.value}),\n`
}
}
if (hasSamples) {
output = output.substring(0, output.length - 2) + ";\n"
} else {
output = "; No samples available"
}
return output
}
const fileManager = FileManager.iCloud()
const outputPath = fileManager.joinPath(fileManager.documentsDirectory(), `${config.outputFileName}.${config.outputFormat}`)
fileManager.writeString(outputPath, dataFormatters[config.outputFormat](output))