The below script, when run in a directory of Day One JSON exports. The result is an opinionated output of Obsidian-compatible Markdown files. The big difference between this script and similar implementations (like dayone-to-obsidian) is that only one Markdown file is generated per day, with Day one entries arranged chronologically within it. This approach is much more compatible with the current Obsidian daily note plugin ecosystem, (almost) all of which assume only a single note per day.

This is also a pretty simple implementation. So while it’s output is opinionated, it should also be pretty easy to adapt this script to different use cases.

Note

Day One has a tendency to add extra spaces, or add/remove additional line breaks, in a somewhat random fashion. This script tries to correct for some of the more common issues, but it doesn’t correct for all of them. It will be necessary to do a hand-editing pass over the generated Markdown files in order to catch any remaining formatting issues.

The only way around this is probably to directly parse the JSON representation of each journal entry, but that would make for a much more complicated script, and it’s not clear that the extra effort it would take to write such a parser would be less than the effort required just to manually review the output files.

#!/usr/bin/env python3
 
import os
import json
 
from datetime import datetime
from zoneinfo import ZoneInfo
 
# Read in all JSON files in the directory
#
rawDayOneData = {}
 
for file in os.listdir("."):
	if file.endswith(".json"):
		with open(file) as jsonData:
			rawDayOneData[file.replace(".json", "")] = json.load(jsonData)
 
# Create master document list
#
journalData = {}
 
for journalName, rawJournalData in rawDayOneData.items():
	for entry in rawJournalData["entries"]:
		entryDateTimeObject = datetime.fromisoformat(entry["creationDate"])
		entryDate = entryDateTimeObject.astimezone(ZoneInfo(entry["timeZone"])).strftime("%Y-%m-%d")
		entryTime = entryDateTimeObject.astimezone(ZoneInfo(entry["timeZone"])).strftime("%H:%M")
		entryDateTime = entryDateTimeObject.astimezone(ZoneInfo(entry["timeZone"])).strftime("%Y-%m-%dT%H:%M:%S")
 
		entryUuid = entry["uuid"].lower()
		entryIndex = entryDateTime + "|" + entryUuid
		
		entryTags = "#" + journalName.replace(" ", "")
		if "tags" in entry:
			for tag in entry["tags"]:
				entryTags = entryTags + " #" + tag.replace(" ", "")
 
		if ("#Art" in entryTags) and ("#Photography" in entryTags):
			entryHeader = "## " + entryTime + " | Photography"
		else:
			entryHeader = "## " + entryTime + " | " + journalName
 
		entryLocation = ""
		if "location" in entry:
			entryLocation = "[" + entry["location"]["placeName"] + "](geo:" + str(round(entry["location"]["latitude"], 4)) + "," + str(round(entry["location"]["longitude"], 4)) + ")"
		
		entryWeather = ""
		if "weather" in entry:
			entryWeather = str(int(round(entry["weather"]["temperatureCelsius"], 0))) + " °C"
			if "conditionsDescription" in entry["weather"]:
				entryWeather = entryWeather + "" + entry["weather"]["conditionsDescription"]
			elif "weatherCode" in entry["weather"]:
				entryWeather = entryWeather + "" + entry["weather"]["weatherCode"]
 
		entryMedia = {}
		if "audios" in entry:
			for audioFile in entry["audios"]:
				entryMedia["![](dayone-moment:/audio/" + audioFile["identifier"] + ")"] = "![[" + audioFile["md5"] + audioFile["format"] + "]]"
		if "pdfs" in entry:
			for pdfFile in entry["pdfs"]:
				entryMedia["![](dayone-moment:/pdfAttachment/" + pdfFile["identifier"] + ")"] = "![[" + pdfFile["md5"] + ".pdf" + "]]"
		if "photos" in entry:
			for imageFile in entry["photos"]:
				entryMedia["![](dayone-moment://" + imageFile["identifier"] + ")"] = "![[" + imageFile["md5"] + "." + imageFile["type"] + "]]"
		if "videos" in entry:
			for videoFile in entry["videos"]:
				entryMedia["![](dayone-moment:/video/" + videoFile["identifier"] + ")"] = "![[" + videoFile["md5"] + "." + videoFile["type"] + "]]"
		
		entryText = entry["text"]
 
		for oldMediaLink, newMediaLink in entryMedia.items():
			entryText = entryText.replace(oldMediaLink, newMediaLink)
 
		entryText = entryText.replace("\n\n\n", "\n\n")
		entryText = entryText.replace(" ![[", "\n\n![[")
		entryText = entryText.replace("]]\n\n![[", "]]\n![[")
		entryText = entryText.replace(".\n![[", ".\n\n![[")
		entryText = entryText.replace("!\n![[", "!\n\n![[")
		entryText = entryText.replace("?\n![[", "?\n\n![[")
		entryText = entryText.replace(")\n![[", ")\n\n![[")
		entryText = entryText.replace("\\&", "&")
		entryText = entryText.replace("\\~", "~")
		entryText = entryText.replace("\\-", "-")
		entryText = entryText.replace("\\_", "_")
		entryText = entryText.replace("\\+", "+")
		entryText = entryText.replace("\\.", ".")
		entryText = entryText.replace("\\!", "!")
		entryText = entryText.replace("\\(", "(")
		entryText = entryText.replace("\\)", ")")
		entryText = entryText.replace("\\{", "{")
		entryText = entryText.replace("\\}", "}")
		entryText = entryText.replace("\\[\\]", "[]")
		entryText = entryText.replace("\\\\", "\\")
		entryText = entryText.replace("[ *", "[*")
		entryText = entryText.replace("* ]", "*]")
		entryText = entryText.replace("#", "")
		entryText = entryText.replace("$", "")
		entryText = entryText.replace(" --- ", "")
		entryText = entryText.replace(" -- ", "")
		entryText = entryText.replace("...", "")
		entryText = entryText.strip()
 
		if entryDate not in journalData:
			journalData[entryDate] = {}
 
		journalData[entryDate][entryIndex] = {}
 
		journalData[entryDate][entryIndex]["header"] = entryHeader
		journalData[entryDate][entryIndex]["uuid"] = entryUuid
		journalData[entryDate][entryIndex]["datetime"] = entryDateTime
		journalData[entryDate][entryIndex]["tags"] = entryTags
		if len(entryLocation) > 0:
			journalData[entryDate][entryIndex]["location"] = entryLocation
		if len(entryWeather) > 0:
			journalData[entryDate][entryIndex]["weather"] = entryWeather
		journalData[entryDate][entryIndex]["text"] = entryText
 
journalData = dict(sorted(journalData.items()))
 
for journalDate, journalEntries in journalData.items():
	journalData[journalDate] = dict(sorted(journalEntries.items()))
 
for journalDate, journalEntries in journalData.items():
	journalDateTime = datetime.strptime(journalDate, "%Y-%m-%d")
 
	journalDayNumber = journalDateTime.strftime("%-d")
 
	journalDaySuffix = "th"
	if (journalDayNumber == "1") or (journalDayNumber == "21") or (journalDayNumber == "31"):
		journalDaySuffix = "st"
	elif (journalDayNumber == "2") or (journalDayNumber == "22"):
		journalDaySuffix = "nd"
	elif (journalDayNumber == "3") or (journalDayNumber == "23"):
		journalDaySuffix = "rd"
 
	journalDayName = journalDateTime.strftime("%A")
	journalHumanDate = journalDateTime.strftime("%B") + " " + journalDayNumber + journalDaySuffix + " " + journalDateTime.strftime("%Y")
	journalAmericanDate = journalDateTime.strftime("%-m/%-d/%Y")
 
	journalFileName = journalDate + " " + journalDayName + ".md"
 
	with open (journalFileName, "w") as journalFile:
		print("---", file = journalFile)
		print("title: Journal for " + journalDayName + ", " + journalHumanDate, file = journalFile)
		print("date: " + journalDate, file = journalFile)
		print("tags:", file = journalFile)
		print("  - Journal", file = journalFile)
		print("aliases:", file = journalFile)
		print("  - Journal for " + journalDayName + ", " + journalHumanDate, file = journalFile)
		print("  - " + journalDayName + ", " + journalHumanDate, file = journalFile)
		print("  - " + journalHumanDate, file = journalFile)
		print("  - " + journalDate, file = journalFile)
		print("  - " + journalAmericanDate, file = journalFile)
		print("locations:", file = journalFile)
		print("---", file = journalFile)
 
		for entryId, entry in journalEntries.items():
 
			print(entry["header"], file = journalFile)
			print("[dayOneId:: " + entry["uuid"] + "]", file = journalFile)
			print("[datetime:: " + entry["datetime"] + "]", file = journalFile)
			print("[tags:: " + entry["tags"] + "]", file = journalFile)
			if "location" in entry:
				print("[location:: " + entry["location"] + "]", file = journalFile)
			if "weather" in entry:
				print("[weather:: " + entry["weather"] + "]", file = journalFile)
			print("", file = journalFile)
			print(entry["text"], file = journalFile)
			print("", file = journalFile)