Skip to content

fix: make get_datetaken timezone-aware when exif provides an offset - #28

Merged
escaped merged 2 commits into
masterfrom
fix/datetaken-timezone
Sep 14, 2026
Merged

escaped merged 2 commits into
masterfrom
fix/datetaken-timezone

Conversation

@escaped

@escaped escaped commented Sep 12, 2026 •

Copy link
Copy Markdown
Owner

Why

Fix #17. With USE_TZ = True, get_datetaken returned naive datetimes even
when the exif data contained OffsetTimeOriginal/OffsetTime or a
GPSDateTime, so Django warned about a naive datetime and stored the value in
the default timezone — the wrong instant for photos taken elsewhere.

Walkthrough

  • DateTimeOriginal is combined with OffsetTimeOriginal (or the
    OffsetTime fallback) and returned with that UTC offset.
  • GPSDateTime is treated as UTC, including the ...Z values ExifTool
    actually emits (the old code failed to parse those and ignored the tag).
  • When DateTimeOriginal has no offset, an aware GPSDateTime is preferred
    over a naive original; a valid GPS value is also used when the offset is
    unparseable.
  • A malformed offset now raises ExifError instead of silently returning a
    naive datetime (review finding).
  • A naive datetime is still returned when the exif data has no offset at all;
    the README explains how to handle that with make_aware().

Changes

File(s) Change Summary
exiffield/getters.py offset/GPS-aware get_datetaken + parse helper
tests/test_getters.py offset, GPS, precedence, malformed-offset and invalid cases
README.md timezone behavior and a make_aware() recipe
CHANGELOG.md note the fix under [Unreleased]

Testing

Red first: 5 of the new/updated cases failed against the old implementation
(AssertionError, plus ExifError for the ...Z GPS value); the
malformed-offset case failed against the first revision of the fix. After:

$ uv run pytest -q
78 passed, 1 xfailed in 5.08s
$ uv run ruff format --check . && uv run ruff check . && uv run mypy
17 files already formatted / All checks passed! / no issues found

%z parsing of the Z suffix was also checked on Python 3.10, the minimum
supported version.

CI on 48cb745: run 34694505570 — 14/14 jobs success.

PR breakdown

Stacked on the modernization branch test/e2e (PR #26). Merge in order.

# PR What it does Status
1 #27 #23: missing orientation tag crash Open
2 This PR #17: timezone-aware get_datetaken Open
3 #29 #6: document non-editable fields Open
4 #30 #1: document and test deferred extraction Open

@coveralls

coveralls commented Sep 12, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 34898527593

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.2%) to 98.454%

Details

  • Coverage increased (+0.2%) from the base build.
  • Patch coverage: 28 of 28 lines across 1 file are fully covered (100%).
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 194
Covered Lines: 191
Line Coverage: 98.45%
Coverage Strength: 10.83 hits per line

💛 - Coveralls

@escaped

escaped commented Sep 12, 2026

Copy link
Copy Markdown
Owner Author

Review finding addressed in 48cb745: an unparseable offset now raises ExifError instead of silently returning a naive datetime, with a valid GPSDateTime still preferred when it is available. See the review record on #27.

Base automatically changed from fix/get-orientation-missing to master September 14, 2026 21:19
get_datetaken returned a naive datetime even when the exif data carried
an UTC offset or GPS time, which triggers Django's naive datetime warning
and stores the wrong instant with USE_TZ=True. Use OffsetTimeOriginal /
OffsetTime for DateTimeOriginal, treat GPSDateTime as UTC, and keep the
naive fallback only for files without any offset information.

Refs #17
A present but malformed offset was silently ignored, returning a naive
datetime even though the file claims to carry timezone information.
Mirrors the existing invalid-datetime behavior; a valid GPSDateTime is
still preferred when it is available.
@escaped
escaped force-pushed the fix/datetaken-timezone branch from 48cb745 to ced103d Compare September 14, 2026 21:23
@escaped
escaped merged commit 0b1f396 into master Sep 14, 2026
25 checks passed
@escaped
escaped deleted the fix/datetaken-timezone branch September 14, 2026 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Timezone and get_datetaken

2 participants