From 319b04e413e9ca4e487834c4b3f30ccec9e92da5 Mon Sep 17 00:00:00 2001 From: owain Date: Fri, 12 Jun 2026 12:03:16 +0100 Subject: [PATCH] fix(sleep): store sleep timestamps from GMT not Local (displayed +1h in BST) --- backend/app/services/garmin_connect_sync.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/app/services/garmin_connect_sync.py b/backend/app/services/garmin_connect_sync.py index bad334f..c2244f8 100644 --- a/backend/app/services/garmin_connect_sync.py +++ b/backend/app/services/garmin_connect_sync.py @@ -555,10 +555,15 @@ def _parse_day(stats, sleep_data, hrv_data) -> dict: _set(row, "sleep_rem_s", dto.get("remSleepSeconds")) _set(row, "sleep_awake_s", dto.get("awakeSleepSeconds")) - # Timestamps are milliseconds since epoch in local time - for key, col in (("sleepStartTimestampLocal", "sleep_start"), - ("sleepEndTimestampLocal", "sleep_end")): - ms = dto.get(key) + # Use the GMT timestamps (true epoch-ms instants). The *Local fields are + # the GMT value pre-shifted by the local UTC offset, so storing them as + # UTC and letting the frontend convert to local double-applies the offset + # (sleep displayed +1h in BST). GMT stored as UTC converts back correctly. + for gmt_key, local_key, col in ( + ("sleepStartTimestampGMT", "sleepStartTimestampLocal", "sleep_start"), + ("sleepEndTimestampGMT", "sleepEndTimestampLocal", "sleep_end"), + ): + ms = dto.get(gmt_key) or dto.get(local_key) if ms: _set(row, col, datetime.fromtimestamp(ms / 1000, tz=timezone.utc).isoformat())