Skip to content

fix(server): use the server TZ for files whose metadata has no zone - #1148

Open
Deeds67 wants to merge 2 commits into
mainfrom
fix/1147-no-zone-server-tz
Open

Deeds67 wants to merge 2 commits into
mainfrom
fix/1147-no-zone-server-tz

Conversation

@Deeds67

@Deeds67 Deeds67 commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1147.

What was wrong

When a file's metadata carries no time zone, getDates() in metadata.service.ts stored it as UTC even when the server's TZ is set. The docs describe TZ as the fallback. West of UTC this moved evening photos to the next day in the timeline and the viewer. Two paths did this:

  1. Files with no date in their metadata (e.g. WhatsApp, which strips EXIF). The fallback to the earliest file timestamp was stored as a bare instant with timeZone: null, so the local time was lost.
  2. Videos with no zone (e.g. Android camera videos with location off). exiftool parses QuickTime times as UTC (defaultVideosToUTC), and that assumed zone was stored as if the file said so.

What changed

  • Videos whose zone exiftool only assumed now use the server's zone. The moment the video was recorded is unchanged; only the local time and zone differ.
  • Videos whose date says +00:00 or Z outright keep UTC. exiftool reports the same defaultVideosToUTC source for these (verified on a real file). iPhones write the local offset into Keys:CreationDate, so a video recorded in Lisbon or Reykjavik really is UTC+0.
  • The no-date fallback keeps its local time in the file's zone if one is known, otherwise the server's.
  • New helper getServerTimeZone() (src/utils/date.ts):
    • Returns null when the server runs in UTC under any alias (UTC, Etc/UTC, GMT, which Node reports as +00:00, …) or TZ is unusable. Installs without TZ store exactly what they did before.
    • Keeps the configured spelling of TZ. Node would otherwise report legacy ICU names (Asia/Kolkata → Asia/Calcutta, Europe/Kyiv → Europe/Kiev, Asia/Ho_Chi_Minh → Asia/Saigon), which the mobile app's timezone database doesn't have. It validates first, so TZ=:America/New_York stores America/New_York.
  • exiftool-vendored's types deprecate tzSource for zoneSource, but 35.x only sets tzSource at runtime. The fix reads either. When a sidecar supplies the date, zoneSource is now stripped along with zone/tz/tzSource, so the video's assumed UTC can't be applied to a sidecar date. The fix proposed in the issue checked zoneSource only, which never matches on 35.x; the existing verbose log line had the same problem and always printed "via undefined".
  • Unchanged: photos with a capture time but no offset (the Time zone is lost on sync from android app #607 path), and files where exiftool found a real zone (tags, GPS, datestamps).

Existing assets are not rewritten; they pick up the fix when their metadata is re-extracted. On a non-UTC server, re-extracting all metadata will therefore move dateless files and zone-less videos to their local day. That is the intended correction.

Tests

Unit, metadata.service.spec.ts:

  • no-date file, on a non-UTC server and on a UTC-alias server
  • a file's own zone wins over the server's
  • UTC-assumed video, on non-UTC and UTC servers, reported as tzSource or zoneSource
  • explicit +00:00 and Z video dates stay UTC
  • video with no date at all
  • a video with a real zone is not overridden
  • a zone-less sidecar date on a video, in both source-tag shapes, doesn't pick up the server zone
  • the Time zone is lost on sync from android app #607 path is unaffected

Unit, date.spec.ts: getServerTimeZone() for UTC aliases, Europe/London, legacy-renamed zones, a :-prefixed TZ and an invalid TZ.

Medium, real exiftool and database:

  • a dateless PNG
  • short.mp4 with a zone-less QuickTime CreateDate, on non-UTC and UTC servers
  • short.mp4 with an explicit +00:00 Keys:CreationDate
  • short.mp4 with no date at all

Against the original code the first two fail with exactly the rows from the issue (null / 01:07, UTC / 20:57).

Mutation check: 12 targeted mutations of the fix (each guard removed or weakened), and every one fails at least one test.

Harness:

  • The unit spec deleted TZ in beforeEach, which let the machine's zone leak into tests. It now resets TZ to UTC.
  • The medium config had no TZ, so exif-date-time.spec.ts > should ignore the TimeCreated tag depended on the machine's zone once this fix landed. It is now pinned to UTC, as the unit config already is.
  • MediaRepository is added to the medium harness's mockable repositories.

Local: unit 201 files / 6460 tests, medium 181 files / 3223 tests, tsc, eslint and prettier clean.

…1147)

When a file's metadata carries no time zone, getDates() stored it as UTC even
when the server's TZ is set, although the docs describe TZ as the fallback.
West of UTC this moved evening photos to the next day. Two paths:

- Files with no date (e.g. WhatsApp, which strips EXIF) fell back to the
  earliest file timestamp as a bare instant, with a null time zone.
- QuickTime videos with no zone (e.g. Android camera videos with location
  off) are parsed by exiftool as UTC (defaultVideosToUTC), and that assumed
  zone was stored as if it came from the file.

Videos whose zone exiftool only assumed now use the server's zone, and the
no-date fallback keeps its local time in the file's zone if known, else the
server's. exiftool-vendored reports the source as `tzSource`, not
`zoneSource`, so the check (and the existing log line) use that field.

getServerTimeZone() returns null for UTC under any alias, so installs without
TZ store the same values as before. Photos with a capture time but no offset
(the #607 path) are unchanged.

Tests: unit cases for both paths plus the guards, medium cases that run the
real exiftool on a dateless image and a zone-less mp4, and TZ is pinned to
UTC in the medium config as it already is in the unit config, so results no
longer depend on the machine's zone.
…ames

Review follow-ups to the server TZ fallback:

- exiftool reports tzSource 'defaultVideosToUTC' even when the video's date
  says +00:00 or Z outright, e.g. an iPhone video recorded in Lisbon, where
  Keys:CreationDate carries the local offset. That UTC came from the file, so
  it is no longer replaced by the server's zone.
- Node resolves TZ=Asia/Kolkata to the legacy ICU name Asia/Calcutta (also
  Europe/Kyiv -> Europe/Kiev, Asia/Ho_Chi_Minh -> Asia/Saigon), which the
  mobile app's zone database does not have. getServerTimeZone() now keeps the
  configured spelling when it names the same zone, matching the current names
  geo-tz stores for GPS-located photos.
- exiftool-vendored's types deprecate tzSource in favour of zoneSource while
  35.x only sets tzSource at runtime; read either, and strip zoneSource along
  with the other zone tags when a sidecar supplies the date.
- The unit helper for assumed-UTC video tags now builds the date with
  ExifDateTime.fromEXIF, matching the raw value real exiftool returns.
@Deeds67 Deeds67 added the changelog:fix Bug fix for changelog label Sep 25, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog:fix Bug fix for changelog 🗄️server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Files with no zone in their metadata are shown in UTC instead of TZ

1 participant