From 0dbb6320b87c2517895e45b9a8d6d84882f389bb Mon Sep 17 00:00:00 2001 From: AZero13 Date: Thu, 11 Dec 2025 10:59:31 -0500 Subject: [PATCH] Use st_birthtimespec instead of st_ctim.tv_sec on Darwin st_ctim records the last time the inode changed. Closest thing on linux, but st_birthtimespec is what we actually want here. --- .../FileManager/FileManager+Files.swift | 2 +- .../FileManager/FileManagerTests.swift | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift index b2666ccbc..901e39f1c 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift @@ -155,7 +155,7 @@ extension stat { var creationDate: Date { #if canImport(Darwin) - Date(seconds: TimeInterval(st_ctimespec.tv_sec), nanoSeconds: TimeInterval(st_ctimespec.tv_nsec)) + Date(seconds: TimeInterval(st_birthtimespec.tv_sec), nanoSeconds: TimeInterval(st_birthtimespec.tv_nsec)) #else Date(seconds: TimeInterval(st_ctim.tv_sec), nanoSeconds: TimeInterval(st_ctim.tv_nsec)) #endif diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift index 44893c17e..982a77d2d 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift @@ -1093,6 +1093,24 @@ private struct FileManagerTests { } #endif + @Test(.enabled(if: isDarwin, "Birth time is only exposed on Darwin")) + func creationDateUsesBirthTime() async throws { + try await FilePlayground { + File("file") + }.test { fileManager in + let attrs1 = try fileManager.attributesOfItem(atPath: "file") + let creation1 = try #require(attrs1[.creationDate] as? Date) + + // Mutate metadata to advance ctime without touching birth time + try fileManager.setAttributes([.posixPermissions: 0o600], ofItemAtPath: "file") + + let attrs2 = try fileManager.attributesOfItem(atPath: "file") + let creation2 = try #require(attrs2[.creationDate] as? Date) + + #expect(creation1 == creation2, "Creation date should remain stable when metadata changes (uses birth time, not ctime)") + } + } + #if canImport(Darwin) @Test func SearchPathsWithoutExpandingTilde() async throws { for path in _DarwinSearchPaths(for: .libraryDirectory, in: .userDomainMask, expandTilde: false) {