Writing

Why your Google Takeout photos all have the wrong date

If you have exported your photos out of Google Photos, you have probably hit this: fifteen years of memories arrive on your computer and every single one is dated today. Album order collapses. Backup software files them all under the wrong year. Nothing is corrupted, but nothing is where it should be either.

The reason is simple, and the fix is simple in principle. What is not simple — and what I spent most of my time on when building the tool for this — is that Google's naming rules for the files holding those dates have at least four different shapes, and most scripts only handle the first one.

Why the dates disappear

Google Photos does not store "when this photo was taken" inside your photo. It stores it in its own database. Your phone usually writes an EXIF timestamp when it takes a picture, but anything edited in Google Photos, uploaded from a computer, or restored from an older backup may not have one — and Google's copy of that date lives on Google's side.

When you export with Takeout, Google can't put its database inside your JPEG. So it does the next best thing: it writes a companion .json file next to every photo containing the metadata, including the field that matters:

{
  "title": "IMG_1234.jpg",
  "photoTakenTime": {
    "timestamp": "1451606400",
    "formatted": "1 Jan 2016, 00:00:00 UTC"
  },
  ...
}

photoTakenTime.timestamp is a Unix timestamp — seconds since 1 January 1970. That is your real date. It is right there in the export. It is just not in the photo, and no photo app reads sidecar JSON files, so as far as your computer is concerned the picture has no date and it falls back to the file's creation time — the moment you unzipped the download.

So the fix is: read the JSON, write the date into the image's EXIF DateTimeOriginal field, done. Which works fine until you try to match up thousands of files.

The matching problem

You would expect IMG_1234.jpg to be described by IMG_1234.jpg.json. Often it is. But Takeout exports contain at least four naming variants, and if your script only checks the obvious one it will silently skip photos — which is worse than failing, because you won't notice until months later.

1. The newer "supplemental-metadata" suffix

Exports from roughly 2024 onward frequently use a longer name:

IMG_1234.jpg
IMG_1234.jpg.supplemental-metadata.json

Older exports use plain .json. Both appear in the wild, sometimes in the same archive if you have re-exported over time, so a tool has to accept either.

2. The 51-character truncation

Google truncates long sidecar filenames. The cut-off lands around 51 characters for the whole name, which means a photo with a long descriptive filename gets a sidecar whose name is chopped mid-word — often mid-extension:

A_very_long_holiday_photo_filename_from_2019_here.jpg
A_very_long_holiday_photo_filename_from_2019_.json

Exact-name matching fails completely here. You have to test truncated candidates too.

3. Duplicates move the counter

This one is genuinely counter-intuitive. When Google Photos holds two files with the same name, the export disambiguates with (1), (2) and so on. On the photo, the counter goes where you expect — before the extension. On the sidecar, it goes after it:

IMG_1234(1).jpg
IMG_1234.jpg(1).json

Note the difference: the photo is IMG_1234(1).jpg, but the JSON is IMG_1234.jpg(1).json — the counter has moved past .jpg. Naive string concatenation produces IMG_1234(1).jpg.json, which does not exist, so the photo is skipped.

4. Edited copies point at the original

If you cropped or adjusted a photo in Google Photos, the export contains both versions. The edited copy usually has no sidecar of its own — it inherits the original's:

IMG_1234.jpg
IMG_1234.jpg.json
IMG_1234-edited.jpg        ← no json of its own

The suffix is localised, too. English exports use -edited; German exports use -bearbeitet, French -modifié, Spanish -editado. If your account language has ever changed, one archive can contain more than one of these.

What a correct matcher has to do

Rather than constructing one expected filename, generate a candidate list per photo and take the first that exists. In our implementation that's:

  1. the exact name plus .json
  2. the exact name plus .supplemental-metadata.json
  3. the duplicate-counter variant, with the counter moved after the extension
  4. the de-suffixed original, for edited copies, in each known language
  5. truncated forms of all of the above, cut to the 51-character limit

That is around a dozen candidates per photo. It sounds wasteful; it takes microseconds and it is the difference between "restored 4,812 photos" and "restored 4,100 photos and quietly ignored the rest".

Writing the date back

Once matched, convert the Unix timestamp to EXIF's format — YYYY:MM:DD HH:MM:SS, colons in the date, local time — and write three fields:

Different software reads different fields, so writing all three avoids an app picking the one you left blank.

What can't be fixed this way

Being straight about the limits: EXIF is a JPEG/TIFF convention. PNG, HEIC and video files store metadata differently, and rewriting those safely in a browser is not something I'd trust yet — so our tool reports them rather than touching them. If your archive is mostly HEIC from an iPhone, a desktop tool such as ExifTool is the better instrument.

Also worth knowing: fixing EXIF does not change the file's creation date on disk. Most photo applications and cloud backups prefer EXIF and will sort correctly once it's set, but a plain file-manager sort by "date modified" still shows the download date.

If you'd rather not run a script

Most existing solutions for this are Python scripts on GitHub, which are excellent if you are comfortable with a terminal. The browser version on this site does the same matching described above without installing anything, and processes the photos on your own machine — which matters here, because a photo archive is about as personal as files get.