feat(dates): calendar-day date filters in the owner's display zone (#152)
/photos, /photos/exif and unified search take date_from_day / date_to_day (YYYY-MM-DD): the server turns them into the first day's start and the last day's end in the owner's display zone, the same days /memories groups by. Each replaces its instant counterpart. Clients stop computing midnights in the device's zone, which drifted by the travel offset whenever the phone's zone differed from display_tz. e2e check: every fixture is included by its display-zone day and excluded by the days either side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a11d20bdcd
commit
bfc3c1c21a
5 files changed
+133
-5
No files matched your search
@@ -7,7 +7,10 @@ For every indexed fixture, the manifest's expected wall clock must equal the
|
||||
`/photos/exif`; and `/image/metadata` / `/photos/exif`'s `true_instant` must
|
||||
equal the expected instant. The `/photos/exif` date filter must also treat
|
||||
that instant as a range edge (#152): `[t, t]` includes the file, and a range
|
||||
ending at `t - 1` or starting at `t + 1` excludes it. One disagreement
|
||||
ending at `t - 1` or starting at `t + 1` excludes it; and filtering by the
|
||||
file's calendar day in the owner's display zone (`date_from_day` /
|
||||
`date_to_day`) includes it, while the day before or after excludes it. One
|
||||
disagreement
|
||||
anywhere is exactly the class of bug these fields exist to end, so any
|
||||
mismatch fails the run.
|
||||
|
||||
@@ -19,9 +22,13 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.parse
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
E2E = Path(__file__).with_name("e2e.sh")
|
||||
# The owner's display zone in the e2e env (no prefs row → the app default).
|
||||
DISPLAY_ZONE = ZoneInfo("America/New_York")
|
||||
|
||||
|
||||
def api(path: str) -> dict:
|
||||
@@ -44,6 +51,10 @@ def main() -> int:
|
||||
rows = api(f"photos/exif?date_from={lo}&date_to={hi}")["photos"]
|
||||
return any(r["file_path"] == path for r in rows)
|
||||
|
||||
def in_exif_days(path: str, first: str, last: str) -> bool:
|
||||
rows = api(f"photos/exif?date_from_day={first}&date_to_day={last}")["photos"]
|
||||
return any(r["file_path"] == path for r in rows)
|
||||
|
||||
failures = 0
|
||||
for item in items:
|
||||
path, want = item["path"], item["wall_clock"]
|
||||
@@ -70,6 +81,14 @@ def main() -> int:
|
||||
bad["photos/exif [.., t-1]"] = "included"
|
||||
if in_exif_range(path, t + 1, t + 86_400):
|
||||
bad["photos/exif [t+1, ..]"] = "included"
|
||||
# Calendar days are the owner's display-zone days (#152).
|
||||
day = datetime.fromtimestamp(t, timezone.utc).astimezone(DISPLAY_ZONE).date()
|
||||
iso = lambda d: d.isoformat()
|
||||
if not in_exif_days(path, iso(day), iso(day)):
|
||||
bad[f"photos/exif day {day}"] = "excluded"
|
||||
for other in (day - timedelta(days=1), day + timedelta(days=1)):
|
||||
if in_exif_days(path, iso(other), iso(other)):
|
||||
bad[f"photos/exif day {other}"] = "included"
|
||||
if bad:
|
||||
failures += 1
|
||||
print(f"MISMATCH {path}: expected {want}, got {bad}")
|
||||
|
||||
Reference in new issue
Block a user