Google Health API 提供专用汇总端点,可将健康记录按时间窗口分组和汇总。请勿在客户端上检索和汇总单个原始记录,而是使用汇总端点通过协调的数据流计算指标(例如总计、平均值、最小值和最大值):
rollUp端点会使用可配置的窗口时长(以秒为单位,windowSize)汇总 UTC 物理时间间隔内的数据点。dailyRollUp端点会使用相对于用户本地时钟的民用时间,汇总一个或多个日历日 (windowSizeDays) 的数据点。
这两个端点都会自动协调已连接设备之间的重叠数据点,并排除支持佩戴检测的可穿戴设备数据类型的未佩戴时段。
比较汇总端点
选择与应用对时序健康指标进行分组和显示的方式相匹配的汇总端点:
| 功能 | rollUp 个端点 |
dailyRollUp 个端点 |
|---|---|---|
| HTTP 请求 | POST https://health.googleapis.com/v4/users/me/dataTypes/data-type/dataPoints:rollUp |
POST https://health.googleapis.com/v4/users/me/dataTypes/data-type/dataPoints:dailyRollUp |
| 时间表示法 | 世界协调时间 (UTC) 的物理时间(startTime 和 endTime,采用 RFC 3339 格式)。 |
民用时间(start 和 end 作为 CivilDateTime 对象)。 |
| 窗口参数 | windowSize(必需的字符串,时长以秒为单位,最短为 "1s")。 |
windowSizeDays(可选,整数天数,默认为 1)。 |
| 响应对象 | RollupDataPoint |
DailyRollupDataPoint |
| 回答排序 | 按时间倒序排列(最新窗口排在最前面)。 | 按时间倒序排列(最新窗口排在最前面)。 |
| 适用场景 | 日内图表、小时或分钟级直方图,以及固定时长的 UTC 分析。 | 每日统计信息、多日趋势和摘要,其中会考虑时区变化或夏令时。 |
这两个端点都会根据数据类型强制执行最大查询范围限制:
calories-in-heart-rate-zone、heart-rate、active-minutes和total-calories的查询范围上限为 14 天。- 对于所有其他受支持的数据类型,查询范围上限为 90 天。
如需查看哪些数据类型支持 rollup 和 dailyRollup,请参阅 Google Health API 数据类型参考文档。
按物理时间间隔汇总
使用 rollUp 端点,按窗口时长(以秒为单位,windowSize)对物理时间 range(采用世界协调时间 [UTC])内的数据点进行分组汇总。
调用 rollUp 端点时,请提供一个 JSON 请求正文,其中包含左闭右开的 range(包含 startTime,不包含 endTime)和 windowSize。请注意以下 windowSize 要求:
windowSize时长必须至少为 1 秒 ("1s")。API 会拒绝亚秒级时长、零或负时长,并返回400 Bad Request(INVALID_ROLLUP_WINDOW) 错误。- 为避免汇总数据在子存储桶中分布不均匀,请选择等于或大于数据类型的底层存储分辨率的
windowSize(例如,对于 1 分钟的步数间隔,选择"60s")。如需了解详情,请参阅汇总时间范围和存储分辨率。
例如,以下请求会以 1 分钟为间隔 (60s) 汇总步数:
请求
POST https://health.googleapis.com/v4/users/me/dataTypes/steps/dataPoints:rollUp
Authorization: Bearer access-token
Accept: application/json
{
"range": {
"startTime": "2026-02-17T17:00:00Z",
"endTime": "2026-02-17T17:59:59Z"
},
"windowSize": "60s"
}响应
{
"rollupDataPoints": [
{
"startTime": "2026-02-17T17:55:00Z",
"endTime": "2026-02-17T17:56:00Z",
"steps": {
"countSum": "72"
}
},
{
"startTime": "2026-02-17T17:54:00Z",
"endTime": "2026-02-17T17:55:00Z",
"steps": {
"countSum": "85"
}
},
...
]
}按日历日汇总
使用 dailyRollUp 端点汇总单个日历日或多个日历日 (windowSizeDays) 的数据。在请求正文中提供左闭右开的民用时间 range(start 和 end)。根据数据类型,端点会返回汇总值,例如每个民用时间窗口的总和或平均值。
由于 dailyRollUp 使用每个数据点记录的 UTC 偏移量来处理民用时间,因此它会自动协调因夏令时转换或跨时区旅行而导致的变量日长。
例如,以下请求会汇总单个民用日 (windowSizeDays: 1) 的步数:
请求
POST https://health.googleapis.com/v4/users/me/dataTypes/steps/dataPoints:dailyRollUp
Authorization: Bearer access-token
Accept: application/json
{
"range": {
"start": {
"date": {
"year": 2026,
"month": 2,
"day": 26
},
"time": {
"hours": 0,
"minutes": 0,
"seconds": 0,
"nanos": 0
}
},
"end": {
"date": {
"year": 2026,
"month": 2,
"day": 26
},
"time": {
"hours": 23,
"minutes": 59,
"seconds": 59,
"nanos": 0
}
}
},
"windowSizeDays": 1
}响应
{
"rollupDataPoints": [
{
"civilStartTime": {
"date": {
"year": 2026,
"month": 2,
"day": 26
},
"time": {}
},
"civilEndTime": {
"date": {
"year": 2026,
"month": 2,
"day": 26
},
"time": {
"hours": 23,
"minutes": 59,
"seconds": 59
}
},
"steps": {
"countSum": "3822"
}
}
]
}按数据源系列汇总
rollUp 和 dailyRollUp 都在 JSON 请求正文中接受可选的 dataSourceFamily 字段,以将汇总限制为特定来源组:
users/me/dataSourceFamilies/all-sources选项(默认)会汇总在所有已注册的第一方和第三方来源中进行核对的数据。users/me/dataSourceFamilies/google-wearables选项会汇总 Google 和 Fitbit 穿戴式跟踪器设备记录的数据,不包括手动记录的数据和手机估算的数据。users/me/dataSourceFamilies/google-sources选项会汇总来自 Google 和 Fitbit 第一方来源的数据,包括穿戴式追踪器、Health Connect 以及第一方应用中的手动条目。
如需了解完整的参数要求以及请求和响应示例,请参阅“过滤数据”指南中的按数据源系列过滤。
汇总窗口和存储分辨率
虽然 rollUp 端点接受 1 秒 ("1s") 或更长的任何 windowSize,但不同的数据类型会以不同的采样率或时间间隔在底层存储空间中记录和保留测量结果。例如,可穿戴设备身体活动指标(如 steps、distance、active-minutes 和 active-energy-burned)通常以 1 分钟 (60s) 为间隔进行记录。
下表列出了常见数据类型的底层存储分辨率和建议的最小 windowSize 值:
| 存储分辨率 | 建议的最低要求:windowSize |
数据类型 |
|---|---|---|
1 秒 (1s) |
"1s" |
heart-rate |
1 分钟 (60s) |
"60s" |
active-energy-burned、active-minutes、active-zone-minutes、altitude、calories-in-heart-rate-zone、distance、floors、steps、time-in-heart-rate-zone、total-calories |
汇总时间间隔数据类型时,rollUp 端点会将每个记录的数据点放入包含该数据点 startTime 的存储桶中。该 API 不会跨子间隔存储桶对间隔数据进行切片、插值或分布。
如果您指定的 windowSize 小于底层数据存储区间隔(例如,请求以 1 分钟间隔存储的 steps 的 10 秒窗口):
- 与时间间隔的
startTime(例如10:00:00到10:00:10)匹配的第一个子存储桶会接收整分钟的累计计数(例如,该分钟记录的所有 100 步)。 - 同一分钟内的其余子存储桶(
10:00:10到10:00:20、10:00:20到10:00:30等)不会收到任何数据点,因为这些时间段内没有开始任何时间间隔。
这会产生尖峰数据,其中整个时间间隔的值都集中在第一个子窗口中,如图 1 所示。
为了获得均匀分布且有意义的汇总数据,请始终将 windowSize 设置为等于或大于目标数据类型的底层存储分辨率的时长(例如,对于 steps,设置为 "60s" 或更长)。如需了解每种数据类型的存储分辨率和建议的最小汇总窗口,请参阅 Google Health API 数据类型参考文档。
范围不是窗口大小的倍数时的分桶
如果请求的范围不是 windowSize(或 windowSizeDays)的精确倍数,则最终的时间顺序存储桶会在范围的上限端点处被截断,并且涵盖的时长短于窗口大小。该 API 会接受您的请求,而不进行修改,也不会执行舍入、时移或数据插值。
为了涵盖整个请求范围,API 使用向上取整除法来计算汇总窗口总数:
Number of windows = ceiling(Range duration / Window size)
每个存储桶都从范围的开头按顺序开始。如果添加另一个完整大小的窗口会超出您请求的结束时间,则最后一个窗口会在范围结束时间处被截断(限制)。
分桶的工作原理
当您请求不可分割范围的汇总时,API 会应用以下规则:
- 分桶从请求范围的开头(
range.startTime或range.start)开始,并按窗口大小(windowSize或windowSizeDays)向前推进。 - 最后一个时间段会限制在所请求范围(
range.endTime或range.end)的末尾,涵盖的时间段比所请求的窗口大小短。 - 返回的
RollupDataPoint或DailyRollupDataPoint对象包含其自己的显式开始和结束时间戳,您可以使用这些时间戳来检查截断存储分区的实际时长。 - 由于 API 按时间倒序(最新数据在前)返回汇总数据,因此最终的时间段(截断的时间段)会显示为返回列表中的第一个元素 (
index 0)。
图 2 说明了 API 如何从 range.startTime 开始向前对不可分割的范围进行分桶,将最终分桶限制在 range.endTime,并按时间倒序返回生成的分桶。
12 分钟范围(5 分钟窗口)示例
假设客户端请求在 12 分钟范围内使用 5 分钟 windowSize ("300s") 进行汇总:
range.startTime值为10:00:00Z。range.endTime值为10:12:00Z,总时长为 12 分钟。windowSize值为"300s"(5 分钟)。
由于 12 分钟不是 5 分钟 (12 = 5 * 2 + 2) 的确切倍数,因此 API 会将窗口数计算为 ceiling(12 / 5) = 3。这会产生三个时间段:
| 存储桶 | 间隔 | 时长 | 车窗状态 |
|---|---|---|---|
| 存储桶 1 | [10:00:00Z, 10:05:00Z) |
5 分钟 | 全窗口 |
| 存储桶 2 | [10:05:00Z, 10:10:00Z) |
5 分钟 | 全窗口 |
| 存储桶 3 | [10:10:00Z, 10:12:00Z) |
2 分钟 | 已在 range.endTime 处截断 |
对汇总值的影响
由于最后一个时间窗口的时长较短,因此在被截断的时间段内,累加指标(例如步数的总和或计数)会因时间跨度较短而较低。
如果用户在这整个 12 分钟的时间范围内以每分钟 100 步的稳定速度行走,则这些存储桶包含以下总数:
- 时间段 1(
10:00:00Z到10:05:00Z)记录了 500 步(5 分钟,每分钟 100 步)。 - 时间段 2(
10:05:00Z到10:10:00Z)记录了 500 步(5 分钟,每分钟 100 步)。 - 分段 3(
10:10:00Z到10:12:00Z)记录了 200 步(2 分钟,每分钟 100 步)。
存储桶 3 中步数较低(200 步)是因为截断窗口的时长为 2 分钟,而不是因为用户活动减少。在绘制或分析这些数据时,请使用每个存储桶的明确时长对计数进行归一化,以避免呈现误导性的用户活动下降。
图 3 比较了三个分桶中的原始累加步数总计 (countSum) 与时长归一化速率。
显示排序的 API 响应示例
由于 API 按时间倒序返回结果,因此截断的存储桶会显示为返回列表中的第一个元素:
{
"rollupDataPoints": [
{
"startTime": "2026-08-20T10:10:00Z",
"endTime": "2026-08-20T10:12:00Z",
"steps": {
"countSum": "200"
}
},
{
"startTime": "2026-08-20T10:05:00Z",
"endTime": "2026-08-20T10:10:00Z",
"steps": {
"countSum": "500"
}
},
{
"startTime": "2026-08-20T10:00:00Z",
"endTime": "2026-08-20T10:05:00Z",
"steps": {
"countSum": "500"
}
}
]
}