This is a python script to extract plain text from a Day One journal. I use Day One both on my iPhone and my MacBook, and I think it's a great program to write down your daily thoughts and ideas. But I couldn't stand the fact, that everything was kept in an opaque bundle.

So I looked at the bundle and realized that the entries are kept in property lists, which are pretty easy to parse with python.

You may have to make the script executable (chmod a+x or
use python /Path/to/Journal.dayone

Download it here (4 KB)


$ ./ -h
usage: extractDOJ [-h] [-d DATE_FORMAT] [-c] [-r]
              /Path/to/Journal.dayone [outfile]

Extracts plain text fromy our Day One journal (

positional arguments:
                        Your journal file (bundle, actually)
  outfile               save output to file OUTFILE

optional arguments:
  -h, --help            show this help message and exit
  -d DATE_FORMAT        format of date (google 'strftime python')
  -c, --csv             output CSV (tab separated values)
  -r                    reversed order


./ /Path/to/Journal.dayone Journal.txt

Extracts entries as plain text into file Journal.txt

./ -c /Path/to/Journal.dayone Journal.txt

Extracts entries as tab separated values into file Journal.txt. Useful to import your diary into an Excel file.


# encoding: utf-8

Created by Niklas Hennigs on 2011-05-08.

import sys
import os
import locale
import time
import argparse
import plistlib

from operator import itemgetter

entries = []

locale.setlocale(locale.LC_ALL, "")

def extractPlainText(arg, path, file_list):
    global entries

    for file_name in file_list:
        # We need the full path to locate the file
        file_path = os.path.join(path, file_name)

        pl = plistlib.readPlist(file_path)

        EntryDate = pl["Creation Date"]
        EntryText = pl["Entry Text"]

        entries.append([EntryDate, EntryText])

def main():
    global entries

    parser = argparse.ArgumentParser(description=
                                     'Extracts plain text from'
                                     'your Day One journal'

    parser.add_argument('journal_bundle', metavar='/Path/to/Journal.dayone',
                        help='Your journal file (bundle, actually)')
    parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'),
                        default=sys.stdout, help='save output to file OUTFILE')
    parser.add_argument('-d', dest='date_format', default='%A, %x, %H:%M',
                        help='format of date (google \'strftime python\')')
    parser.add_argument('-c', '--csv', action="store_true", dest='csv',
                        help='output CSV (tab separated values)')
    parser.add_argument('-r', action="store_true", dest='reversed',
                        help='reversed order')

    args = parser.parse_args()

    dateformat = args.date_format

    # voodoo because of Python's unicode capabilities
    # see
    # Uncomment the following line an add 'import codecs' at the beginning of
    # this file, if you have trouble piping the output to another program:
    # sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)
    # Another workaround:
    # Put following lines into site-packages/
    # see
    #       import os, sys
    #       if 'UTF-8' in os.environ['LANG']:
    #           sys.setdefaultencoding('utf-8')

    if not os.path.exists(args.journal_bundle):
        sys.stderr.write("ERROR: Journal %r was not"
                         "found!\n" % (args.journal_bundle))

    entrydir = os.path.join(args.journal_bundle, "entries")

    os.path.walk(entrydir, extractPlainText, None)

    for entry in sorted(entries, key=itemgetter(0), reverse=(args.reversed)):
        if args.csv == True:
            args.outfile.write("\"" + entry[0].strftime(dateformat) +
                                "\"\t\"" + entry[1] + "\"\n")
            args.outfile.write(entry[0].strftime(dateformat) + "\n" +
                                entry[1] + "\n")

if __name__ == "__main__":