This started out short and grew as I realized how many ways it could be used and how many edge cases there might be. I’m sure there are still situations I haven’t covered, so please please please test this before unleashing it and definitely have a backup of all your files before you do anything else.
The script is intended to be named redate
and saved in your $PATH
. As explained in the usage message near the top of the script, it has one main option, -t
, which doesn’t do any renaming but shows you how the script would rename the files passed to it.
It can accept any number of files as arguments, so you can run it as
redate *.pdf
from within a folder of your dated PDFs. It will also work with nested folders, so if you are in a folder that contains folders of dated PDFs, you can run
redate */*.pdf
It skips files that are already in the desired YYYY-MM-DD format and files for which the stem (the file name without the extension) can’t be parsed as a date. It assumes the US convention for numeric dates, so 3-2-20 is taken as March 2, not February 3.
If there are multiple files that have different names but parse to the same date, it adds ~n to the file stem. So
02-28-20.pdf
02-28-2020.pdf
2-28-20.pdf
get renamed to
2020-02-28.pdf
2020-02-28~2.pdf
2020-02-28~3.pdf
I’m pretty sure it could be turned into a Quick Action (what we used to call a Service) through Automator, but I haven’t tried that.
Although I normally work in Python 3, this was written to run in the Python 2.7 that comes with macOS. It uses no libraries that aren’t installed with the system. I think the comments are good enough for you to modify it if it doesn’t quite meet your needs.
Again, don’t trust me to have covered all the bases. Make sure you have backups and test before you leap.
#!/usr/bin/python
from dateutil.parser import parse
import os.path
import os
import sys
from getopt import getopt, GetoptError
usage = """Usage: redate [-th] FILES
Rename files with date names to YYYY-MM-DD format.
-t Don't rename files; test by showing how they'd be renamed
-h Print this help message
Skip files with names that cannot be parsed as dates and files
that are already in the desired format. If files with different
names parse to the same date, i.e., 2-28-20.txt and 02-28-20.txt,
add ~2, ~3, etc. to the base file name.
"""
# Initialize the test conditional and the list of new filenames
test = False
newpaths = []
# Handle any command-line options
try:
opts, args = getopt(sys.argv[1:], 'th')
except GetoptError as err:
print str(err)
print usage
sys.exit()
for o, v in opts:
if o == "-t":
test = True
else:
print usage
sys.exit()
# Loop through all the file path arguments
for path in args:
# Get all the parts of the absolute file path
oldpath = os.path.abspath(path)
directory, name = os.path.split(oldpath)
stem, ext = os.path.splitext(name)
# Parse the date and construct a new file name and absolute path
try:
filedate = parse(stem, yearfirst=False, dayfirst=False)
except ValueError:
sys.stderr.write("Date parsing error on {}. Skipping...\n".format(name))
continue
newstem = filedate.strftime("%Y-%m-%d")
newname = "{}{}".format(newstem, ext)
newpath = os.path.join(directory, newname)
# Skip if the new name is the same as the old
if newpath == oldpath:
continue
# Handle naming conflicts by appending ~n to the date
if os.path.exists(newpath) or newpath in newpaths:
n = 2
newnamen = "{}~{}{}".format(newstem, n, ext)
newpathn = os.path.join(directory, newnamen)
while os.path.exists(newpathn) or newpathn in newpaths:
n += 1
newnamen = "{}~{}{}".format(newstem, n, ext)
newpathn = os.path.join(directory, newnamen)
newname = newnamen
newpath = newpathn
# Rename the file or show how it would be renamed
if test:
print "{}\n{} -> {}".format(directory, name, newname)
print
else:
os.rename(oldpath, newpath)
newpaths.append(newpath)