phpgoogle-fitgoogle-fit-sdk

Using the GoogleFit REST API timezone is ignored when making a request for aggregate data bucketed by time period


I combine these data sources:

$steps = static::getDataByAggregate('derived:com.google.step_count.delta:com.google.android.gms:estimated_steps', 'com.google.step_count.delta', $period['Start'], $period['End']);
$distance = static::getDataByAggregate('derived:com.google.distance.delta:com.google.android.gms:merge_distance_delta', 'com.google.distance.delta', $period['Start'], $period['End']);
$calories = static::getDataByAggregate('derived:com.google.calories.expended:com.google.android.gms:merge_calories_expended', 'com.google.calories.expended', $period['Start'], $period['End']);
$activeMinutes = static::getDataByAggregate('derived:com.google.active_minutes:com.google.android.gms:merge_active_minutes', 'com.google.active_minutes', $period['Start'], $period['End']);

$aggregates = array_merge($aggregates, static::mergeAggregates([
    'steps' => $steps,
    'distance' => $distance,
    'calories' => $calories,
    'activeMinutes' => $activeMinutes,
]));

So this function runs the actual aggregate pull.

     /**
     * Gets aggregated data by date for a given timerange.
     *
     * @see https://developers.google.com/fit/rest/v1/data-types#data_types_for_aggregate_data
     *
     * @param string $sourceId
     * @param string $dataType
     * @param int $start UTC unix timestamp
     * @param int $end UTC unix timestamp
     * @return array Array with values by data type, the date is the key.
     */
    public static function getDataByAggregate($sourceId, $dataType, $start, $end)
    {
        if (!in_array($sourceId, static::$availableSources)) {
            return;
        }

        $time = new \Google_Service_Fitness_BucketByTime();
        $aggregateBy = new \Google_Service_Fitness_AggregateBy();


        $bucketByTimePeriod = new \Google_Service_Fitness_BucketByTimePeriod();
        $bucketByTimePeriod->setValue(1);
        $bucketByTimePeriod->setType('day');
        $bucketByTimePeriod->setTimeZoneId(static::$aggregateTimezone);
        $time->setPeriod($bucketByTimePeriod);


        //$time->setDurationMillis("86400000");
        $aggregateBy->setDataTypeName($dataType);

        $aggregateBy->setDataSourceId($sourceId);
        $rx = new \Google_Service_Fitness_AggregateRequest();
        $rx->startTimeMillis = $start / 1000000;
        $rx->endTimeMillis = $end / 1000000;
        $rx->setAggregateBy([$aggregateBy]);
        $rx->setBucketByTime($time);
        $aggr = static::users_dataset_aggregate('me', $rx);

        $data = [];
        $buckets = $aggr->getBucket();
        foreach ($buckets as $bucket) {
            $dataset = $bucket->getDataset();
            foreach ($dataset[0]->getPoint() as $point) {
                $value = $point->getValue()[0]->getIntVal();
                if ($value === null) {
                    $value = $point->getValue()[0]->getFpVal();
                }

                $Segment = [
                    'Data'=> $value,
                    'StartTime' => intval($point->startTimeNanos / 1000000 / 1000),
                    'EndTime' => intval($point->endTimeNanos / 1000000 / 1000),
                ];

                /**
                 * Apparently the step, distance, and calorie count in the app is pegged to UTC no matter what timezone.
                 */
                $Start = new DateTime('now', new DateTimeZone('UTC'));
                $End = new DateTime('now', new DateTimeZone('UTC'));

                $Start->setTimestamp($Segment['StartTime']);
                $End->setTimestamp($Segment['EndTime']);

                $data[$Start->format('Y-m-d')] = $Segment['Data'];
            }
        }

        return $data;
    }

No matter what timezone I put into $bucketByTimePeriod->setTimeZoneId(static::$aggregateTimezone); I'm still stuck getting the same exact data.

The data never matches exactly what it shown on the device. Has anyone been able to match it perfectly? I was thinking of pulling this data together from activity segments but I'm not sure if that would even work properly.


Solution

  • The documentation for this isn't clear at all.

    The aggregation time buckets are determined by the startTimeMillis and either the bucketByTime.period or bucketByTime.durationMillis. Basically, the start time is incremented by the period/duration until it reaches (or exceeds) the endTimeMillis; if the final bucket end time is after endTimeMillis, it is just clamped down to that time.

    bucketByTime.period.timeZoneId actually has relatively little influence on the bucket boundaries: it's simply used to do the "plus days", "plus weeks", "plus months" calculation in the correct time zone, for occasions when "a day" isn't 24 hours long etc. This will probably only really make a difference around daylight savings time changes.

    So, if you want the start and end times to be aligned to particular times of day, you need to specify the alignment yourself via the startTimeMillis field.

    This may be perceived as an irritating detail that you shouldn't have to deal with, but it does give you the flexibility to choose the bucket times, rather than, say, having "midnight" (or, more precisely, the start of day) forced upon you.