Date metadata very rarely includes the time zone.
This creates a fundamental problem:
This library provides sophisticated heuristics to handle these timezone ambiguities.
This library returns date values as custom classes that handle timezone complexity:
const tags = await exiftool.read("photo.jpg");
const dateTime = tags.DateTimeOriginal;
if (dateTime instanceof ExifDateTime) {
console.log("Raw value:", dateTime.rawValue); // "2024:03:15 14:30:00"
console.log("As Date:", dateTime.toDate()); // JavaScript Date object
console.log("ISO string:", dateTime.toISOString()); // "2024-03-15T14:30:00.000Z"
console.log("Timezone offset:", dateTime.tzoffset); // minutes from UTC (or null)
console.log("Timezone name:", dateTime.zone); // timezone name (or "UnsetZone")
}
rawValue
: Original string from the image filetoDate()
: Convert to JavaScript Date objecttzoffset
: Minutes from UTC (positive = east of UTC, negative = west)zone
: Timezone name (e.g., "America/New_York") or "UnsetZone" for unknownThe library uses multiple heuristics to determine timezone offsets, applied in priority order:
Highest Priority - Use explicit timezone tags if present:
// EXIF timezone tags (rarely set)
TimeZoneOffset; // Applied to DateTimeOriginal
OffsetTime; // Generic time offset
OffsetTimeOriginal; // Specific to DateTimeOriginal
OffsetTimeDigitized; // Specific to DateTimeDigitized
High Priority - Infer timezone from GPS coordinates:
const tags = await exiftool.read("photo.jpg");
if (tags.GPSLatitude && tags.GPSLongitude) {
console.log("Location:", tags.GPSLatitude, tags.GPSLongitude);
// Library uses tz-lookup to determine timezone from coordinates
console.log("Inferred timezone:", tags.DateTimeOriginal?.zone);
}
Note: Coordinates of 0, 0
are considered invalid if the ignoreZeroZeroLatLon
option is set
Medium Priority - Calculate offset from UTC timestamps:
// If present, compare local time with UTC time
const localTime = tags.DateTimeOriginal; // Local camera time
const utcTime = tags.GPSDateTime; // UTC from GPS
// or
const utcTime2 = tags.DateTimeUTC; // UTC timestamp
// Library calculates the delta to infer timezone offset
// Deltas > 14 hours are considered invalid
import { ExifDateTime } from "exiftool-vendored";
const tags = await exiftool.read("photo.jpg");
const dt = tags.DateTimeOriginal;
if (dt instanceof ExifDateTime) {
if (dt.tzoffset !== null) {
console.log(
`Photo taken at UTC${dt.tzoffset >= 0 ? "+" : ""}${dt.tzoffset / 60}`,
);
console.log("Timezone:", dt.zone);
} else {
console.log("Timezone unknown - using system default");
}
}
import { ExifDateTime } from "exiftool-vendored";
const dt = tags.DateTimeOriginal;
if (dt instanceof ExifDateTime) {
const utcDate = new Date(dt.toDate().getTime() - (dt.tzoffset ?? 0) * 60000);
console.log("UTC time:", utcDate.toISOString());
// Convert to specific timezone (requires additional library like date-fns-tz)
const nyTime = utcDate.toLocaleString("en-US", {
timeZone: "America/New_York",
});
console.log("New York time:", nyTime);
}
// Write complete date with timezone
await exiftool.write("photo.jpg", {
DateTimeOriginal: "2024:03:15 14:30:00+05:00",
});
// Write date in multiple formats
await exiftool.write("photo.jpg", {
AllDates: "2024:03:15 14:30:00", // Updates DateTimeOriginal, DateTime, ModifyDate
});
New in v30.2.0 - XMP tags support partial dates:
// Year only
await exiftool.write("photo.jpg", {
"XMP:CreateDate": 1980,
});
// Year and month
await exiftool.write("photo.jpg", {
"XMP:CreateDate": "1980:08", // or "1980-08"
});
// Using ExifDate helper
import { ExifDate } from "exiftool-vendored";
await exiftool.write("photo.jpg", {
"XMP:CreateDate": ExifDate.fromYear(1980),
"XMP:MetadataDate": ExifDate.fromYearMonth("1980-08"),
});
⚠️ Important: Partial dates only work with XMP tags (XMP:CreateDate
, XMP:MetadataDate
). EXIF tags require complete dates.
Warning: Editing timestamp tags can impact timezone inference.
If you change DateTimeOriginal
but not GPS timestamps, the timezone inference may become incorrect:
// This might break timezone calculation
await exiftool.write("photo.jpg", {
DateTimeOriginal: "2024:03:15 14:30:00", // New local time
// GPS timestamp unchanged - now timezone delta is wrong!
});
// Better: Update related timestamps or include timezone
await exiftool.write("photo.jpg", {
AllDates: "2024:03:15 14:30:00", // Updates all date fields
TimeZoneOffset: "+05:00", // Explicit timezone
});
Modern smartphones and cameras can record timestamps with microsecond precision:
const dt = tags.DateTimeOriginal;
if (dt instanceof ExifDateTime) {
// Floating point milliseconds include microsecond precision
console.log("Milliseconds:", dt.millisecond); // e.g., 123.456 (123456 microseconds)
}
Both ExifDateTime
and ExifTime
preserve this sub-millisecond precision.