normfn is a command-line utility that helps you to 'normalize' (rename) files
and directories to use a leading ISO-8601 date prefix
(YYYY-MM-DD-rest-of-name.ext), inspired by Mark Hurst's file naming strategy
from Bit Literacy. The date is derived from the
filename or one of the file's timestamps.
ISO-8601 is used because it is the only date format that is unambiguous across
locales - 03/04/2024 means March 4th in the US and April 3rd in the UK, but
2024-03-04 means exactly one thing everywhere. It also sorts
lexicographically: alphabetical order and chronological order are the same
thing, so files sort naturally and consistently in any file manager, shell
listing, or search result.
Embedding timestamps in a filename is helpful because OS-level file timestamps (creation time, modification time) are fragile. They are routinely lost when files are copied, emailed, or moved between systems. Embedding the date directly in the filename makes it a permanent part of the file's identity.
A short video introduction to normfn.
Add a date prefix to a file that has none (uses the file's oldest timestamp):
(-v is optional, but shows renames):
$ normfn -v report.pdf
INFO: report.pdf moved to 2026-03-27-report.pdfReformat a date already in the filename to ISO-8601:
$ normfn -v "Invoice_2024_03_15_acme.pdf"
INFO: Invoice_2024_03_15_acme.pdf moved to 2024-03-15-Invoice_acme.pdfPreview what would happen without making changes:
$ normfn --dry-run *.txt
INFO: Not moving notes.txt to 2026-03-27-notes.txt; dry run.
INFO: Not moving todo-25-12-2025.txt to 2025-12-25-todo.txt; dry run.Add a time component to the prefix:
$ normfn -v --add-time meeting-notes.docx
INFO: meeting-notes.docx moved to 2026-03-27T09-15-00-meeting-notes.docxReplace the entire filename with just the date prefix:
$ normfn -v --discard-existing-name "Invoice_2024_03_15_acme.pdf"
INFO: Invoice_2024_03_15_acme.pdf moved to 2024-03-15.pdfRename a directory and all files inside it recursively:
$ normfn -v -r project/
INFO: project/ moved to 2026-03-27-project/
INFO: 2026-03-27-project/report.pdf moved to 2026-03-27-project/2024-06-01-report.pdf
INFO: 2026-03-27-project/notes.txt moved to 2026-03-27-project/2026-03-27-notes.txt-
Intelligent date detection: Recognises a wide range of date formats already embedded in filenames (e.g.
2024_03_15,15-03-2024,March 15 2024) and reformats them to ISO-8601. -
Timestamp fallback: When no date is found in the filename, normfn falls back to the file's filesystem timestamps (ctime and mtime) or the current time. By default (
--earliest), it uses the oldest;--latestuses the newest;--nowalways uses the current time. Note: on Linux and macOS, ctime is not file creation time. -
PDF metadata: For PDF files, normfn reads the embedded creation date from the file's metadata (if available and the optional
pypdflibrary is installed), preferring it over filesystem timestamps. -
Time-based naming: Use
--add-timeto include the time of day (not just the date) in the prefix, producing filenames like2026-03-27T09-15-00-report.pdf. -
Recursive processing: Use
-r/--recursiveto rename files throughout an entire directory tree. -
Undo log: Every rename is recorded as a shell command in
~/.local/state/normfn-undo.log.shso it can be reversed. (See the comments at the top of that file for instructions on how to undo changes.) -
Default exclusions: By default, normfn skips hidden files, lock files, files inside version-control directories, and other misc files. Use
--allto override this.
normfn requires at least Python 3.12.
To install the latest development version directly from GitHub:
pip install git+https://github.com/andrewferrier/normfn
# or
pipx install git+https://github.com/andrewferrier/normfn
# or
uv tool install git+https://github.com/andrewferrier/normfnDownload the .deb file from the Assets of the latest
release and install
using any standard .deb installation approach, e.g.:
dpkg -i normfn*.debDownload the .pkg.tar.zst file from the Assets of the latest
release and install
with pacman:
pacman -U normfn-*.pkg.tar.zstShell completion is automatically included when installing via Debian or Arch Linux.
For pip or uv installs, generate and install the completion script manually. Replace bash with zsh or tcsh as appropriate:
# bash
normfn --completions bash > ~/.local/share/bash-completion/completions/normfn
# zsh (add to a directory on your $fpath)
normfn --completions zsh > ~/.zfunc/_normfn
# tcsh
normfn --completions tcsh > ~/.normfn.tcsh
echo 'source ~/.normfn.tcsh' >> ~/.tcshrcusage: normfn [-v] [-h] [-V] [--config PATH] [--initialize-config] [-n] [-a]
[-f] [-t] [-d] [-r] [--now | --latest | --earliest]
[--completions [{bash,zsh,tcsh}]]
[filename ...]
Normalizes filenames by prefixing a date to them. See
https://github.com/andrewferrier/normfn for more information.
positional arguments:
filename Filenames
options:
-v, --verbose Add debugging output. Using this twice makes it doubly
verbose.
-h, --help Show help information for normfn.
-V, --version Show the version of normfn and exit.
--config PATH Path to the configuration file. Defaults to
/home/runner/.config/normfn/normfn.toml.
--initialize-config Create a template configuration file at the path given
by --config (default:
/home/runner/.config/normfn/normfn.toml) and exit.
Fails if the file already exists.
-n, --dry-run Don't actually make any changes, just show them.
Forces a single level of verbosity (-v).
-a, --all Affect all files, including those in default exclude
lists.
-f, --force Overwrite target files if they already exist (USE WITH
CAUTION, consider using --dry-run first).
-t, --add-time If a time is not found in the filename, add one.
-d, --discard-existing-name
Discard existing name and just use the date/time
prefix.
-r, --recursive Recurse into directories specified on the command
line. The default is not to do this, and simply look
at the name of the directory itself.
--now Use date and time now as the default file prefix for
filenames without them.
--latest, --newest Use the latest of ctime and mtime to define a file
prefix for files without them. Note: ctime is *not*
file creation on Linux/macOS; see
http://lwn.net/Articles/397442/.
--earliest, --oldest Use earliest of ctime and mtime to define a file
prefix for files without them. This is the default.
--completions [{bash,zsh,tcsh}]
Output a shell completion script, then exit. Shell is
auto-detected from $SHELL if not specified.
For safety, by default, normfn keeps a log file in
~/.local/state/normfn-undo.log.sh of all the actions it takes, in
shell format to make it easier to undo them. See the comment at the head of that
file (once normfn has generated it) for more information. The undo log location
and other persistent preferences are configured via the config file (see below).
For more information on all the options available, run normfn --help.
normfn reads persistent preferences from a TOML configuration file located at
${XDG_CONFIG_HOME}/normfn/normfn.toml.
If XDG_CONFIG_HOME is not set, it defaults to ~/.config, so the effective
default path is ~/.config/normfn/normfn.toml. If the file does not exist,
normfn silently uses its built-in defaults.
To create a starter config file (with all options commented out and annotated), run:
normfn --initialize-configAll the options are documented as comments in the generated file itself.
Project hosted on github.