[BUG] Journey photo picker searches provider photos by UTC day, not the local day — morning photos missing from "This day"
TREK version
4.2.1
Describe the bug
The provider photo search (Immich / Synology Photos) used by the Journey
photo picker converts the date-only from/to it receives into a UTC
day window. For any user east of UTC, "This day" for entry date D
actually searches D 00:00–23:59 UTC, which is a window shifted into
the local afternoon/next morning.
Concretely at UTC+10 (Australia/Melbourne, September): the searched
window for D is 10:00 local on D → 09:59 local on D+1. Two symmetric
symptoms:
- Photos taken before 10:00 local on the entry's day are missing from the default "This day" results. The user has to switch to the custom date-range tab and widen the range to find their own morning photos.
- Photos taken before 10:00 local on the following day are wrongly included in D's results (they fall inside D's UTC day).
In practice this hits every breakfast/morning photo of a trip, silently — there's no indication the day search is incomplete.
The picker's date group headings have the same issue: photos are grouped
by the UTC date of takenAt, so a photo taken 07:32 local appears under
the previous day's heading.
Setting TZ on the container does not (and cannot) help — the window is
built by string concatenation / UTC parsing, not via the process timezone,
and on a multi-user server each user has their own timezone anyway.
Steps to reproduce
- Run TREK in a timezone well east of UTC (e.g.
Australia/Melbourne, UTC+10) with an Immich provider connected. Browser in the same zone. - In Immich, have a photo taken at e.g. 07:32 local on day D (stored by Immich as D−1 21:32 UTC).
- Create a Journey entry dated D, open the entry editor → External photos → the picker's default This day filter.
- The 07:32 photo is absent. Widen to a custom range starting D−1 → it appears. Conversely, a photo taken 07:00 local on D+1 does show up under "This day" for D.
Expected behavior
"This day" for entry date D returns the photos taken on calendar day D at the place they were captured (at minimum: in the user's local timezone rather than UTC), and date group headings show the capture-local date. West-of-UTC users have the mirror-image problem (evening photos missing instead of morning ones).
Where it happens (code)
Client sends date-only strings; the picker's "This day" filter passes the
entry date as both bounds
(client/src/components/Journey/JourneyDetailPageProviderPicker.tsx:104-105):
if (filter === 'day' && initialDate) {
searchPhotos(initialDate, initialDate)The server then pins those dates to UTC for Immich
(server/src/nest/memories/immich.service.ts:213-214):
takenAfter: from ? `${from}T00:00:00.000Z` : undefined,
takenBefore: to ? `${to}T23:59:59.999Z` : undefined,(pinned as-is by server/tests/unit/nest/immich.service.test.ts:326-327).
Synology has the same window through a different expression —
new Date(<date-only>) parses as UTC midnight per the JS spec
(server/src/nest/memories/synology.service.ts:583-586):
params.start_time = Math.floor(new Date(from).getTime() / 1000);
...
params.end_time = Math.floor(new Date(to).getTime() / 1000) + 86400;Display grouping uses the UTC date too
(client/src/pages/journeyDetail/JourneyDetailPage.helpers.ts:65):
const key = asset.takenAt ? asset.takenAt.slice(0, 10) : '__unknown__'Affected surfaces: all picker filters (This day / Trip period / Date
range) in the Journey entry editor, gallery view and both mobile screens,
plus the search_provider_photos MCP tool (memories.mcp.ts,
searchProviderPhotos) — every path funnels into the two service methods
above. The trip-period filter likewise drops the first trip morning and
picks up the morning after the trip ends.
Related but distinct: #2280 / #2291 (merged after 4.2.1) fixed the
picker's ordering; the day window and the UTC date grouping are
unchanged on dev (immich.service.ts:213-214,
JourneyDetailPage.helpers.ts:68 there).
Suggested fix
Three possible anchors for "day D", in descending order of correctness:
- The photo's own capture-local date. Immich returns
localDateTimeon every search result (/search/metadata→AssetResponseDto), documented as "the photographer's local time regardless of timezone … used for timeline grouping by local days". Query a padded window (the local-day bounds ±1 day) and keep assets whoselocalDateTimedate equals the requested day; group headings use the same field. No timezone lookup needed, entries without a location work, and travel days spanning timezones resolve per-photo — matching what the user sees in Immich's own timeline. The server already post-filters this endpoint (hidden assets) withhasMoreon the raw page length, so the pagination pattern exists. - The entry's location timezone, when the entry has coordinates (the picker already receives them for distance sorting). Needs a lat/lng→IANA lookup dependency and a fallback for unlocated entries and the trip/custom/all filters.
- The user's browser timezone — the minimal fix and the natural
fallback for providers without per-asset local time (Synology): client
sends local-day instants (e.g.
new Date(from + 'T00:00:00').toISOString()), server passes through values that already contain a time component, keeping the bare-date suffixing for backward compatibility.
Whichever anchor is chosen, the date group headings should switch off
takenAt.slice(0, 10) (UTC) to the same anchor.
Deployment method
Docker Compose
Host OS
Ubuntu Server 24.04
Accessing TREK from
Desktop browser
Browser
(any — the window is built server-side; confirmed in Chromium and the PWA)
Source: liketrek/TREK