fix: preserve Mi Fitness local timezone offsets

This commit is contained in:
binglua committed 2026-06-22 02:10:55 +00:00
1 parent eef795672d
commit c901e712d7
2 files changed
+54 -31

No files matched your search

+23 -12
View File
@@ -6,7 +6,7 @@ import os
import struct import struct
from collections import defaultdict from collections import defaultdict
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from datetime import UTC, datetime from datetime import UTC, datetime, timedelta, timezone
from typing import Any from typing import Any
from urllib.parse import urlencode from urllib.parse import urlencode
@@ -184,6 +184,21 @@ class MiFitnessCloudAdapter(DataAdapter):
await asyncio.sleep(0.5 * (attempt + 1)) await asyncio.sleep(0.5 * (attempt + 1))
raise RuntimeError(f"Mi Fitness request failed: {last_error}") raise RuntimeError(f"Mi Fitness request failed: {last_error}")
def _request_timezone(self, region: str | None = None) -> timezone:
region_name = self.region if region is None else region
if region_name in ("", "cn"):
return timezone(timedelta(hours=8))
return UTC
def _date_range_to_timestamps(
self, start_date: str, end_date: str, region: str | None = None
) -> tuple[int, int]:
tz = self._request_timezone(region)
start_dt = datetime.fromisoformat(start_date).replace(tzinfo=tz)
end_dt = datetime.fromisoformat(end_date + "T23:59:59").replace(tzinfo=tz)
return int(start_dt.timestamp()), int(end_dt.timestamp())
async def _fetch_key( async def _fetch_key(
self, key: str, start_date: str, end_date: str, region: str | None = None self, key: str, start_date: str, end_date: str, region: str | None = None
) -> list[dict]: ) -> list[dict]:
@@ -193,10 +208,7 @@ class MiFitnessCloudAdapter(DataAdapter):
if region_name in ("", "cn") if region_name in ("", "cn")
else f"https://{region_name}.hlth.io.mi.com" else f"https://{region_name}.hlth.io.mi.com"
) )
start_time = int(datetime.fromisoformat(start_date).replace(tzinfo=UTC).timestamp()) start_time, end_time = self._date_range_to_timestamps(start_date, end_date, region_name)
end_time = int(
datetime.fromisoformat(end_date + "T23:59:59").replace(tzinfo=UTC).timestamp()
)
next_key = None next_key = None
items: list[dict] = [] items: list[dict] = []
@@ -259,8 +271,9 @@ class MiFitnessCloudAdapter(DataAdapter):
def _record_datetime(self, item: dict) -> datetime: def _record_datetime(self, item: dict) -> datetime:
timestamp = int(item.get("time", 0)) timestamp = int(item.get("time", 0))
zone_offset = int(item.get("zone_offset", 0)) zone_offset = int(item.get("zone_offset", 0) or 0)
return datetime.fromtimestamp(timestamp + zone_offset, tz=UTC).replace(tzinfo=None) tz = timezone(timedelta(seconds=zone_offset))
return datetime.fromtimestamp(timestamp, tz=tz)
def _parse_value(self, item: dict) -> dict[str, Any]: def _parse_value(self, item: dict) -> dict[str, Any]:
raw = item.get("value", "{}") raw = item.get("value", "{}")
@@ -269,7 +282,8 @@ class MiFitnessCloudAdapter(DataAdapter):
return json.loads(raw) return json.loads(raw)
def _timestamp_to_datetime(self, timestamp: Any, zone_offset: int = 0) -> datetime: def _timestamp_to_datetime(self, timestamp: Any, zone_offset: int = 0) -> datetime:
return datetime.fromtimestamp(int(timestamp) + int(zone_offset), tz=UTC).replace(tzinfo=None) tz = timezone(timedelta(seconds=int(zone_offset or 0)))
return datetime.fromtimestamp(int(timestamp), tz=tz)
async def _fetch_sport_records_by_time( async def _fetch_sport_records_by_time(
self, start_date: str, end_date: str, region: str | None = None self, start_date: str, end_date: str, region: str | None = None
@@ -280,10 +294,7 @@ class MiFitnessCloudAdapter(DataAdapter):
if region_name in ("", "cn") if region_name in ("", "cn")
else f"https://{region_name}.hlth.io.mi.com" else f"https://{region_name}.hlth.io.mi.com"
) )
start_time = int(datetime.fromisoformat(start_date).replace(tzinfo=UTC).timestamp()) start_time, end_time = self._date_range_to_timestamps(start_date, end_date, region_name)
end_time = int(
datetime.fromisoformat(end_date + "T23:59:59").replace(tzinfo=UTC).timestamp()
)
next_key = None next_key = None
items: list[dict] = [] items: list[dict] = []
+31 -19
View File
@@ -457,7 +457,11 @@ class Database:
timezone, collected_at, timestamp, bpm, sample_type timezone, collected_at, timestamp, bpm, sample_type
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET ON CONFLICT(id) DO UPDATE SET
timezone = excluded.timezone,
collected_at = excluded.collected_at,
timestamp = excluded.timestamp,
bpm = excluded.bpm, bpm = excluded.bpm,
sample_type = excluded.sample_type,
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
""", """,
( (
@@ -486,6 +490,9 @@ class Database:
timezone, collected_at, timestamp, spo2_pct timezone, collected_at, timestamp, spo2_pct
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET ON CONFLICT(id) DO UPDATE SET
timezone = excluded.timezone,
collected_at = excluded.collected_at,
timestamp = excluded.timestamp,
spo2_pct = excluded.spo2_pct, spo2_pct = excluded.spo2_pct,
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
""", """,
@@ -514,6 +521,9 @@ class Database:
timezone, collected_at, timestamp, stress_score, level timezone, collected_at, timestamp, stress_score, level
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET ON CONFLICT(id) DO UPDATE SET
timezone = excluded.timezone,
collected_at = excluded.collected_at,
timestamp = excluded.timestamp,
stress_score = excluded.stress_score, stress_score = excluded.stress_score,
level = excluded.level, level = excluded.level,
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
@@ -544,6 +554,8 @@ class Database:
timezone, collected_at, event_id, start_at, end_at, duration_seconds timezone, collected_at, event_id, start_at, end_at, duration_seconds
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(user_id, event_id) DO UPDATE SET ON CONFLICT(user_id, event_id) DO UPDATE SET
timezone = excluded.timezone,
collected_at = excluded.collected_at,
start_at = excluded.start_at, start_at = excluded.start_at,
end_at = excluded.end_at, end_at = excluded.end_at,
duration_seconds = excluded.duration_seconds, duration_seconds = excluded.duration_seconds,
@@ -622,7 +634,7 @@ class Database:
""" """
SELECT * FROM sleep_sessions SELECT * FROM sleep_sessions
WHERE user_id = ? WHERE user_id = ?
AND date(start_at) >= ? AND date(start_at) <= ? AND substr(start_at, 1, 10) >= ? AND substr(start_at, 1, 10) <= ?
ORDER BY start_at ORDER BY start_at
""", """,
(user_id, start_date, end_date), (user_id, start_date, end_date),
@@ -641,7 +653,7 @@ class Database:
""" """
SELECT * FROM workouts SELECT * FROM workouts
WHERE user_id = ? WHERE user_id = ?
AND date(start_at) >= ? AND date(start_at) <= ? AND substr(start_at, 1, 10) >= ? AND substr(start_at, 1, 10) <= ?
ORDER BY start_at ORDER BY start_at
""", """,
(user_id, start_date, end_date), (user_id, start_date, end_date),
@@ -660,7 +672,7 @@ class Database:
""" """
SELECT * FROM body_measurements SELECT * FROM body_measurements
WHERE user_id = ? WHERE user_id = ?
AND date(timestamp) >= ? AND date(timestamp) <= ? AND substr(timestamp, 1, 10) >= ? AND substr(timestamp, 1, 10) <= ?
ORDER BY timestamp ORDER BY timestamp
""", """,
(user_id, start_date, end_date), (user_id, start_date, end_date),
@@ -678,7 +690,7 @@ class Database:
""" """
SELECT * FROM heart_rate_samples SELECT * FROM heart_rate_samples
WHERE user_id = ? WHERE user_id = ?
AND date(timestamp) >= ? AND date(timestamp) <= ? AND substr(timestamp, 1, 10) >= ? AND substr(timestamp, 1, 10) <= ?
ORDER BY timestamp ORDER BY timestamp
""", """,
(user_id, start_date, end_date), (user_id, start_date, end_date),
@@ -696,7 +708,7 @@ class Database:
""" """
SELECT * FROM spo2_samples SELECT * FROM spo2_samples
WHERE user_id = ? WHERE user_id = ?
AND date(timestamp) >= ? AND date(timestamp) <= ? AND substr(timestamp, 1, 10) >= ? AND substr(timestamp, 1, 10) <= ?
ORDER BY timestamp ORDER BY timestamp
""", """,
(user_id, start_date, end_date), (user_id, start_date, end_date),
@@ -714,7 +726,7 @@ class Database:
""" """
SELECT * FROM stress_samples SELECT * FROM stress_samples
WHERE user_id = ? WHERE user_id = ?
AND date(timestamp) >= ? AND date(timestamp) <= ? AND substr(timestamp, 1, 10) >= ? AND substr(timestamp, 1, 10) <= ?
ORDER BY timestamp ORDER BY timestamp
""", """,
(user_id, start_date, end_date), (user_id, start_date, end_date),
@@ -732,7 +744,7 @@ class Database:
""" """
SELECT * FROM abnormal_heart_beat_events SELECT * FROM abnormal_heart_beat_events
WHERE user_id = ? WHERE user_id = ?
AND date(start_at) >= ? AND date(start_at) <= ? AND substr(start_at, 1, 10) >= ? AND substr(start_at, 1, 10) <= ?
ORDER BY start_at ORDER BY start_at
""", """,
(user_id, start_date, end_date), (user_id, start_date, end_date),
@@ -768,9 +780,9 @@ class Database:
row = conn.execute( row = conn.execute(
""" """
SELECT SELECT
MIN(date(start_at)) as first_date, MIN(substr(start_at, 1, 10)) as first_date,
MAX(date(start_at)) as last_date, MAX(substr(start_at, 1, 10)) as last_date,
COUNT(DISTINCT date(start_at)) as days_with_data COUNT(DISTINCT substr(start_at, 1, 10)) as days_with_data
FROM sleep_sessions FROM sleep_sessions
WHERE user_id = ? WHERE user_id = ?
""", """,
@@ -788,9 +800,9 @@ class Database:
row = conn.execute( row = conn.execute(
""" """
SELECT SELECT
MIN(date(start_at)) as first_date, MIN(substr(start_at, 1, 10)) as first_date,
MAX(date(start_at)) as last_date, MAX(substr(start_at, 1, 10)) as last_date,
COUNT(DISTINCT date(start_at)) as days_with_data COUNT(DISTINCT substr(start_at, 1, 10)) as days_with_data
FROM workouts FROM workouts
WHERE user_id = ? WHERE user_id = ?
""", """,
@@ -808,9 +820,9 @@ class Database:
row = conn.execute( row = conn.execute(
""" """
SELECT SELECT
MIN(date(timestamp)) as first_date, MIN(substr(timestamp, 1, 10)) as first_date,
MAX(date(timestamp)) as last_date, MAX(substr(timestamp, 1, 10)) as last_date,
COUNT(DISTINCT date(timestamp)) as days_with_data COUNT(DISTINCT substr(timestamp, 1, 10)) as days_with_data
FROM body_measurements FROM body_measurements
WHERE user_id = ? WHERE user_id = ?
""", """,
@@ -827,9 +839,9 @@ class Database:
row = conn.execute( row = conn.execute(
""" """
SELECT SELECT
MIN(date(timestamp)) as first_date, MIN(substr(timestamp, 1, 10)) as first_date,
MAX(date(timestamp)) as last_date, MAX(substr(timestamp, 1, 10)) as last_date,
COUNT(DISTINCT date(timestamp)) as days_with_data COUNT(DISTINCT substr(timestamp, 1, 10)) as days_with_data
FROM heart_rate_samples FROM heart_rate_samples
WHERE user_id = ? WHERE user_id = ?
""", """,