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 as setfile)
  • 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