Package fitbit: Fix parsing of sleep data.
[kraftakt.git] / fitbit / sleep.go
index 76fc197..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 = 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,17 +108,19 @@ 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
 }
 
-// Sleep returns the sleep log for date. Times are parsed in the user's timeozne, loc.
-func (c *Client) Sleep(ctx context.Context, date string, loc *time.Location) (*Sleep, error) {
+// Sleep returns the sleep log for date t. Times are parsed in the same timezone as t.
+func (c *Client) Sleep(ctx context.Context, t time.Time) (*Sleep, error) {
        url := fmt.Sprintf("https://api.fitbit.com/1.2/user/%s/sleep/date/%s.json",
-               c.fitbitUserID, date)
+               c.fitbitUserID, t.Format("2006-01-02"))
 
        res, err := c.client.Get(url)
        if err != nil {
@@ -118,5 +134,5 @@ func (c *Client) Sleep(ctx context.Context, date string, loc *time.Location) (*S
        }
        log.Debugf(ctx, "GET %s -> %s", url, data)
 
-       return parseSleep(ctx, data, loc)
+       return parseSleep(ctx, data, t.Location())
 }