Skip to content

Instantly share code, notes, and snippets.

@voldyman
Created April 26, 2019 03:29
Show Gist options
  • Save voldyman/e6dcde00388ea0c3c3be3b8b8da86972 to your computer and use it in GitHub Desktop.
Save voldyman/e6dcde00388ea0c3c3be3b8b8da86972 to your computer and use it in GitHub Desktop.
This program takes the XML file from iPhone's Health app (Health -> User -> Export data) and converts it into a CSV file with max, min, avg, p75 & p90 heart rate.
package main
import (
"encoding/csv"
"encoding/xml"
"flag"
"fmt"
"io/ioutil"
"os"
"sort"
"strconv"
"time"
)
type Record struct {
XMLName xml.Name `xml:"Record"`
Text string `xml:",chardata"`
Type string `xml:"type,attr"`
SourceName string `xml:"sourceName,attr"`
SourceVersion string `xml:"sourceVersion,attr"`
Device string `xml:"device,attr"`
Unit string `xml:"unit,attr"`
CreationDate string `xml:"creationDate,attr"`
StartDate string `xml:"startDate,attr"`
EndDate string `xml:"endDate,attr"`
Value string `xml:"value,attr"`
}
type ByValue []Record
func (b ByValue) Len() int {
return len([]Record(b))
}
func (b ByValue) Less(i, j int) bool {
recs := []Record(b)
lhs, err := strconv.Atoi(recs[i].Value)
if err != nil {
return true
}
rhs, err := strconv.Atoi(recs[j].Value)
if err != nil {
return false
}
return lhs < rhs
}
func (b ByValue) Swap(i, j int) {
recs := []Record(b)
recs[i], recs[j] = recs[j], recs[i]
b = ByValue(recs)
}
type HealthData struct {
XMLName xml.Name `xml:"HealthData"`
Records []Record `xml:"Record"`
}
type CSVRec struct {
Day time.Time
Max string
Min string
Avg string
P90 string
}
func main() {
flag.Parse()
args := flag.Args()
if len(args) != 1 {
fmt.Println("please provide file to parse")
os.Exit(1)
}
file := args[0]
fh, err := os.Open(file)
if err != nil {
fmt.Println("unable to open file:", file)
os.Exit(1)
}
data, err := ioutil.ReadAll(fh)
if err != nil {
fmt.Println("unable to read file", file)
os.Exit(1)
}
var hd HealthData
err = xml.Unmarshal(data, &hd)
if err != nil {
fmt.Println("unable to unmarshal file", err)
os.Exit(1)
}
dayRate := map[time.Time][]Record{}
for _, rec := range hd.Records {
if rec.Type != "HKQuantityTypeIdentifierHeartRate" {
continue
}
recStart, err := time.Parse("2006-01-02 15:04:05 -0700", rec.StartDate)
if err != nil {
fmt.Println("ignoring rec:", rec)
continue
}
day := time.Date(recStart.Year(), recStart.Month(), recStart.Day(), 0, 0, 0, 0, time.Local)
if exRec, ok := dayRate[day]; ok {
dayRate[day] = append(exRec, rec)
} else {
dayRate[day] = []Record{rec}
}
}
csvRecs := [][]string{
{"day", "max", "min", "avg", "p75", "p90"},
}
for day, recs := range dayRate {
sort.Sort(ByValue(recs))
p90 := int(0.9 * float32(len(recs)))
p75 := int(0.75 * float32(len(recs)))
fmt.Println("Day:", day.Year(), day.Month(), day.Day(),
"->",
"Max:", recs[len(recs)-1].Value,
"Min:", recs[0].Value,
"Avg:", recs[len(recs)/2].Value,
"P75", recs[p75].Value,
"P90", recs[p90].Value,
)
csvRecs = append(csvRecs, []string{
fmt.Sprintf("%d/%s/%d", day.Year(), day.Month().String(), day.Day()),
recs[len(recs)-1].Value,
recs[0].Value,
recs[len(recs)/2].Value,
recs[p90].Value,
})
}
out, err := os.OpenFile("prosessed.csv", os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
out = os.Stdout
}
csv.NewWriter(out).WriteAll(csvRecs)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment