exiftool-vendored
    Preparing search index...

    Date and Timezone Handling

    Date metadata very rarely includes the time zone.

    This creates a fundamental problem:

    • Parsing the same file in different parts of the world gives different absolute times
    • Most metadata contains only "local time" without timezone context
    • GPS coordinates might be present but not linked to date fields

    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 file
    • toDate(): Convert to JavaScript Date object
    • tzoffset: Minutes from UTC (positive = east of UTC, negative = west)
    • zone: Timezone name (e.g., "America/New_York") or "UnsetZone" for unknown

    The 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.