It may happen that you view your photos and videos in your file manager or through a gallery and realize that they are not sorted by creation date (the date they were taken) because the files were copied from one disk to another or after a restore from an old backup, so we need to fix creation date.
It happened to me recently, changing mobile and restoring the WhatsApp archive from a backup on Google Drive, that the modification date of both the images and videos were all on the same date as the day of restoration and therefore in the Gallery they were seen without a temporary arrangement.
I therefore decided to solve the problem without using third-party apps which are often paid for (or only allow the arrangement of a few files at a time).
Table of Contents
- Ipotesi iniziali
- Implementazione
- Riferimenti
Initial hypothesis
- Suppose we have one or more photo or video files with incorrect modification data compared to that of the creation of the shot;
- we are using a Linux, windows or android device;
- the file has exif metadata or the creation date is part of the file name.
Initial hypothesis: the creation date is part of the file name
If the file name respects a format where the date and time of creation are also present, then we can use this date.
For example, under Android devices, photos have a name like IMG_YYYYMMDD_hhmmss.jpg and videos like VID_YYYYMMDD_hhmmss.mp4.
WhatsApp photos are of the type IMG-YYYYMMDD_WASEQ.jpg and videos of the type VID-YYYYMMDD_WASEQ.mp4.
In general, the file name can have the following characters:
- start with at least 3 letters and no more than 5 lowercase or uppercase ( ^[A-Za-z]\{3,5\} );
- followed by the character “–” o “_” ( [_-] );
- so by 8-digit ( AAAAMMDD [0-9]\{8\} );
- followed by “–” o “_” ( [_-] );
- than 2-digit (2 cifre o WA .\{2\} );
- followed by 4-digit ( mmss [0-9]\{4\} ).
- finally followed by other characters including the file extension.
Where:
- AAAA is the 4-digit year of the shot;;
- MM is the 2-digit month of the shot;
- DD is the 2-digit day of the shot;
- hh is the 2-digit hours of the shot;
- mm is the 2-digit minutes of the shot;
- ss is the 2-digit seconds of the shot;
- WA indicates that the file is from WhatsApp (we don’t have the 2-digits of the hours);
- SEQ is a sequence of 4 digits starting from 0000 and increases by one with each new image of the same day (minutes and seconds are lost).
For these files, simply extract the date from the name and save it in its metadata using the command:
- Android or Linux:
touch -t "AAAAMMGGhhmm.ss"
- Windows PowerShell:
$fileImg.creationtime=$newdate; $fileImg.lastwritetime=$newdate
Initial hypothesis: file has exif metadata
Suppose we can extract the exif information from the image and video metadata then for the creation date we will use:
- for the photos
- first ExifDTOrig (id 36868);
- than ExifDTDigitized (id 36867) if the first doesn’t exist
- for videos:
- MediaCreateDate
To extract the exif metadata we use the exiftool command under Linux while in PowerShell under Windows we will use the properties of New-Object -ComObject Wia.ImageFile.
Unfortunately under the ADB shell of Android it is not possible to easily trace the metadata so we will skip this hypothesis for Android.
Implementation
Fix the creation date under Linux or equivalent systems
Fix the creation date: file name
Let’s write a function in Bash SetTimestampByFilename(), which accepts as a parameter a file where its name respect the initial hypotheses and suppose we have some images to fix the date:
SetTimestampByFilename() {
local f=$(basename "$1")
local filedate=""
filedate=$( echo $f | sed -e 's/^\([A-Za-z]\{3,5\}\)[_-]\([0-9]\{8\}\)[_-]\(.\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\).*$/./' -e 's/WA/00/' )
if [ "x$f" != "x$filedate" -a ${#filedate} -eq 15 ]; then
readable_date=$(echo $filedate | sed -e 's/^\(....\)\(..\)\(..\)\(..\)\(..\)\.\(..\)/\/\/ ::/')
echo "$1: setting date to $readable_date ($filedate)"
touch -t "$filedate" "$1"
return 0
else
echo "ERR: Invalid file name format: $1" >&2
return 1
fi
}
Once this function has been written (or done the copy paste from here) just launch it with a path to a file such as:
SetTimestampByFilename $HOME/images/IMG_20230104_154035_e.jpg
or on an entire directory:
for f in $HOME/images/*
do
SetTimestampByFilename "$f"
done
Fix the creation date: Exif
We assume that the exiftool command is already installed:
- Ubuntu and derivatives:
apt install -y libimage-exiftool-perl
- RHEL 9:
subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms
dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
- AlmaLinux 9, Rocky Linux 9:
dnf config-manager --set-enabled crb
dnf install epel-release
- RHEL 8:
subscription-manager repos --enable codeready-builder-for-rhel-8-$(arch)-rpms
dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
- AlmaLinux 8, Rocky Linux 8:
dnf config-manager --set-enabled powertools
dnf install epel-release
- RHEL 7:
subscription-manager repos --enable rhel-\*-optional-rpms --enable rhel-\*-extras-rpms --enable rhel-ha-for-rhel-\*-server-rpms
yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
- CentOS 7:
yum install epel-release
With exiftool we extract the single metadata with the -p parameter: for example to extract CreateDate in the file myphoto.jpg we use:
exiftool -p '$CreateDate' myphoto.jpg
which returns the format: AAAA:MM:DD hh:mm:ss (ex.2023:01:04 16:04:18) and we should transform it to the format for the touch -t
command (AAAAMMDDhhmm.ss)
:
exiftool -p '$CreateDate' myphoto.jpg 2>/dev/null | sed -e 's/[: ]//g' -e 's/\(..$\)/\./')
So let’s write a function in Bash, SetTimestampByExit(), which takes a file as a parameter:
SetTimestampByExif() {
local filedate=""
local readable_date=""
filedate=$(exiftool -p '$MediaCreateDate' "$1" 2>/dev/null | sed -e 's/[: ]//g' -e 's/\(..$\)/\./')
if [ -z "$filedate" ]; then
filedate=$(exiftool -p '$CreateDate' "$1" 2>/dev/null | sed -e 's/[: ]//g' -e 's/\(..$\)/\./')
if [ -z "$filedate" ]; then
filedate=$(exiftool -p '$DateTimeOriginal' "$1" 2>/dev/null | sed -e 's/[: ]//g' -e 's/\(..$\)/\./')
fi
fi
if [ -n "$filedate" ]; then
readable_date=$(echo $filedate | sed -e 's/^\(....\)\(..\)\(..\)\(..\)\(..\)\.\(..\)/\/\/ ::/')
date --date "$readable_date" >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "ERR: $1: invalid exif date format" >&2
return 2
fi
echo "$1: setting date exif to $readable_date ($filedate)"
touch -t "$filedate" "$1"
return 0
else
echo "ERR: exif date not found: $1" >&2
return 1
fi
}
Once you write this function just launch it with a path to a file such as:
SetTimestampByExit $HOME/images/IMG_20230104_154035_e.jpg
or on an entire directory:
for f in $HOME/images/*
do
SetTimestampByExit "$f"
done
Fix creation date automatically first with Exif otherwise by file name
To automate the process I created the repository SetTimestampByEximOrFilename su Github, download it and unzip it in a folder.
So let’s copy SetTimestampByExifOrFilename.sh in a directory present in our $PATH for example in $HOME/bin:
# First create, if it doesn't already exist, the $HOME/bin folder
mkdir -p $HOME/bin
cp SetTimestampByExifOrFilename.sh $HOME/bin
chmod +x $HOME/bin/SetTimestampByExifOrFilename.sh
# Add $HOME/bin to the $PATH variable if it doesn't exist
if ! $(echo $PATH | tr ":" "\n" | grep -qx $HOME/bin
then PATH=$PATH:$HOME/bin
fi
At this point we can fix the dates of the photos or videos simply with:
# On single file
SetTimestampByExifOrFilename.sh $HOME/images/IMG_20180816_215747.jpg
# On all file in a directory
SetTimestampByExifOrFilename.sh $HOME/images
The script will try with the exif metadata first and if it doesn’t find it with the date in the filename.
Fix the creation date under Windows with PowerShell
First we need to open a PowerShell terminal for example:
- clicking with the right mouse button in the Start menu and then on Windows PowerShell;
- or WIN + R key (run window) and typing powershell and then the enter keys;
- otherwise WIN + S key (search window) and looking for powershell and then clicking on Open in the right;
- or any other method.
Fix the creation date: file name
Let’s write a function in PowerShell, Set-TimestampByFilename(), which accepts a -File parameter where its name respect the initial hypotheses and suppose we have some images to fix the date:
function Set-TimestampByFilename() {
[CmdletBinding()]
param (
[Parameter(Mandatory)] [string]$File
)
if (! (Test-Path $File)) {
return "ERR: ${File}: not exist"
}
$fileName=$(Get-Item $File).Name
if (! ($fileName -match '^[A-Za-z]{3,5}[_-](\d{4})(\d{2})(\d{2})[_-](WA|\d{2})(\d{2})(\d{2}).*$')) {
return "ERR: Invalid file name format: ${fileName}"
}
$filedate=$Matches[1] + '/' + $Matches[2] +'/'+ $Matches[3] +' '+ $Matches[4] +':'+ $Matches[5] +':'+ $Matches[6]
$filedate = $filedate -replace 'WA','00'
$(Get-Item $File).creationtime=$(Get-Date $filedate)
$(Get-Item $File).lastwritetime=$(Get-Date $filedate)
Write-Host "${File}: setting date to ${filedate}"
return "OK"
}
Once this function has been written (or done the copy paste from here) just launch it with a path to a file such as:
Set-TimestampByFilename -File images\IMG_20180816_215747.jpg
or on an entire directory:
Get-ChildItem "images" | ForEach-Object { $fix=Set-TimestampByFilename -File $_.Fullname }
Fix the creation date: Exif
Let’s write a PowerShell function, Set-TimestampByExif(), which accepts -File as a parameter for passing an image file:
function Set-TimestampByExif() {
[CmdletBinding()]
param (
[Parameter(Mandatory)] [string]$File
)
if (! (Test-Path $File)) {
return "ERR: ${File}: not exist"
}
$fullName=$(Get-Item $File).FullName
$image = New-Object -ComObject Wia.ImageFile
try {
$image.LoadFile($fullName);
}
catch {
return "ERR: ${File}: not an image file"
}
if ($image.Properties.Length -eq 0) {
return "ERR: ${File}: not an image file"
}
if ( $image.Properties.Exists('ExifDTOrig')) {
$filedate=$image.Properties.Item('ExifDTOrig').Value
}
elseif ($image.Properties.Exists('ExifDTDigitized')) {
$filedate=$image.Properties.Exists('ExifDTDigitized').Value
}
else {
return "ERR: ${File}: Exif date not found"
}
if ($filedate -match '^\d\d\d\d:\d\d:\d\d \d\d:\d\d:\d\d$') {
$filedate_obj=[datetime]::ParseExact($filedate,'yyyy:MM:dd HH:mm:ss',$null)
}
else {
return "ERR: ${File}: Exif date unrecognized format ($filedate)"
}
$(Get-Item $File).creationtime=$filedate_obj
$(Get-Item $File).lastwritetime=$filedate_obj
Write-Host "${File}: setting exif date to ${filedate}"
return "OK"
}
Once you write this function just launch it with a path to a file such as:
Set-TimestampByExif -File images\IMG_20230104_154035.jpg
or on an entire directory:
Get-ChildItems "images" | ForEach-Object { $fix=Set-TimestampByExif -File $_.Fullname
Fix creation date automatically first with Exif otherwise by file name
To automate the process I created the repository SetTimestampByEximOrFilename su Github, download it and unzip it in a folder than copy Set-TimestampByExifOrFilename.ps1 in a directory.
mkdir -Force $ENV:LOCALAPPDATA\SetTimestampByEximOrFilename
cp Set-TimestampByExifOrFilename.ps1 $ENV:LOCALAPPDATA\SetTimestampByEximOrFilename
Note: You need to have the right to run .ps1 scripts so let’s check with:
Get-ExecutionPolicy
if it’s AllSigned or Restricted it is not possible to run the script so you need a stronger execution policy (RemoteSigned, Bypass or UnRestricted)and for security we use RemoteSigned and then unlock the script :
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force
Unblock-File -Path $ENV:LOCALAPPDATA\SetTimestampByEximOrFilename\Set-TimestampByExifOrFilename.ps1
We can then use the script on a photo or on an entire directory.
cd $ENV:LOCALAPPDATA\
SetTimestampByEximOrFilename# single file .\Set-TimestampByExifOrFilename.ps1 -File Z:\images\IMG_20230104_154035.jpg # All file on a directory .\Set-TimestampByExifOrFilename.ps1 -File Z:\images
The script will try with the exif metadata first and if it doesn’t find it with the date in the filename.
Fix the creation date under Android with ADB
To use this script on an Android device you need to connect it to a computer via USB cable so that the adb command can be used.
To use the adb command and therefore to be able to connect you need to enable the Developer options and then usb debugging.
If you don’t know how to do it, do a quick search online how to enable “USB Debugging”
adb can be dowload directly from its site.
Once downloaded just unzip the zip in a folder and enter inside with a terminate (Bash for Linux, PowerShell for Windows)
Through the adb shell we don’t have the possibility to easily trace the exif metadata since the exiftool command is not available so we only consider the date change associated with the file name.
Second as we installed adb from Linux package or by downloading the zip we can use the command without the full or relative path:
# without peth adb #
with relative path if inside the folder containing adb./adb
From now on we use, for simplicity, the command adb without path, so if you need to put the absolute path of adb in the shell search path:
- on Bash:
PATH:=$PATH:$(pwd)
- on PowerShell:
$ENV:PATH += ';' + $(pwd)
First, let’s verify that the adb command finds our Android device with the command:
adb devices
A connection acceptance request should appear on the screen of the Android device, accept it in order to continue.
Once accepted, the request will not be resubmitted again.
Fix the creation date: file name
We enter the Android shell with the command:
adb shell
Once inside, write (or copy and paste) the bash function SetTimestampByFilename()
SetTimestampByFilename() {
local f=$(basename "$1")
local filedate=""
filedate=$( echo $f | sed -e 's/^\([A-Za-z]\{3,5\}\)[_-]\([0-9]\{8\}\)[_-]\(.\{2\}\)\([0-9]\{2\}\)\([0-9]\{2\}\).*$/./' -e 's/WA/00/' )
if [ "x$f" != "x$filedate" -a ${#filedate} -eq 15 ]; then
readable_date=$(echo $filedate | sed -e 's/^\(....\)\(..\)\(..\)\(..\)\(..\)\.\(..\)/\/\/ ::/')
echo "$1: setting date to $readable_date ($filedate)"
touch -t "$filedate" "$1"
return 0
else
echo "Invalid file name format: $1" >&2
return 1
fi
}
Suppose we have an external memory connected with an “images” folder inside where our images to be modified are and that its path is “storage/9C33-6BBD“.
To verify the path of the external memory just view the storage folder or use the sm command:
ls -al storage
sm list-volumes public
For the internal memory, i.e. the first available in the device:
echo ${EXTERNAL_STORAGE:-/sdcard}
Now we can launch the new function SetTimestampByFilename() with a path to a file such as:
SetTimestampByFilename storage/9C33-6BBD/images/IMG_20191006_100750_BURST3.jpg
ls -al storage/9C33-6BBD/images/IMG_20191006_100750_BURST3.jpg
or on an entire directory:
for file in storage/9C33-6BBD/images/*; do SetTimestampByFilename "$file"; done
ls -al storage/9C33-6BBD/images/
As soon as the changes have been made, exit the adb shell with the exit command:
Naturally, if we want to re-enter adb shell to fix other photos or videos we have to rewrite the SetTimestampByFilename() function each time or we can follow the steps in the next paragraph to automate this activity also for subsequent times.
Fix creation date automatically by file name
To automate the process I created the repository SetTimestampByEximOrFilename su Github, download it and unzip it in a folder than copy SetTimestampByFilenameAdb.sh in a directory.
We copy it to the first internal memory, and we find the path with:
- on Bash:
pri_storage=$(adb shell 'echo $EXTERNAL_STORAGE')
pri_storage=${pri_storage:-/sdcard}
echo $pri_storage - on PowerShell:
$pri_storage=$(adb shell 'echo $EXTERNAL_STORAGE')
if (-not $pri_storage) {$pri_storage='/sdcard'}
echo $pri_storage
So copy SetTimestampByFilenameAndroidAdb.sh
adb push SetTimestampByFilenameAndroidAdb.sh $pri_storage
adb shell ls -al $pri_storage/
having done this we have in our Android device the script that allows us to fix the date of creation of photos and videos of a single file or directory.
adb shell
s=${EXTERNAL_STORAGE:-/sdcard}
echo $s
cd $s
# single file
sh ./SetTimestampByFilenameAndroidAdb.sh DCIM/Camera/IMG_20230105_123335.jpg
sh ./SetTimestampByFilenameAndroidAdb.sh DCIM/Camera/VID_20230105_124001.mp4
# or all file in a directory
sh ./SetTimestampByFilenameAndroidAdb.sh DCIM/Camera
References
- SetTimestampByEximOrFilename
- Linux on Wikipedia and kernel.org
- Windows on Wikipedia and ufficial site
- Android on Wikipedia and ufficial site
- adb
- exif and exiftool (Wikipedia and ufficial site)
- WhatsApp and Google Drive
- Shell Bash and PowerShell (see ours Faq Bash)
- PowerShell Execution Policies
- GitHub
- Ubuntu, RedHat (RHEL), AlmaLinux, Rocky Linux, CentOS