Sorts files into folders, by date, using embedded or filesystem metadata.
You type this:
bydate move src_dir dest_dir
This happens:
[1/3] src_dir/subdir1/IMG_0213.JPG -> dest_dir/2017/01/02/IMG_0213.JPG
[2/3] src_dir/subdir2/IMG_0215.JPG -> dest_dir/2017/01/08/IMG_0215.JPG
[3/3] src_dir/subdir3/Video112.mov -> dest_dir/2017/02/10/Video112.mov
Copy or move file(s) based on date:
bydate copy|move SOURCE_DIR_OR_FILE DEST_DIR
Just show where file(s) would be copied or moved to:
bydate show SOURCE_DIR_OR_FILE DEST_DIR
- Source file(s) will be copied or moved into the destination,
beneath a folder based on the year, month, and day of the file.
For example, files whose date is Jan 1st 2017 will be copied or moved
into
dest_dir/2017/01/01/ - To determine the authoritative date of the file, the metadata inside
the file is examined first. The
CreateDateEXIF tag is used as the primary source. Values in the canonicalYYYY:MM:DD HH:MM:SSform are used as-is (they are now range-checked); a number of other unambiguous formats (ISO variants, ctime, and order-forced numeric dates -- see "Supported CreateDate formats") are normalized to the canonical form with a per-file note. If the tag is missing, zero, or cannot be parsed unambiguously, the file's filesystem modification time (mtime) is used instead. - After copying or moving it, the filesystem date will be set on the destination file according to the authoritative date as determined above.
- Files will never be overwritten; if the destination file already exists, the source file will be left in place and the destination won't be touched.
CreateDate values are trusted only when they can be parsed
unambiguously into a real calendar date:
YYYY:MM:DD HH:MM:SS— the canonical exiftool form (range-checked).YYYY-MM-DD,YYYY/MM/DD, with an optional time after a space orT(e.g.2004-06-13,2004/06/13 10:20:30).- ctime style:
Dow Mon D HH:MM:SS YYYY(e.g.Sat Sep 4 16:12:41 2004); the weekday is recognized but not verified against the date. A/B/YYorA/B/YYYY(slash or dash separated), with an optional time (HH:MM:SS, orH:MM[:SS] AM/PM). These are accepted only when some field is greater than 12, which forces the month/day order (28/01/97-> 1997-01-28;01/02/97is ambiguous and is rejected). Two-digit years pivot at 69:00-68-> 20xx,69-99-> 19xx.
Every accepted value is range-checked (month 1-12, valid day for the
month including leap years, hour <= 23, minute/second <= 59).
Normalized values are converted to the canonical form with a per-file
note; anything else is rejected with a warning and the file falls back
to its filesystem mtime.
Make sure you have the dependencies:
- You must have
bashversion 4 or greater. exiftoolmust be in your PATHjqmust be in your path
On macos, these can all be installed via brew. On other OSes, you
probably already have a new enough version of bash, and the
other dependencies are pretty straightforward to install manually
if your usual package repository doesn't have them.
Then you can put this script somewhere easy to execute, like
/usr/local/bin/bydate, and make sure it's executable and in your
PATH.
- Improve speed by sending batches of files to exiftool, rather than one at a time.
- Leverage the built-in renaming/moving file support in exiftool: https://www.sno.phy.queensu.ca/~phil/exiftool/filename.html
- For each file, show whether EXIF or filesystem metadata was used to determine the date.
-
2026-08-20
- Treat CreateDates in non-ISO formats (e.g.,
28/01/97,Sat Jun 12 19:16:03 2004) as invalid. Previously they produced garbage directory paths and atouchfailure that aborted the run; now the file falls back to its filesystem mtime, with a warning. - Fixed macOS compatibility: the filesystem mtime fallback now uses
BSD
stat -f '%Sm'when GNUstat -c '%y'is unavailable, andtest.shsets fixture mtimes with portabletouch -tinstead of GNU-onlytouch -d. - Parse CreateDates in unambiguous non-ISO formats (ISO variants such
as
2004-06-13T10:20:30, ctime such asSat Sep 4 16:12:41 2004, and order-forced numeric dates such as28/01/97 14:30:00) instead of rejecting them; see "Supported CreateDate formats". Canonical values are now range-checked. The fixture dates ofFont.pfb,Font.afm,Font.pfa,FITS.fits, andPostScript.epschanged from the 1999-01-01 mtime fallback to their real dates, and eight new FITS fixtures plustest-parse.shunit tests were added.
- Treat CreateDates in non-ISO formats (e.g.,
-
2026-05-03
- Fixed handling of invalid/zero CreateDate values (e.g.,
0000:00:00) from exiftool, which previously caused errors and incorrect directory paths. Falls back to the file's filesystem modification time instead. - Rewrote date extraction to avoid bash word-splitting bugs with timestamp formats containing spaces. Now uses separate jq queries for each field.
- Handle spaces in filenames and paths correctly throughout the script.
- Fixed handling of invalid/zero CreateDate values (e.g.,
-
2019-04-17
- Switched main script from ruby to bash, simplifying deployment.
- Now using
exiftoolfor broader EXIF awareness (MOV, HEIC, etc.) This version is noticeably slower due to every file being examined by exiftool.
-
2017-02-12
- Original version, required ruby
- Only supported EXIF metadata on JPEG files