Skip to content

Instantly share code, notes, and snippets.

@notconfusing
Forked from konnov/rtm2todoist.py
Last active March 11, 2023 20:10
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save notconfusing/68c84868098dcaa04e44522bebb77638 to your computer and use it in GitHub Desktop.
Save notconfusing/68c84868098dcaa04e44522bebb77638 to your computer and use it in GitHub Desktop.
Converting Remember-the-milk tasks (ICS) to a Todoist template (CSV)
#!/usr/bin/env python
# coding: utf-8
# whereas the original rtm2todoist.py[1] worked off the .ics export, this version uses the more modern .json export
# [1] https://gist.github.com/notconfusing/68c84868098dcaa04e44522bebb77638
import json
import pandas as pd
import datetime
# todo add commandline interface to make this a parameter. Use click?
# requires 'in' and 'out' paths in directior #todo automatically make the paths
f = open('in/rememberthemilk_export_2022-05-13T17 53 48.222Z.json', 'r')
d = json.load(f)
config = d['config']
lists = d['lists']
tasks = d['tasks']
notes = d['notes']
def insert_section_heading(section_name, df):
"""insert a row with "TYPE"="seciton" CONTENT="section_name" """
section_insert = pd.DataFrame({'TYPE': 'section', 'CONTENT': section_name}, index=[0])
first_section_index = df.list_name.eq(section_name).idxmax()
first_section_loc = df.index.get_loc(first_section_index)
head = df[:first_section_loc]
tail = df[first_section_loc:]
section_inserted_df = pd.concat(objs=[head, section_insert, tail])
print(f'Section {section_name}, first loc {first_section_loc}')
print(f'length before {len(df)}, length after {len(section_inserted_df)}')
return section_inserted_df
def insert_section_headings(df):
ordered_df = df.sort_values('list_name')
all_list_names = ordered_df['list_name'].unique()
for list_name in all_list_names:
ordered_df = insert_section_heading(list_name, ordered_df)
return ordered_df
uncompleted_tasks = [t for t in tasks if 'date_completed' not in t.keys()]
lists_id_name = {listobj['id']: listobj['name'] for listobj in lists}
df = pd.DataFrame.from_records(uncompleted_tasks)
df['list_name'] = df['list_id'].apply(lambda lid: lists_id_name[lid])
df = df.sort_values('series_id')
# https://todoist.com/help/articles/how-to-format-your-csv-file-so-you-can-import-it-into-todoist
# - "TYPE CONTENT PRIORITY INDENT AUTHOR RESPONSIBLE DATE DATE_LANG TIMEZONE"
# uncompleted-only tasks
df['TYPE'] = 'task' # - type = 'task', 'section', 'note'
df['CONTENT'] = df['name']
df['DESCRIPTION'] = df['url']
df['PRIORTITY'] = df['priority'].apply(lambda p: 2 if p == 'PN' else p) # not sure exactly what this PN means
df['INDENT'] = df['parent_id'].apply(lambda pid: 1 if pd.isna(pid) else 2) # todo understand more about series_id
df['AUTHOR'] = '' # todo get from config
df['RESPONSIBLE'] = '' # todo get from config
df['DATE'] = df['date_due'].apply(lambda dd: datetime.datetime.utcfromtimestamp(dd / 1000.0) if pd.notna(dd) else '')
df['DATE_LANG'] = 'en' # todo get from config
df['TIMEZONE'] = 'US/Pacific' # todo get from config
sectioned_df = insert_section_headings(df)
sectioned_df.to_csv('out/todoist_sections.csv')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment