Working with DateTime Tags¶
This guide covers working with EXIF datetime tags using tagkit’s high-level datetime API.
Overview¶
EXIF images contain several datetime-related tags that store information about when photos were taken, modified, or digitized. Tagkit provides a user-friendly API for working with these datetime fields without needing to know the underlying EXIF tag IDs or structures.
EXIF DateTime Tags¶
There are three primary datetime tags in EXIF:
DateTimeOriginal (tag 36867, Exif IFD) - The date and time when the original image was captured
DateTimeDigitized (tag 36868, Exif IFD) - The date and time when the image was digitized
DateTime (tag 306, IFD0) - The date and time when the image file was last modified
Tag Precedence¶
When reading datetime information, tagkit uses the following precedence order:
DateTimeOriginal (preferred) - represents when the photo was taken
DateTimeDigitized (backup) - represents when the photo was digitized
DateTime (fallback) - represents last modification
This ensures that requesting a “primary” datetime returns the most relevant value for a photo (DateTimeOriginal > DateTimeDigitized > DateTime).
Basic Usage¶
Getting Datetime from an Image¶
Use the ExifImage object to retrieve the primary datetime from an image:
from tagkit import ExifImage
# Get the primary datetime (uses precedence order)
exif = ExifImage('photo.jpg')
dt = exif.get_datetime()
print(dt) # 2025-05-01 14:30:00
Setting Datetime¶
Use ExifImage.set_datetime() to set datetime values (the change is kept
in-memory; call save() to write the file):
from datetime import datetime
from tagkit import ExifImage
exif = ExifImage('photo.jpg')
dt = datetime(2025, 6, 15, 10, 30, 0)
# Update all three datetime tags (DateTime, DateTimeOriginal, DateTimeDigitized)
exif.set_datetime(dt)
exif.save()
# Or update only a specific tag
exif.set_datetime(dt, tags=['DateTimeOriginal'])
exif.save()
Getting All Datetime Tags¶
To read all present datetime tags from an image:
from tagkit import ExifImage
exif = ExifImage('photo.jpg')
datetimes = exif.get_all_datetimes()
for tag_name, dt in datetimes.items():
print(f"{tag_name}: {dt}")
Advanced Operations¶
Offsetting Datetime Values¶
Adjust datetime values by a timedelta with ExifImage.offset_datetime():
from datetime import timedelta
from tagkit import ExifImage
exif = ExifImage('photo.jpg')
# Add 2 hours to all datetime tags
exif.offset_datetime(timedelta(hours=2))
exif.save()
# Offset only the primary tag
exif.offset_datetime(timedelta(hours=2), tags=['DateTimeOriginal'])
exif.save()
Bulk Operations¶
For multiple files use ExifImageCollection which provides collection-wide
helpers and a save_all() convenience method:
from datetime import timedelta
from tagkit import ExifImageCollection
files = ['photo1.jpg', 'photo2.jpg', 'photo3.jpg']
collection = ExifImageCollection(files)
# Offset all images in memory and then persist
collection.offset_datetime(timedelta(hours=2))
collection.save_all()
Finding Earliest or Latest Datetime¶
You can compute extrema from the per-tag datetimes returned by
ExifImage.get_all_datetimes():
from tagkit import ExifImage
exif = ExifImage('photo.jpg')
datetimes = exif.get_all_datetimes()
if datetimes:
earliest = min(datetimes.values())
latest = max(datetimes.values())
print(f"Earliest: {earliest}")
print(f"Latest: {latest}")
EXIF-Specific Operations¶
For fine-grained control over specific datetime tags, pass a list of tag names
to the tags parameter on the set_datetime / offset_datetime methods.
from datetime import datetime, timedelta
from tagkit import ExifImage
exif = ExifImage('photo.jpg')
# Read a specific tag
dt = exif.get_datetime(tag='DateTimeOriginal')
# Set only the DateTime tag
exif.set_datetime(datetime(2025, 6, 15, 10, 30, 0), tags=['DateTime'])
exif.save()
# Offset only DateTimeOriginal
exif.offset_datetime(timedelta(hours=2), tags=['DateTimeOriginal'])
exif.save()
Parsing and Formatting¶
Utility helpers for parsing/formatting the EXIF string representation are
available from tagkit.image.exif and are re-exported at the package root:
from datetime import datetime
from tagkit import parse_exif_datetime, format_exif_datetime
dt = parse_exif_datetime('2025:05:01 14:30:00')
print(format_exif_datetime(dt)) # '2025:05:01 14:30:00'
Common Use Cases¶
Correcting Camera Time¶
If your camera’s clock was set incorrectly, offset the datetimes on the file:
from datetime import timedelta
from tagkit import ExifImage
exif = ExifImage('photo.jpg')
exif.offset_datetime(timedelta(hours=-3))
exif.save()
Timezone Adjustments¶
Convert a folder of images from one timezone to another with an
ExifImageCollection:
from datetime import timedelta
from tagkit import ExifImageCollection
import glob
photos = glob.glob('vacation/*.jpg')
collection = ExifImageCollection(photos)
collection.offset_datetime(timedelta(hours=-5))
collection.save_all()
Standardizing Datetime Tags¶
Use the primary datetime to set all datetime tags for an image:
from tagkit import ExifImage
exif = ExifImage('photo.jpg')
dt = exif.get_datetime()
if dt:
exif.set_datetime(dt)
exif.save()
Batch Processing with Error Handling¶
Use ExifImageCollection and handle exceptions at the script level. The
collection helpers operate in-memory; call save_all() to persist changes.
from pathlib import Path
from datetime import timedelta
from tagkit import ExifImageCollection
photo_dir = Path('my_photos')
photos = [str(p) for p in photo_dir.glob('**/*.jpg')]
collection = ExifImageCollection(photos)
try:
collection.offset_datetime(timedelta(hours=2))
collection.save_all()
print(f'Processed {len(photos)} files successfully')
except Exception as e:
with open('errors.log', 'w') as f:
f.write(str(e) + '\n')
print('Errors occurred; see errors.log')
Important Notes¶
Timezone Handling¶
All datetime operations in tagkit are timezone-naive by default; returned datetimes do not carry tzinfo. EXIF does have offset tags (OffsetTime*), but they are not automatically applied by the high-level API at this time.
DateTime Format¶
EXIF datetime strings use the format YYYY:MM:DD HH:MM:SS (note the colons).
The library exposes parse_exif_datetime and format_exif_datetime to convert
between this format and Python’s datetime objects.
File Modifications¶
All modifications are made in-memory on the ExifImage instance. Call
save() or ExifImageCollection.save_all() to write changes. You can create a
backup when saving using the create_backup=True flag on save().
Error Handling¶
ExifImage methods raise DateTimeError when an EXIF datetime string cannot be
parsed. IO-related errors such as FileNotFoundError or IOError are also
propagated so callers can handle them appropriately.
from tagkit import ExifImage
from tagkit.core.exceptions import DateTimeError
exif = ExifImage('photo.jpg')
try:
dt = exif.get_datetime()
except FileNotFoundError:
print('Image file not found')
except DateTimeError as e:
print(f'Invalid datetime format: {e}')
except IOError as e:
print(f'Error reading image: {e}')
API Reference¶
ExifImage (single-file API)¶
ExifImage.get_datetime(tag=None, use_precedence=True)— Get the primary datetimeExifImage.set_datetime(dt, tags=None)— Set datetime values (in-memory)ExifImage.get_all_datetimes()— Get all datetime tags as a dictExifImage.offset_datetime(delta, tags=None)— Offset datetimes (in-memory)ExifImage.save(create_backup=False)— Persist changes to disk
ExifImageCollection (multi-file API)¶
ExifImageCollection.get_datetime(files=None, tag=None)— Get datetimes for collection filesExifImageCollection.set_datetime(dt, tags=None, files=None)— Set datetimes across the collection (in-memory)ExifImageCollection.offset_datetime(delta, tags=None, files=None)— Offset datetimes across the collection (in-memory)ExifImageCollection.save_all(create_backup=False)— Save changes for all files
Utilities¶
parse_exif_datetime(datetime_str)— Parse an EXIF datetime stringformat_exif_datetime(dt)— Format a datetime as an EXIF string