Package fitbit: Fix parsing of sleep data.
authorFlorian Forster <ff@octo.it>
Sun, 4 Feb 2018 15:09:23 +0000 (16:09 +0100)
committerFlorian Forster <ff@octo.it>
Sun, 4 Feb 2018 15:09:23 +0000 (16:09 +0100)
fitbit/sleep.go
fitbit/sleep_test.go

index d7da138..a912938 100644 (file)
@@ -5,6 +5,7 @@ import (
        "encoding/json"
        "fmt"
        "io/ioutil"
+       "sort"
        "time"
 
        "google.golang.org/appengine/log"
@@ -43,29 +44,42 @@ type Sleep struct {
        Stages []SleepStage
 }
 
+type byTime []SleepStage
+
+func (t byTime) Len() int           { return len(t) }
+func (t byTime) Less(i, j int) bool { return t[i].StartTime.Before(t[j].StartTime) }
+func (t byTime) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
+
 func parseSleep(ctx context.Context, data []byte, loc *time.Location) (*Sleep, error) {
        type rawData struct {
                DateTime string
                Level    string
                Seconds  int64
        }
-       var jsonSleep struct {
-               Levels struct {
-                       Data      []rawData
-                       ShortData []rawData
+       var parsed struct {
+               Sleep []struct {
+                       Levels struct {
+                               Data      []rawData
+                               ShortData []rawData
+                       }
                }
        }
-       if err := json.Unmarshal(data, &jsonSleep); err != nil {
+       if err := json.Unmarshal(data, &parsed); err != nil {
                return nil, err
        }
 
-       rawStages := jsonSleep.Levels.Data
-       if len(jsonSleep.Levels.ShortData) != 0 {
-               rawStages = append(rawStages, jsonSleep.Levels.ShortData...)
+       var allStages []rawData
+       for _, s := range parsed.Sleep {
+               if len(s.Levels.Data) != 0 {
+                       allStages = append(allStages, s.Levels.Data...)
+               }
+               if len(s.Levels.ShortData) != 0 {
+                       allStages = append(allStages, s.Levels.ShortData...)
+               }
        }
 
        var ret Sleep
-       for _, stg := range rawStages {
+       for _, stg := range allStages {
                tm, err := time.ParseInLocation("2006-01-02T15:04:05.999", stg.DateTime, loc)
                if err != nil {
                        log.Warningf(ctx, "unable to parse time: %q", stg.DateTime)
@@ -94,10 +108,12 @@ func parseSleep(ctx context.Context, data []byte, loc *time.Location) (*Sleep, e
                })
        }
 
-       if len(ret.Stages) == 0 && len(jsonSleep.Levels.Data) != 0 {
+       if len(ret.Stages) == 0 && len(allStages) != 0 {
                return nil, fmt.Errorf("parsing sleep stages failed")
        }
 
+       sort.Sort(byTime(ret.Stages))
+
        return &ret, nil
 }
 
index 5d83858..24b3ce4 100644 (file)
@@ -2,7 +2,6 @@ package fitbit
 
 import (
        "context"
-       "reflect"
        "testing"
        "time"
 )
@@ -10,91 +9,240 @@ import (
 func TestParseSleep(t *testing.T) {
        ctx := context.Background()
 
-       input := `{
-    "dateOfSleep": "2017-04-02",
-    "duration": 42000,
-    "efficiency": 42,
-    "isMainSleep": true,
-    "levels": {
-        "summary": {
-            "deep": {
-                "count": 42,
-                "minutes": 42,
-                "thirtyDayAvgMinutes": 42
-            },
-            "light": {
-                "count": 42,
-                "minutes": 42,
-                "thirtyDayAvgMinutes": 42
-            },
-            "rem": {
-                "count": 42,
-                "minutes": 42,
-                "thirtyDayAvgMinutes": 42
-            },
-            "wake": {
-                "count": 42,
-                "minutes": 42,
-                "thirtyDayAvgMinutes": 42
-            }
-        },
-        "data": [
-            {
-                "datetime": "2017-04-01T23:58:30.000",
-                "level": "wake",
-                "seconds": 1080
-            },
-            {
-                "datetime": "2017-04-02T00:16:30.000",
-                "level": "rem",
-                "seconds": 2000
-            }
-        ],
-        "shortData": [
-            {
-                "datetime": "2017-04-01T23:58:30.000",
-                "level": "wake",
-                "seconds": 1080
-            },
-            {
-                "datetime": "2017-04-02T00:16:30.000",
-                "level": "deep",
-                "seconds": 100
-            },
-            {
-                "datetime": "2017-04-02T00:18:10.000",
-                "level": "rem",
-                "seconds": 1900
-            }
-        ]
-    },
-    "logId": 42,
-    "minutesAfterWakeup": 42,
-    "minutesAsleep": 42,
-    "minutesAwake": 42,
-    "minutesToFallAsleep": 0,
-    "startTime": "2017-04-01T23:58:30.000",
-    "timeInBed": 42,
-    "type": "stages"
-}`
+       input := `{"sleep":[{"dateOfSleep":"2018-02-04","duration":30420000,"efficiency":95,"endTime":"2018-02-04T06:36:30.000","infoCode":0,"isMainSleep":true,"levels":{"data":[{"dateTime":"2018-02-03T22:09:30.000","level":"light","seconds":390},{"dateTime":"2018-02-03T22:16:00.000","level":"wake","seconds":840},{"dateTime":"2018-02-03T22:30:00.000","level":"light","seconds":1110},{"dateTime":"2018-02-03T22:48:30.000","level":"wake","seconds":870},{"dateTime":"2018-02-03T23:03:00.000","level":"light","seconds":120},{"dateTime":"2018-02-03T23:05:00.000","level":"wake","seconds":600},{"dateTime":"2018-02-03T23:15:00.000","level":"light","seconds":180},{"dateTime":"2018-02-03T23:18:00.000","level":"deep","seconds":360},{"dateTime":"2018-02-03T23:24:00.000","level":"light","seconds":390},{"dateTime":"2018-02-03T23:30:30.000","level":"deep","seconds":390},{"dateTime":"2018-02-03T23:37:00.000","level":"light","seconds":2580},{"dateTime":"2018-02-04T00:20:00.000","level":"rem","seconds":1080},{"dateTime":"2018-02-04T00:38:00.000","level":"wake","seconds":480},{"dateTime":"2018-02-04T00:46:00.000","level":"light","seconds":540},{"dateTime":"2018-02-04T00:55:00.000","level":"deep","seconds":1770},{"dateTime":"2018-02-04T01:24:30.000","level":"light","seconds":390},{"dateTime":"2018-02-04T01:31:00.000","level":"wake","seconds":630},{"dateTime":"2018-02-04T01:41:30.000","level":"light","seconds":480},{"dateTime":"2018-02-04T01:49:30.000","level":"rem","seconds":930},{"dateTime":"2018-02-04T02:05:00.000","level":"light","seconds":1920},{"dateTime":"2018-02-04T02:37:00.000","level":"deep","seconds":1830},{"dateTime":"2018-02-04T03:07:30.000","level":"light","seconds":630},{"dateTime":"2018-02-04T03:18:00.000","level":"rem","seconds":1740},{"dateTime":"2018-02-04T03:47:00.000","level":"wake","seconds":960},{"dateTime":"2018-02-04T04:03:00.000","level":"light","seconds":4860},{"dateTime":"2018-02-04T05:24:00.000","level":"rem","seconds":1890},{"dateTime":"2018-02-04T05:55:30.000","level":"light","seconds":2460}],"shortData":[{"dateTime":"2018-02-03T22:10:00.000","level":"wake","seconds":120},{"dateTime":"2018-02-03T22:13:30.000","level":"wake","seconds":30},{"dateTime":"2018-02-03T22:39:00.000","level":"wake","seconds":60},{"dateTime":"2018-02-03T23:52:00.000","level":"wake","seconds":60},{"dateTime":"2018-02-03T23:56:30.000","level":"wake","seconds":30},{"dateTime":"2018-02-04T00:04:30.000","level":"wake","seconds":90},{"dateTime":"2018-02-04T00:17:30.000","level":"wake","seconds":30},{"dateTime":"2018-02-04T00:51:30.000","level":"wake","seconds":30},{"dateTime":"2018-02-04T02:06:30.000","level":"wake","seconds":90},{"dateTime":"2018-02-04T02:17:00.000","level":"wake","seconds":30},{"dateTime":"2018-02-04T02:24:00.000","level":"wake","seconds":30},{"dateTime":"2018-02-04T03:07:00.000","level":"wake","seconds":30},{"dateTime":"2018-02-04T03:23:30.000","level":"wake","seconds":30},{"dateTime":"2018-02-04T03:42:00.000","level":"wake","seconds":60},{"dateTime":"2018-02-04T03:45:00.000","level":"wake","seconds":30},{"dateTime":"2018-02-04T04:16:30.000","level":"wake","seconds":30},{"dateTime":"2018-02-04T04:21:30.000","level":"wake","seconds":60},{"dateTime":"2018-02-04T04:47:30.000","level":"wake","seconds":60},{"dateTime":"2018-02-04T06:29:00.000","level":"wake","seconds":30}],"summary":{"deep":{"count":4,"minutes":72,"thirtyDayAvgMinutes":81},"light":{"count":28,"minutes":255,"thirtyDayAvgMinutes":272},"rem":{"count":7,"minutes":92,"thirtyDayAvgMinutes":90},"wake":{"count":25,"minutes":88,"thirtyDayAvgMinutes":81}}},"logId":17122464961,"minutesAfterWakeup":0,"minutesAsleep":419,"minutesAwake":88,"minutesToFallAsleep":0,"startTime":"2018-02-03T22:09:30.000","timeInBed":507,"type":"stages"}],"summary":{"stages":{"deep":94,"light":155,"rem":78,"wake":71},"totalMinutesAsleep":419,"totalSleepRecords":1,"totalTimeInBed":507}}`
 
        want := &Sleep{
                Stages: []SleepStage{
                        SleepStage{
-                               StartTime: time.Date(2017, time.April, 1, 23, 58, 30, 0, time.UTC),
-                               EndTime:   time.Date(2017, time.April, 2, 0, 16, 30, 0, time.UTC),
+                               StartTime: time.Date(2018, time.February, 3, 22, 9, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 22, 16, 0, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 22, 10, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 22, 12, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 22, 13, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 22, 14, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 22, 16, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 22, 30, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 22, 30, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 22, 48, 30, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 22, 39, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 22, 40, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 22, 48, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 23, 3, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 23, 3, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 23, 5, 0, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 23, 5, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 23, 15, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 23, 15, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 23, 18, 0, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 23, 18, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 23, 24, 0, 0, time.UTC),
+                               Level:     SleepLevelDeep,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 23, 24, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 23, 30, 30, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 23, 30, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 23, 37, 0, 0, time.UTC),
+                               Level:     SleepLevelDeep,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 23, 37, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 0, 20, 0, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 23, 52, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 23, 53, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 3, 23, 56, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 3, 23, 57, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 0, 4, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 0, 6, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 0, 17, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 0, 18, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 0, 20, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 0, 38, 0, 0, time.UTC),
+                               Level:     SleepLevelREM,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 0, 38, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 0, 46, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 0, 46, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 0, 55, 0, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 0, 51, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 0, 52, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 0, 55, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 1, 24, 30, 0, time.UTC),
+                               Level:     SleepLevelDeep,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 1, 24, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 1, 31, 0, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 1, 31, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 1, 41, 30, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 1, 41, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 1, 49, 30, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 1, 49, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 2, 5, 0, 0, time.UTC),
+                               Level:     SleepLevelREM,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 2, 5, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 2, 37, 0, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 2, 6, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 2, 8, 0, 0, time.UTC),
                                Level:     SleepLevelWake,
                        },
                        SleepStage{
-                               StartTime: time.Date(2017, time.April, 2, 0, 16, 30, 0, time.UTC),
-                               EndTime:   time.Date(2017, time.April, 2, 0, 18, 10, 0, time.UTC),
+                               StartTime: time.Date(2018, time.February, 4, 2, 17, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 2, 17, 30, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 2, 24, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 2, 24, 30, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 2, 37, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 3, 7, 30, 0, time.UTC),
                                Level:     SleepLevelDeep,
                        },
                        SleepStage{
-                               StartTime: time.Date(2017, time.April, 2, 0, 18, 10, 0, time.UTC),
-                               EndTime:   time.Date(2017, time.April, 2, 0, 49, 50, 0, time.UTC),
+                               StartTime: time.Date(2018, time.February, 4, 3, 7, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 3, 7, 30, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 3, 7, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 3, 18, 0, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 3, 18, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 3, 47, 0, 0, time.UTC),
                                Level:     SleepLevelREM,
                        },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 3, 23, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 3, 24, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 3, 42, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 3, 43, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 3, 45, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 3, 45, 30, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 3, 47, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 4, 3, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 4, 3, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 5, 24, 0, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 4, 16, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 4, 17, 0, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 4, 21, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 4, 22, 30, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 4, 47, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 4, 48, 30, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 5, 24, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 5, 55, 30, 0, time.UTC),
+                               Level:     SleepLevelREM,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 5, 55, 30, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 6, 36, 30, 0, time.UTC),
+                               Level:     SleepLevelLight,
+                       },
+                       SleepStage{
+                               StartTime: time.Date(2018, time.February, 4, 6, 29, 0, 0, time.UTC),
+                               EndTime:   time.Date(2018, time.February, 4, 6, 29, 30, 0, time.UTC),
+                               Level:     SleepLevelWake,
+                       },
                },
        }
 
@@ -103,14 +251,17 @@ func TestParseSleep(t *testing.T) {
                t.Errorf("parseSleep() = %v", err)
        }
 
-       for i, stg := range got.Stages {
-               t.Logf("got.Stages[%d] = %+v", i, stg)
-       }
-       for i, stg := range want.Stages {
-               t.Logf("want.Stages[%d] = %+v", i, stg)
+       if got, want := len(got.Stages), len(want.Stages); got != want {
+               t.Fatalf("parseSleep() = %d stages, want %d stages", got, want)
        }
 
-       if !reflect.DeepEqual(got, want) {
-               t.Errorf("parseSleep() = %+v, want %+v", got, want)
+       for i, gotStage := range got.Stages {
+               wantStage := want.Stages[i]
+
+               if gotStage.Level != wantStage.Level ||
+                       !gotStage.StartTime.Equal(wantStage.StartTime) ||
+                       !gotStage.EndTime.Equal(wantStage.EndTime) {
+                       t.Errorf("Stages[%d] differ; got %+v, want %+v", i, gotStage, wantStage)
+               }
        }
 }