List Folders Older Than 30 Days

Does anyone have an existing script that will look at a specific folder, with many subfolders, and create a list of those folders that are more than 30 days old or older?

I actually have a script (below) that was created here that will list documents older than 7 days, but no matter what I try to do, I can not convert it to folders.

use framework "Foundation"
use scripting additions

on main()
	set theFolder to "/Users/homer/Documents"
	set theDays to 7 --set to desired value
	set theFiles to getFiles(theFolder, theDays)
	set folderTree to getFolderTree(theFiles)
	writeFile(folderTree)
end main

on getFiles(theFolder, theDays)
	set fileManager to current application's NSFileManager's defaultManager()
	set theFolder to current application's |NSURL|'s fileURLWithPath:theFolder
	set dateKey to current application's NSURLContentModificationDateKey
	set directoryKey to current application's NSURLIsDirectoryKey
	set packageKey to current application's NSURLIsPackageKey
	set folderContents to (fileManager's enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:6 errorHandler:(missing value))'s allObjects() --option 6 skips hidden files and package contents
	set filterDate to current application's NSDate's dateWithTimeIntervalSinceNow:(-86400 * theDays)
	set theFiles to current application's NSMutableArray's new()
	repeat with anItem in folderContents
		set {theResult, aDate} to (anItem's getResourceValue:(reference) forKey:dateKey |error|:(missing value))
		if (aDate's compare:filterDate) = 1 then --1 is NSOrderedDescending
			set {theResult, aDirectory} to (anItem's getResourceValue:(reference) forKey:directoryKey |error|:(missing value))
			if (aDirectory as boolean) is false then
				(theFiles's addObject:anItem)
			else
				set {theResult, aPackage} to (anItem's getResourceValue:(reference) forKey:packageKey |error|:(missing value))
				if (aPackage as boolean) is true then (theFiles's addObject:anItem)
			end if
		end if
	end repeat
	if theFiles's |count|() = 0 then display dialog "No matching files found" buttons {"OK"} cancel button 1
	set pathDescriptor to current application's NSSortDescriptor's sortDescriptorWithKey:"stringByDeletingLastPathComponent" ascending:true selector:"localizedStandardCompare:"
	set nameDescriptor to current application's NSSortDescriptor's sortDescriptorWithKey:"lastPathComponent" ascending:true selector:"localizedStandardCompare:"
	return ((theFiles's valueForKey:"path")'s sortedArrayUsingDescriptors:{pathDescriptor, nameDescriptor})
end getFiles

on getFolderTree(theFiles)
	set fileTree to current application's NSMutableArray's new()
	set previousFolder to (theFiles's objectAtIndex:0)'s stringByDeletingLastPathComponent()
	fileTree's addObject:(previousFolder's stringByAppendingString:"/")
	repeat with aFile in theFiles
		set aFolder to aFile's stringByDeletingLastPathComponent()
		set aFileName to aFile's lastPathComponent()
		if (aFolder's isEqualToString:previousFolder) is false then
			(fileTree's addObject:"")
			(fileTree's addObject:(aFolder's stringByAppendingString:"/"))
			(fileTree's addObject:aFileName)
		else
			(fileTree's addObject:aFileName)
		end if
		set previousFolder to aFolder
	end repeat
	return ((fileTree's componentsJoinedByString:linefeed) as text)
end getFolderTree

on writeFile(theText)
	set pdfMargins to 25
	set targetTextFile to ((current application's NSTemporaryDirectory())'s stringByAppendingPathComponent:"Recent File Changes.txt") as text
	set targetPdfFile to (((current application's NSHomeDirectory())'s stringByAppendingPathComponent:"Desktop")'s stringByAppendingPathComponent:"Recent File Changes.pdf") as text
	(current application's NSString's stringWithString:theText)'s writeToFile:targetTextFile atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
	do shell script "cupsfilter -D -o page-left=" & pdfMargins & " -o page-right=" & pdfMargins & " -o page-top=" & pdfMargins & " -o page-bottom=" & pdfMargins & " " & quoted form of targetTextFile & " > " & quoted form of targetPdfFile
end writeFile

main()

On any volume indexed by Spotlight it’s pretty convenient to search for the items with mdfind

set inputFolder to quoted form of POSIX path of (choose folder)
set foundFolders to paragraphs of (do shell script "mdfind -onlyin " & inputFolder & " 'kMDItemKind = \"Folder\" && kMDItemContentCreationDate < $time.today(-30)'")

The search criteria are

  • the kind (kMDItemKind) is Folder
  • the creation date (kMDItemContentCreationDate) comes before today - 30 days ($time.today(-30))

The result in foundFolders is a list of POSIX paths.

For the modification date use kMDItemContentModificationDate or kMDItemFSContentChangeDate.

1 Like

What do you mean by older than 30 days.

Last time access, date created, date modified etc?

1 Like

Good point. There are Spotlight specifiers for all occasions.

Unless the number of folders is quite large, you can do what you want with Finder. The script in your post uses modification date and the same is used in the following:

set theFolder to (choose folder)
set archiveDate to (current date) - 30 * days

set oldFolders to {}
tell application "Finder"
	set theFolders to every folder of the entire contents of theFolder as alias list
	repeat with aFolder in theFolders
		set folderDate to modification date of aFolder
		if folderDate is less than archiveDate then set end of oldFolders to (aFolder as text)
	end repeat
end tell
 
return oldFolders

Seeing how I’m going to be looking at folders created by backup programs, I would think date created (probably should have included that bit of information).

I have a script that is pure AppleScript

local sw, c, cf, i, folderPaths, modDates, oldDate
tell application "System Events"
	set {folderPaths, modDates} to {path, modification date} of folders of folder "/Users/Homer/Desktop/MacScripter Files/"
	set cf to count folderPaths
	set c to 1
	set oldDate to ((current date) - 30 * days)
	repeat with i from 1 to cf
		if (item i of modDates) ≥ oldDate then
			if i ≠ c then
				tell modDates
					set sw to item i
					set item i to item c
					set item c to sw
				end tell
				tell folderPaths
					set sw to item i
					set item i to item c
					set item c to sw
				end tell
			end if
			set c to c + 1
		end if
	end repeat
	if c > 1 then
		set modDates to items c thru -1 of modDates
		set folderPaths to items c thru -1 of folderPaths
	else if c > cf then
		set modDates to {}
		set folderPaths to {}
	end if
end tell
get {modDates, folderPaths}

The path is in folderPaths, the date modified is in modDates

I copied my MacScripter Files folder to the desktop and first tried the script posted by StefanK in post #2, and then changed peavine’s script in post #5 from “modification date” to “creation date” and both scripts found the same 12 folders that were older than 30 days. Thanks guys.

Could not figure out how to get your script to select the “MacScripter Files” folder that I copied to the Desktop. Where would I insert the path, which is “Users/Homer/Desktop/MacScripter Files”?

I fixed it for you above.

Since you are using POSIX paths instead of HFS paths

You can use the shell’s find command.

% find '/Users/homer/Documents' -type d -depth 1 ! -Btime -30

The above breaks down as follows:

find '/Users/homer/Documents'’ means ‘find within the specified directory’. Alternatively, you could set it as a variable or could change the shell’s working directory.

-type d’ restricts the results to directories. You can also use ‘f’ to specify files; there are some other less obvious types as well.

-depth 1’ restricts the results to those in the top directory of the search. If you wish to generate results from subdirectories, then increase the number accordingly or remove the option entirely.

! -Btime -30’ has three components.

  • The ‘B’ at the beginning of ‘Btime’ means check the creation time. You can also look for the last modified time using ‘mtime’. Note that this option is case sensitive.

  • The ‘-30’ means 30 days. Normally, the option ‘-Btime -30’ would list results created within the last thirty days

  • The preceding ‘!’ reverses that to not within the last thirty days.

Here is how an applescript might look, assuming the Documents folder:

use scripting additions
set cmd to "find '/Users/homer/Documents' -type d -depth 1 ! -Btime -30"
set older30 to do shell script cmd
1 Like

Homer712. The following is the script you posted but edited to return folders with a creation date older than 30 days. In my testing, it returned the same number of folders as Stefan’s and my scripts earlier in this thread. It’s marginally slower than Stefan’s suggestion, but it works with drives that are not indexed.

use framework "Foundation"
use scripting additions

set theFolder to POSIX path of (choose folder)
set theDays to 30 --set to desired value
set theFolders to getFolders(theFolder, theDays)

on getFolders(theFolder, theDays)
	set fileManager to current application's NSFileManager's defaultManager()
	set theFolder to current application's |NSURL|'s fileURLWithPath:theFolder
	set dateKey to current application's NSURLCreationDateKey
	set directoryKey to current application's NSURLIsDirectoryKey
	set packageKey to current application's NSURLIsPackageKey
	set folderContents to (fileManager's enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:6 errorHandler:(missing value))'s allObjects() --option 6 skips hidden files and package contents
	set filterDate to current application's NSDate's dateWithTimeIntervalSinceNow:(-86400 * theDays)
	set theFolders to current application's NSMutableArray's new()
	repeat with anItem in folderContents
		set {theResult, aDate} to (anItem's getResourceValue:(reference) forKey:dateKey |error|:(missing value))
		if (aDate's compare:filterDate) = -1 then
			set {theResult, aDirectory} to (anItem's getResourceValue:(reference) forKey:directoryKey |error|:(missing value))
			if (aDirectory as boolean) is true then
				set {theResult, aPackage} to (anItem's getResourceValue:(reference) forKey:packageKey |error|:(missing value))
				if (aPackage as boolean) is false then (theFolders's addObject:anItem)
			end if
		end if
	end repeat
	return (theFolders's valueForKey:"path") as list
end getFolders

That works incredibly well. One last request, would it be possible to have the script put a simple text document on the desktop with the list of files/folders found?

Just so you know, The AScriptObjC one from Peavine also returns sub-folders, if that’s what your looking for.

Also, just because it is AScriptObjC, doesn’t mean it’s faster. There is a lot of overhead in calling AScriptObjC. In this case my script is around 2.81 times faster in Script Geek.

When I choose a folder with more and more folders in it, the speed difference gets bigger and bigger.

Here it is cleaned up a bit and turned into a subroutine like Peavine’s

set theFolder to POSIX path of (choose folder)
set theDays to 30 --set to desired value
set theFolders to getFolders(theFolder, theDays)

on getFolders(theFolder, theDays)
	local sw, c, cf, i, folderPaths, modDates, oldDate
	if class of theDays is not in {integer, number} then return false
	tell application "System Events"
		set {folderPaths, modDates} to {POSIX path, modification date} of folders of folder theFolder
		set cf to count folderPaths
		set c to 1
		set oldDate to ((current date) - theDays * days)
		repeat with i from 1 to cf
			if (item i of modDates) ≥ oldDate then
				if i ≠ c then
					tell modDates
						set sw to item i
						set item i to item c
						set item c to sw
					end tell
					tell folderPaths
						set sw to item i
						set item i to item c
						set item c to sw
					end tell
				end if
				set c to c + 1
			end if
		end repeat
	end tell
	if c > cf then
		set modDates to {}
		set folderPaths to {}
	else if c > 1 then
		set modDates to items c thru -1 of modDates
		set folderPaths to items c thru -1 of folderPaths
	end if
	return folderPaths
end getFolders

** EDIT** I did more tests, If I make my script do recursive folders, them ASObjC becomes faster.

Here is my Recursive version…

set theFolder to POSIX path of (choose folder)
set theDays to 30 --set to desired value
set theFolders to getFoldersR(theFolder, theDays)

on getFoldersR(theFolder, theDays) -- Recursive
	script F
		property oldDate : ((current date) - theDays * days)
		
		on getFolders(aFolder)
			local sw, c, cf, i, folderPaths, modDates, foundPaths
			tell application "System Events"
				set {folderPaths, modDates} to {POSIX path, modification date} of folders of folder aFolder
			end tell
			set cf to count folderPaths
			set c to 1
			repeat with i from 1 to cf
				if (item i of modDates) ≥ oldDate then
					if i ≠ c then
						tell modDates
							set sw to item i
							set item i to item c
							set item c to sw
						end tell
						--=modDates
						tell folderPaths
							set sw to item i
							set item i to item c
							set item c to sw
						end tell
						--=folderPaths
					end if
					set c to c + 1
				end if
			end repeat
			
			set foundPaths to {}
			repeat with i from 1 to cf
				if i ≥ c then set end of foundPaths to item i of folderPaths
				set foundPaths to foundPaths & getFolders(item i of folderPaths, theDays)
			end repeat
			return foundPaths
		end getFolders
	end script
	
	if class of theDays is not in {integer, number} then return false
	return F's getFolders(theFolder)
end getFoldersR

Robert, I definitely need the ability to include subfolders. And, I’m not concerned about the speed of the script, as it would be run basically once per month.

This line gives me an error (see screenshot).

on getFoldersR3(theFolder, theDays) -- Recursive

Oops, I had the wrong method name, FIXED

Here is an even shorter version that is recursive. I also now skip folders that are invisible.

set theFolder to POSIX path of (path to documents folder as text)
set theDays to 30 --set to desired value
set theFolders to getFoldersR(theFolder, theDays)

on getFoldersR(theFolder, theDays) -- Recursive FASTEST
	script F
		property oldDate : ((current date) - theDays * days)
		
		on getFolders(aFolder)
			local i, folderPaths, modDates, isVisibles, foundPaths
			tell application "System Events" to set {folderPaths, modDates, isVisibles} to {POSIX path, modification date, visible} of folders of folder aFolder
			set foundPaths to {}
			repeat with i from 1 to count folderPaths
				if item i of isVisibles then
					if (item i of modDates) < oldDate then
						set end of foundPaths to item i of folderPaths
						set foundPaths to foundPaths & getFolders(item i of folderPaths, theDays)
					end if
				end if
			end repeat
			return foundPaths
		end getFolders
	end script
	
	if class of theDays is not in {integer, number} then return false
	return F's getFolders(theFolder)
end getFoldersR

Also, Peavines script finds on Creation date, not modified date.
I changed the line

set dateKey to current application’s* NSURLCreationDateKey

to

set dateKey to current application’s* NSURLContentModificationDateKey

and I still get folders that shouldn’t be in the list.

Mine seems to be more accurate.

Homer712. I added a handler to write the folder data to a text file on the desktop. I also revised the repeat loop to make the date check the last test. This reduced the timing result for the script including the writeFile handler from 111 to 78 milliseconds on a folder with 1078 files in 203 folders. This assumes the Foundation framework is in memory, which would normally be the case.

Based on your post earlier in this thread, the script filters on creation date, but this is easily changed to modification date (see comment in getFolders handler). I did a fair amount of testing, and the results were as expected. Please let me know if you encounter any issues with the script.

use framework "Foundation"
use scripting additions

set theFolder to POSIX path of (choose folder)
set theDays to 30 --set to desired value
set theFolders to getFolders(theFolder, theDays)
writeFile(theFolders)

on getFolders(theFolder, theDays)
	set fileManager to current application's NSFileManager's defaultManager()
	set theFolder to current application's |NSURL|'s fileURLWithPath:theFolder
	set dateKey to current application's NSURLCreationDateKey --or use NSURLContentModificationDateKey
	set directoryKey to current application's NSURLIsDirectoryKey
	set packageKey to current application's NSURLIsPackageKey
	set folderContents to (fileManager's enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:6 errorHandler:(missing value))'s allObjects() --option 6 skips hidden files and package contents
	set filterDate to current application's NSDate's dateWithTimeIntervalSinceNow:(-86400 * theDays)
	set theFolders to current application's NSMutableArray's new()
	repeat with anItem in folderContents
		set {theResult, aDirectory} to (anItem's getResourceValue:(reference) forKey:directoryKey |error|:(missing value))
		if (aDirectory as boolean) is true then
			set {theResult, aPackage} to (anItem's getResourceValue:(reference) forKey:packageKey |error|:(missing value))
			if (aPackage as boolean) is false then
				set {theResult, aDate} to (anItem's getResourceValue:(reference) forKey:dateKey |error|:(missing value))
				if (aDate's compare:filterDate) = -1 then (theFolders's addObject:anItem)
			end if
		end if
	end repeat
	return (theFolders's valueForKey:"path")'s sortedArrayUsingSelector:"localizedStandardCompare:"
end getFolders

on writeFile(theArray)
	set theFile to POSIX path of ((path to desktop as text) & "Old Folders.txt") --set to desired value
	set theString to theArray's componentsJoinedByString:linefeed
	(current application's NSString's stringWithString:theString)'s writeToFile:theFile atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
end writeFile