Changing the file creation date on macOS
If you modify a file in-place using sed
with the -i
option, you will get a file that has a new file creation date. On macOS 13.3.1, this is absolutely 100% true, although you will read claims otherwise. I ran into this problem while implementing a Hazel rule that updates YAML automatically in my Obsidian notes.
Background
I have use YAML frontmatter in my Obsidian notes. It looks like:
---
uid: 20221120152124
aliases: [20221120152124, AllAboutShell]
cdate: 2022-11-20 15:21
mdate: 2023-05-18 05:14
type: zettel
---
My goal is to update the mdate
field whenever the file changes. Hazel is the perfect tool for this, so I set about writing a rule that covers this case. The heart of the rule is a shell script action that writes the modification date:
FILE="$1"
# the date the file was modified
MODDATE=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$FILE")
sed -i '' -E "s/(modification date:).*/\1 $MODDATE/g" "$FILE"
sed -i '' -E "s/(mdate:).*/\1 $MODDATE/g" "$FILE"
It works well; but there’s one catch: sed
allegedly is modifying the file in-place, but it does not, as evidenced by the fact that the file has a new creation date.
Why is the file creation date so important to maintain anyway?
If we’re working with notes in an Obsidian vault, this is what Dataview queries use to find notes created on a certain date. If I’m constantly re-creating notes with new creation dates, then I never have an accurate record of notes created on a particular date.
Solution
The solution, on macOS only, is to grab the file creation date, make our sed
substitution, then re-apply the original file creation date. Fortunately in the Xcode command line developer tools (which you need to have installed) there are two utilities that can help here:
SetFile
(also known assetfile
)GetFileInfo
(getfileinfo
)
GetFileInfo
Running GetFileInfo
on an arbitrary file, we see:
➜ ~ GetFileInfo -d “archive.txt”
05/21/2023 07:09:21
SetFile
SetFile
works in a similar fashion. We can update the creation date in this way:
➜ ~ SetFile -d "02/14/2023 06:00" “archive.txt”
➜ ~ GetFileInfo -d “archive.txt”
02/14/2023 06:00:00
Assembling a solution
Since we can now read and write the file creation date, we just need to bracket our YAML modification with GetFileInfo
and SetFile
calls. That way Obsidian will never see the change as a new file. The original shell script action in the Hazel rule now looks like:
# update the modified field, but in so doing, try to
# preserve the original creation date.
FILE="$1"
# the date the file was modified
MODDATE=$(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$FILE")
# get the file system creation date
CDATE=$(GetFileInfo -d "$FILE")
sed -i '' -E "s/(modification date:).*/\1 $MODDATE/g" "$FILE"
sed -i '' -E "s/(mdate:).*/\1 $MODDATE/g" "$FILE"
SetFile -d "$CDATE" "$FILE"
Now we can use sed
inside a Hazel rule, modying the file whose content has changed without altering the file creation date. Obsidian then is happy; and I’m happy.
References
- setfile(1) -
SetFile
man page - getfileinfo(1) -
GetFileInfo
man page