mirror of
https://github.com/This-weiyuqing/mi-fitness-health.git
synced 2026-10-18 03:03:49 +08:00
fix: preserve Mi Fitness local timezone offsets
This commit is contained in:
1 parent
eef795672d
commit
c901e712d7
2 files changed
+54
-31
No files matched your search
@@ -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] = []
|
||||||
|
|
||||||
|
|||||||
@@ -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 = ?
|
||||||
""",
|
""",
|
||||||
|
|||||||
Reference in new issue
Block a user