Export Day One (macOS) Journals to Emacs org-mode Diary Entries
I noticed that I still had the journaling app Day One (macOS) on my computer. I haven’t touched it in ages. So I figured it’d be time to export to plain text and then shove all the goodness from 2004 and onward into my Emacs org-mode diary.org
.
Turns out there’s no Day One to org-mode exporter.
Yet!
The best StackOverflow topic results in “just regex \tDate:\t
and make an org outline”. It’s a good first step. But might just as well add some more cleverness to the conversion to get the year and month-of-year outline items for free, too!
Since my Emacs Lisp is so bad, it’s a Ruby script, of course.
So the result converts a Day One plain text export (usually called Journal.txt
) into org-mode journal format. It features
- Markdown image conversion from
![](thepath.jpg)
to org inline image links,[[thepath.jpg]]
. - Extraction of images from the front of an entry; moves it right after the property drawer.
- Creation of nested year/month/day date sections (see example below).
The output:
* 2020
** 2020-03 March
*** 2020-03-14 Saturday
**** Here's a diary entry imported from Day One.
:PROPERTIES:
:CREATED: [2020-03-14 Sat 14:33]
:END_PROPERTIES:
**** And another one from the same day.
:PROPERTIES:
:CREATED: [2020-03-14 Sat 19:12]
:END_PROPERTIES:
** 2020-06 June
*** 2020-06-22 Monday
**** Year, month, and day sections are created automatically.
:PROPERTIES:
:CREATED: [2020-06-22 Mon 08:06]
:END_PROPERTIES:
The conversion is pretty simple, but I added a bit of edge-case handling. A lot of my entries were a prominent image at the top plus text below, and with Day One putting all images inline in the plain text export, this meant that the first line started with a Markdown image reference followed by some text. Not pretty. The converter splits these.
Source Code
See the public Gist for a comfortable download.
#!/usr/bin/env ruby
require "date"
input, output, *rest = ARGV
if input.nil?
STDERR.puts "Usage: #{__FILE__} DAYONE_INPUT_PATH ORG_OUTPUT_PATH"
STDERR.puts "Missing input file path"
exit 1
elsif output.nil?
STDERR.puts "Usage: #{__FILE__} DAYONE_INPUT_PATH ORG_OUTPUT_PATH"
STDERR.puts "Missing output file path"
exit 1
end
File.open(output, "w") do |out|
# Cached values to make sub-headings
year = nil
month = nil
day = nil
props = {}
File.readlines(input).each do |line|
if /\A\t(?<key>\w+):\t(?<value>.*+)$/ =~ line
# Collect metadata in `props` dictionary
case key
when "Date"
date = DateTime.parse(value)
props["Created"] = date.strftime("[%Y-%m-%d %a %H:%M]")
# Convert date lines to new entries in org
# Output: "* 2020"
if year != date.year
out.puts "* #{date.year}"
year = date.year
month = nil
end
# Output: "** 2020-03 March"
if month != date.month
out.puts "** #{date.strftime("%Y-%m %B")}"
month = date.month
end
# Output: "*** 2020-03-12 Thursday"
this_day = date.strftime("%Y-%m-%d %A")
if day != this_day
out.puts "*** #{this_day}"
end
else
props[key] = value
end
elsif !props.empty?
# Produce entry title and metadata
if line.strip.empty?
# Skip empty line separator after metadata
else
# Add entry heading, handling leading entry images
cached_image = nil
if /\A!\[]\((?<path>.+)\)(?<rest>.*)$/ =~ line
cached_image = "[[./#{path}]]"
out.puts "**** #{rest}"
else
out.puts "**** #{line}"
end
# Append property drawer
out.puts ":PROPERTIES:"
out.puts(props.map { |key, value| ":" + key.upcase.to_s + ": " + value.to_s }
.join("\n"))
out.puts ":END_PROPERTIES:"
props = {}
if !cached_image.nil?
out.puts ""
out.puts cached_image
end
end
else
line = line.gsub(/!\[\]\((.+)\)/) { "[[./#{$1}]]" }
out.puts line
end
end
end