Skip to content

Instantly share code, notes, and snippets.

@leonroy
Last active April 11, 2022 08:33
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save leonroy/5b764856d56990e1dbac to your computer and use it in GitHub Desktop.
Save leonroy/5b764856d56990e1dbac to your computer and use it in GitHub Desktop.
Convert Things database to CSV. This script produces two CSVs on the user's Desktop. One for todos and the other for projects. It requires that Things.app is installed.
---------
-- Convert Things (from Cultured Code) database to CSV
-- https://culturedcode.com/things/
--
-- Version 1.0
--
-- This script produces two CSVs on the user's Desktop. One for todos and the other for projects.
-- It requires that Things.app is installed.
--
-- Things todos and projects can be tied either using the project name in the todos CSV or better
-- the project id in the todos CSV since this is guaranteed to be unique.
--
-- Project Areas are not present since I had difficulty pulling them out and don't use them so
-- didn't investigate further.
--
-- Known Issues:
-- This script is slow as molasses. Seems to cause Things to hit 100% CPU usage on a single core.
-- Guessing perhaps the Things Applescript implementation isn't parallelized at all or my
-- first attempt at Applescript-ing needs some refinement!
--
-- Also I haven't delineated Inbox, Today, Next, Scheduled, Someday etc. A grievous oversight I know
-- but to be frank I only use Today and I couldn't come up with an elegant approach to pulling those
-- categories out of Things without hard coding them into my script and iterating over each one in
-- turn.
---------
# Setup todo file
set todoFilePath to (path to desktop as Unicode text) & "Things Backup - todo - " & replace_chars(dateISOformat(current date), ":", "-") & ".csv"
set todoFile to (open for access file todoFilePath with write permission)
set eof of todoFile to 0
write "name,status,tag names,cancellation date,due date,modification date,contact,project name, project id,area,activation date,id,completion date,creation date,notes" & linefeed to todoFile
# Setup project file
set projectFilePath to (path to desktop as Unicode text) & "Things Backup - project - " & replace_chars(dateISOformat(current date), ":", "-") & ".csv"
set projectFile to (open for access file projectFilePath with write permission)
set eof of projectFile to 0
write "name,status,tag names,cancellation date,due date,modification date,contact,area,activation date,id,completion date,creation date,notes" & linefeed to projectFile
tell application "Things"
-- debug - can use the below counter to exit the todo loop after N iterations for debugging
-- set counter to 0
repeat with toDo in to dos
set toDoName to my escapeTextToCsv(my trim_line(my formatEmptyValue(name of toDo), " ", 2))
set toDoStatus to my formatEmptyValue(status of toDo)
set toDoTags to my escapeTextToCsv(my formatEmptyValue(tag names of toDo))
set toDoCancellation to my dateISOformat(cancellation date of toDo)
set toDoDue to my dateISOformat(due date of toDo)
set toDoModification to my dateISOformat(modification date of toDo)
set toDoContact to my formatEmptyValue(contact of toDo)
set toDoProject to project of toDo
set toDoProjectName to ""
set toDoProjectId to ""
if (toDoProject is not missing value) then
set toDoProjectName to name of toDoProject
set toDoProjectId to id of toDoProject
end if
set toDoAreaName to ""
set toDoActivation to my dateISOformat(activation date of toDo)
set toDoId to id of toDo
set toDoCompletion to my dateISOformat(completion date of toDo)
set toDoCreation to my dateISOformat(creation date of toDo)
set toDoNotes to my escapeTextToCsv(my trim_line(my formatEmptyValue(notes of toDo), " ", 2))
write toDoName & "," & toDoStatus & "," & toDoTags & "," & toDoCancellation & "," & toDoDue & "," & toDoModification & "," & toDoContact & "," & toDoProjectName & "," & toDoProjectId & "," & toDoAreaName & "," & toDoActivation & "," & toDoId & "," & toDoCompletion & "," & toDoCreation & "," & toDoNotes & linefeed to todoFile
-- debug - can use the below counter to exit the todo loop after N iterations for debugging (default 10)
--get properties of toDo
--set counter to 1 + (counter)
--if (counter) is 10 then
-- exit repeat
--end if
end repeat
repeat with pr in projects
set prName to my escapeTextToCsv(my trim_line(my formatEmptyValue(name of pr), " ", 2))
set prStatus to my formatEmptyValue(status of pr)
set prTags to my escapeTextToCsv(my formatEmptyValue(tag names of pr))
set prCancellation to my dateISOformat(cancellation date of pr)
set prDue to my dateISOformat(due date of pr)
set prModification to my dateISOformat(modification date of pr)
set prContact to my formatEmptyValue(contact of pr)
set prAreaName to ""
set prActivation to my dateISOformat(activation date of pr)
set prId to id of pr
set prCompletion to my dateISOformat(completion date of pr)
set prCreation to my dateISOformat(creation date of pr)
set prNotes to my escapeTextToCsv(my trim_line(my formatEmptyValue(notes of pr), " ", 2))
write prName & "," & prStatus & "," & prTags & "," & prCancellation & "," & prDue & "," & prModification & "," & prContact & "," & prAreaName & "," & prActivation & "," & prId & "," & prCompletion & "," & prCreation & "," & prNotes & linefeed to projectFile
-- debug --
#get properties of pr
end repeat
end tell
on formatEmptyValue(theValue)
if theValue is missing value then
set theValue to ""
end if
return theValue
end formatEmptyValue
on dateISOformat(theDate)
if theDate is missing value then
set theDate to ""
else
set y to text -4 thru -1 of ("0000" & (year of theDate))
set m to text -2 thru -1 of ("00" & ((month of theDate) as integer))
set d to text -2 thru -1 of ("00" & (day of theDate))
set t to time string of theDate
set theDate to y & "-" & m & "-" & d & " " & t
end if
return theDate
end dateISOformat
on escapeTextToCsv(theText)
# replace all single quotes with double
# wrap all strings with double quotes
set the result to the replace_chars(theText, "\"", "\"\"")
set result to "\"" & result & "\""
return result
end escapeTextToCsv
on replace_chars(this_text, search_string, replacement_string)
set AppleScript's text item delimiters to the search_string
set the item_list to every text item of this_text
set AppleScript's text item delimiters to the replacement_string
set this_text to the item_list as string
set AppleScript's text item delimiters to ""
return this_text
end replace_chars
on trim_line(this_text, trim_chars, trim_indicator)
-- 0 = beginning, 1 = end, 2 = both
set x to the length of the trim_chars
-- TRIM BEGINNING
if the trim_indicator is in {0, 2} then
repeat while this_text begins with the trim_chars
try
set this_text to characters (x + 1) thru -1 of this_text as string
on error
-- the text contains nothing but the trim characters
return ""
end try
end repeat
end if
-- TRIM ENDING
if the trim_indicator is in {1, 2} then
repeat while this_text ends with the trim_chars
try
set this_text to characters 1 thru -(x + 1) of this_text as string
on error
-- the text contains nothing but the trim characters
return ""
end try
end repeat
end if
return this_text
end trim_line
@leonroy
Copy link
Author

leonroy commented Nov 14, 2015

@Violetsteel
Copy link

Super handy. Made my migration so much easier. Thank you!

@leonroy
Copy link
Author

leonroy commented Apr 11, 2022

Glad it proved useful after all these years 😁

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment