[script] Import / sync Day One 2 entries to DEVONthink

I worked on a script to import journal entries from a Day One 2 json export. It also does a sort of one-way syncing… if you import the entries, change something in day one, export again, and import again, then it’ll update the record in DT. It sets DT journal entries to locked, so you don’t change it, only ot have it overwritten on the next sync. If you want to modify an entry, you should duplicate it, remove the day one URL (which acts as the link between Day One and DT), and unlock it.

Version 1 strips images from the imported markdown

-- Import Day One journal entries v1
-- Created by Pat Maddox
--
-- This script is provided AS IS!
--
-- Export your Day One journal to JSON, then run this script
--
-- Requires: JSON Helper for AppleScript
-- https://itunes.apple.com/us/app/json-helper-for-applescript/id453114608?mt=12
--
-- Note: this strips any images from the journal entry in DT
--
-- Use at your own risk. I've done my best to create a script that works, but I can't be
-- responsible if it wreaks havoc on your system. Make sure you have good backups!
--
-- Tested on OS X 10.10.5


set theFile to choose file with prompt "Please select your Journal.json file:" of type {"public.json"}
set jsonContents to read theFile as «class utf8»

tell application "JSON Helper"
	set json to read JSON from jsonContents
end tell

set entries to entries of json
set defaultTimeZone to do shell script "ls -al /etc/localtime | sed 's/.*zoneinfo\\///'"

repeat with entry in entries
	set theUUID to the uuid of entry
	set theURL to "dayone2://view?entryId=" & theUUID
	set entryText to last item of text of entry
	set theName to first paragraph of entryText
	set theDateString to creationDate of entry
	try
		set theTimeZone to timeZone of entry
	on error
		set theTimeZone to defaultTimeZone
	end try
	set theCreationDate to timeInZone(theDateString, theTimeZone)
	set theText to ""
	
	repeat with theParagraph in paragraphs of entryText
		if theParagraph does not contain "![](dayone-moment://" then
			set theText to theText & theParagraph & "
"
		end if
	end repeat
	
	tell application "DEVONthink Pro"
		set theRecords to lookup records with URL theURL
		
		if length of theRecords is equal to 0 then
			set theRecord to create record with {type:markdown, name:theName, plain text:theText, URL:theURL, creation date:theCreationDate} in current group
		else if length of theRecords is equal to 1 then
			set theRecord to first item of theRecords
			
			if plain text of theRecord is not equal to theText then
				set plain text of theRecord to theText
			end if
			
			if name of theRecord is not equal to theName then
				set name of theRecord to theName
			end if
			
			if creation date of theRecord is not equal to theCreationDate then
				set creation date of theRecord to theCreationDate
			end if
			
			if modification date of theRecord is not equal to theCreationDate then
				set modification date of theRecord to theCreationDate
			end if
		else
			display alert "Error: found multiple records with URL " & theURL
			error "Found multiple records with URL " & theURL
		end if
		
		set locking of theRecord to true
	end tell
end repeat

on replaceText(find, replace, subject)
	set prevTIDs to text item delimiters of AppleScript
	set text item delimiters of AppleScript to find
	set subject to text items of subject
	
	set text item delimiters of AppleScript to replace
	set subject to subject as text
	set text item delimiters of AppleScript to prevTIDs
	
	return subject
end replaceText

on timeInZone(dateString, timeZone)
	set theDate to convertDate(dateString)
	return TZtoTZ(theDate, "GMT", timeZone)
end timeInZone

on convertDate(textDate)
	set textDate to replaceText("T", " ", textDate)
	set textDate to replaceText("Z", "", textDate)
	set resultDate to the current date
	
	set the year of resultDate to (text 1 thru 4 of textDate)
	set the month of resultDate to (text 6 thru 7 of textDate)
	set the day of resultDate to (text 9 thru 10 of textDate)
	set the time of resultDate to 0
	
	if (length of textDate) > 10 then
		set the hours of resultDate to (text 12 thru 13 of textDate)
		set the minutes of resultDate to (text 15 thru 16 of textDate)
		
		if (length of textDate) > 16 then
			set the seconds of resultDate to (text 18 thru 19 of textDate)
		end if
	end if
	
	return resultDate
end convertDate

on TZtoTZ(TZ1date, TZ1, TZ2)
	return (do shell script ("eraTime=$(TZ=" & TZ1 & " date -jf '%Y-%m-%dT%H:%M:%S' '" & (TZ1date as «class isot» as string) & "' '+%s') ; TZ=" & TZ2 & " date -r \"$eraTime\" '+%Y-%m-%dT%H:%M:%S'") as «class isot») as date
end TZtoTZ

Version 2 imports the photo files

It uses CSS to scale the images to 50% (but doesn’t modify the imported image file in any way). I am holding out hope that DTTG will get x-devonthink-item image linking like desktop has.

-- Import Day One journal entries v2
-- Created by Pat Maddox
--
-- This script is provided AS IS!
--
-- Export your Day One journal to JSON, then run this script
--
-- Requires: JSON Helper for AppleScript
-- https://itunes.apple.com/us/app/json-helper-for-applescript/id453114608?mt=12
--
-- Use at your own risk. I've done my best to create a script that works, but I can't be
-- responsible if it wreaks havoc on your system. Make sure you have good backups!
--
-- Tested on OS X 10.10.5


set theFile to choose file with prompt "Please select your Journal.json file:" of type {"public.json"}
set filePath to POSIX path of theFile
set journalPath to do shell script "dirname '" & filePath & "'"
set jsonContents to read theFile as «class utf8»

tell application "JSON Helper"
	set json to read JSON from jsonContents
end tell

set entries to entries of json
set defaultTimeZone to do shell script "ls -al /etc/localtime | sed 's/.*zoneinfo\\///'"

repeat with entry in entries
	
	set theUUID to the uuid of entry
	set theURL to "dayone2://view?entryId=" & theUUID
	set entryText to last item of text of entry
	set theName to first paragraph of entryText
	set theDateString to creationDate of entry
	try
		set theTimeZone to timeZone of entry
	on error
		set theTimeZone to defaultTimeZone
	end try
	set theCreationDate to timeInZone(theDateString, theTimeZone)
	set theText to ""
	
	try
		set thePhotos to photos of entry
		repeat with thePhoto in thePhotos
			set photoFileName to md5 of thePhoto & "." & type of thePhoto
			set photoFullPath to journalPath & "/photos/" & photoFileName
			set photoURL to "dayone-image-hack://" & md5 of thePhoto
			tell application "DEVONthink Pro"
				set thePhotoRecords to lookup records with URL photoURL
				if length of thePhotoRecords is equal to 0 then
					set thePhotoRecord to import photoFullPath to current group
					set URL of thePhotoRecord to photoURL
				else if length of thePhotoRecords is equal to 1 then
					set thePhotoRecord to item 1 of thePhotoRecords
				else
					display alert "Error: found multiple photos with URL " & photoURL
					error "Found multiple photos with URL " & photoURL
				end if
				
				set locking of thePhotoRecord to true
				set dayOneURL to "![](dayone-moment://" & identifier of thePhoto & ")"
				set photoRecordURL to "<img src=\"x-devonthink-item://" & uuid of thePhotoRecord & "\" style=\"width: 50%\">"
				set entryText to replaceText(dayOneURL, photoRecordURL, entryText) of me
			end tell
		end repeat
	end try
	
	set theText to entryText
	
	
	tell application "DEVONthink Pro"
		set theRecords to lookup records with URL theURL
		
		if length of theRecords is equal to 0 then
			set theRecord to create record with {type:markdown, name:theName, plain text:theText, URL:theURL, creation date:theCreationDate} in current group
		else if length of theRecords is equal to 1 then
			set theRecord to first item of theRecords
			
			if plain text of theRecord is not equal to theText then
				set plain text of theRecord to theText
			end if
			
			if name of theRecord is not equal to theName then
				set name of theRecord to theName
			end if
			
			if creation date of theRecord is not equal to theCreationDate then
				set creation date of theRecord to theCreationDate
			end if
			
			if modification date of theRecord is not equal to theCreationDate then
				set modification date of theRecord to theCreationDate
			end if
		else
			display alert "Error: found multiple records with URL " & theURL
			error "Found multiple records with URL " & theURL
		end if
		
		set locking of theRecord to true
	end tell
end repeat

on replaceText(find, replace, subject)
	set prevTIDs to text item delimiters of AppleScript
	set text item delimiters of AppleScript to find
	set subject to text items of subject
	
	set text item delimiters of AppleScript to replace
	set subject to subject as text
	set text item delimiters of AppleScript to prevTIDs
	
	return subject
end replaceText

on timeInZone(dateString, timeZone)
	set theDate to convertDate(dateString)
	return TZtoTZ(theDate, "GMT", timeZone)
end timeInZone

on convertDate(textDate)
	set textDate to replaceText("T", " ", textDate)
	set textDate to replaceText("Z", "", textDate)
	set resultDate to the current date
	
	set the year of resultDate to (text 1 thru 4 of textDate)
	set the month of resultDate to (text 6 thru 7 of textDate)
	set the day of resultDate to (text 9 thru 10 of textDate)
	set the time of resultDate to 0
	
	if (length of textDate) > 10 then
		set the hours of resultDate to (text 12 thru 13 of textDate)
		set the minutes of resultDate to (text 15 thru 16 of textDate)
		
		if (length of textDate) > 16 then
			set the seconds of resultDate to (text 18 thru 19 of textDate)
		end if
	end if
	
	return resultDate
end convertDate

on TZtoTZ(TZ1date, TZ1, TZ2)
	return (do shell script ("eraTime=$(TZ=" & TZ1 & " date -jf '%Y-%m-%dT%H:%M:%S' '" & (TZ1date as «class isot» as string) & "' '+%s') ; TZ=" & TZ2 & " date -r \"$eraTime\" '+%Y-%m-%dT%H:%M:%S'") as «class isot») as date
end TZtoTZ

Thank you for this, Pat. This is not only useful for Day One 2 users, but is a great example for those who want to learn how to import JSON data in their DEVONthink or other scripts.

Yeah, the JSON helper makes it really easy.

Many thanks for this. A great way of having entries Day One 2 entries searchable as individual items.

The script mostly works for me, but there are a significant number of entries that fail to be imported correctly. In some cases it just includes the UUID as both title and text, and in others it just has the date in special format (e.g. 2016-01-14T23:26:42Z) as the title, with the text being blank. The URL is correct, though, in all the failed cases.

I can’t find anything special in the Day One 2 entries that would account for the failure.

  1. I have similar errors. When I open my journal.json in Oxygen to view the native JSON I’m warned:

I haven’t investigated further but the script might be failing to parse correctly due to special characters. I also notice that in cases where I’m using tables, bullets, and other non-vanilla features of markdown that the import failed to parse the text. In some cases the JSON “text” property contains un-escaped new-line coding: “\n” (un-escaped) instead of “/\n” escaped. This might be causing some AppleScript mistakes when the text is parsed.

When I look in Script Debugger at what the script is reading in from the JSON file in the case a failed output (created a record using the entry date as “name” and there was no “text” output to DEVONthink) , it is correctly capturing the contents of the JSON “text” property but did not write it to the record that it created in the database for some reason.

  1. The entries with names like the one below are entries that have only images, and I’d suggest that Version 1 optionally ignore these, or, 2nd option, put them into a subfolder “Image-only entries”.

![](dayone-moment://D6FC384ECE134B689257001ACA33819B)

  1. The script sets the locked state of all records it creates in DEVONthink to “true” – I see in the OP why that was done, but it should be an option, perhaps, not a requirement.

  2. A useful future enhancement might be to read-in the tags from the JSON and apply them to entries

This looks like a fantastic script, but I am unable to get it to work. I used the second option that imports photos and installed the script. When I first ran it, I did not have JSON Helper installed so it informed me of that. Since installing JSON Helper, nothing happens when I run the script. I never get a query about the location of my JSON file.

Any ideas on what I am missing?

Hrm I am not sure. I would run it from the AppleScript editor to see if there are any errors.

Thanks Padillac. I tried it in Script Editor and it worked, then saved it again and it works great. I must have missed a line of code somewhere.

Thanks for sharing the script. I had been importing the PDF but this is a nice alternative!

Thank you for putting this together, @padillac. I’m new to DTPO and looking to stick with DayOne but aggregate my journal entries into DTPO.

When I try to save the Apple Script, I get a syntax error “Expected end of line but found plural class name.” for the line "set theRecords to lookup records with URL theURL"

I’m wondering if the Syntax has changed over the past couple of years and if you happen to have an updated version of the script. As I have no experience with Apple Script, my efforts to troubleshoot the error haven’t yielded results.

Thank you again!!

Hey there, I stopped using Day One so I’m afraid I can’t be of much help.

That said, I do have one guess: are you using DevonThink 3?

I still haven’t updated to it. It’s possible that some of the scripting commands changed between 2 and 3, which is why it fails.

I did just copy and paste the scripts into Script Editor, and when I compiled the script it asked me to choose an application. I selected DTPO 2.0 and it compiled fine.

So, if you’re using DT 2 then we can try to figure out if it’s a problem with pasting into Script Editor somehow. If you’re using DT 3 then I’d suggest you start looking through the script dictionary, or ask someone else who still uses this script. I no longer use Day One, and haven’t made the switch to DT 3 yet.

Are you using anything to replace Day One? Curious.

BTW DT3 is awesome, have been using it since it came out

I sort of just slowly stopped using Day One, and kept taking notes in my messy “system” of email / text files / OmniFocus / Tinderbox / DEVONthink.

That said, a few weeks ago I started using Agenda and really, really like it. It seems to meet in that sweet spot between DevonThink and Day One that I’d been looking for – a way to take notes that aren’t assigned to dates (what I use DevonThink for) as well as notes that are assigned to dates (Day One). But killer features, for me, are assigning notes directly to calendar events – so I can prepare for and take notes on meetings I have – as well as assigning notes to future dates. Day One can actually set entry dates to the future, it’s just something I hadn’t thought of until using Agenda.

Also, Agenda uses iCloud or Dropbox to sync, and it’s super fast.

I find that I can do project planning in Agenda better than I was able to using the other tools.

Yeah, I just haven’t gotten around to it. Apparently they got rid of three-pane-view – which is one of the reasons I got DT in the first place, and is basically the only view I’ve used for 9+ years. So that’s disappointing. But also it seems they’ve added “see also” across databases, which is something I’ve wanted for many years!

I’ll be open-minded about a different view, and of course will check out DT3 eventually.

AH nice, I heard of Agenda but haven’t really research its features.
I should give it a try.

Thanks for letting me know :slight_smile:

Oh yes definitely you will love the new DT3 many new features and overall awesomeness.

Thanks for the reply!

I am on DPTO 3, which may be the cause of the error. As it’s not mission-critical, I’ll probably end up using the “export as PDF” function for the time being or try Agenda (I downloaded it a while ago but haven’t fully put it to use yet).