Last active
October 12, 2021 13:31
-
-
Save shunf4/567fc3ea49389ae876d8d0090c9542f8 to your computer and use it in GitHub Desktop.
TODO.TXT to VTODO ICS (iCalendar) file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| // CB's Todo.txt to ics script | |
| // from https://chrisbeckstrom.com/2018/06/07/how-to-generate-a-calendar-from-your-todo-txt-file/ | |
| // shunf4 modified | |
| // goes through todo.txt and generates a calendar | |
| // 2018-06-06 | |
| // | |
| // HOW TO USE THIS | |
| // this script parses a file in the todo.txt format and generates an ics file | |
| // the ics file will have a series of VTODO | |
| // you can load this calendar into email or calendar application that supports | |
| // to-do lists (tasks) (like Fastmail's calendar) or other systems | |
| // | |
| // WHY? | |
| // I love todo.txt but sometimes it's tough to get a picture of what I really | |
| // need to do. Sticking my dated tasks on a calendar really helps me! | |
| // inspiration from here: | |
| // https://gist.github.com/jakebellacera/635416 | |
| // hide all errors, we don't want them mucking up our ics output | |
| error_reporting(0); | |
| ini_set('display_errors', 0); | |
| if ($_SERVER['REQUEST_METHOD'] !== "GET") { | |
| http_response_code(405); | |
| die("Not Allowed"); | |
| } | |
| // where does the todo.txt file live? | |
| $todopath = "/path/to/TODO.TXT"; | |
| // timezone of date strings in $todopath | |
| $todoTimezone = new DateTimeZone('+0800'); | |
| // load the todo file, give us an error if we can't open it | |
| $todofile = fopen($todopath, "r") or die("Unable to open todo file!"); | |
| // calendar file top stuff | |
| $ical = "BEGIN:VCALENDAR | |
| VERSION:2.0 | |
| PRODID:-//hacksw/handcal//NONSGML v1.0//EN"; | |
| // iterate through each line of the file | |
| while(!feof($todofile)) { | |
| $line = fgets($todofile); | |
| $trimmedLine = trim($line); | |
| if ($trimmedLine === "") { | |
| continue; | |
| } | |
| //print "line contains DUE! doing stuff <br>"; | |
| // explode the string into an array | |
| $array = explode(" ", $line); | |
| // iterate through each word and figure out what it is | |
| // i.e. a due date, a context, a project, threshold, hidden, etc. | |
| $done = false; | |
| $indexOfFirstDate = 0; | |
| $indexOfSecondDate = -10; | |
| $dateDone = ""; | |
| $dateDoneOrig = ""; | |
| $dateAdded = ""; | |
| $dateAddedOrig = ""; | |
| $dateDue = ""; | |
| $categories = ""; | |
| $priority = ""; | |
| $priorityOrig = ""; | |
| $url = ""; | |
| $project = ""; | |
| $processingAdvancedWords = true; | |
| foreach ($array as $i=>&$word){ | |
| $word = trim($word, "\r\n"); | |
| $word = str_replace("\r", "", $word); | |
| $word = str_replace("\n", " ", $word); | |
| $tryDate = DateTime::createFromFormat('!Y-m-d', $word, $todoTimezone); | |
| $isCurrentWordAdvancedWord = false; | |
| if ($tryDate !== FALSE) { | |
| $tryDate->setTimezone(new DateTimeZone("UTC")); | |
| //echo "---this word is a date! <br>"; | |
| if ($i === $indexOfFirstDate) { | |
| $dateAdded = $tryDate->format("Ymd\THis\Z"); | |
| $dateAddedOrig = $word; | |
| $indexOfSecondDate = $indexOfFirstDate + 1; | |
| $isCurrentWordAdvancedWord = true; | |
| } elseif ($i === $indexOfSecondDate) { | |
| $dateDone = $dateAdded; | |
| $dateDoneOrig = $dateAddedOrig; | |
| $dateAdded = $tryDate->format("Ymd\THis\Z"); | |
| $dateAddedOrig = $word; | |
| $isCurrentWordAdvancedWord = true; | |
| } | |
| } | |
| $trimmedWord = trim($word); | |
| if ($trimmedWord === "x" && $i === 0) { | |
| $done = true; | |
| $indexOfFirstDate += 1; | |
| $indexOfSecondDate += 1; | |
| $isCurrentWordAdvancedWord = true; | |
| } | |
| if ($processingAdvancedWords && strlen($trimmedWord) === 3 && substr($trimmedWord, 0, 1) === "(" && substr($trimmedWord, 2, 1) === ")" && substr($trimmedWord, 1, 1) >= "A" && substr($trimmedWord, 1, 1) <= "Z") { | |
| $priorityOrig = $trimmedWord; | |
| $priority = strval(ord(substr($trimmedWord, 1, 1)) - ord("A") + 1); | |
| $indexOfFirstDate += 1; | |
| $indexOfSecondDate += 1; | |
| $isCurrentWordAdvancedWord = true; | |
| } | |
| //print "value: $value <br>"; | |
| // IF WORD CONTAINS "due:" | |
| if (strpos($word, "due:") === 0) { | |
| $due = substr($word, 4); | |
| $due = trim($due); | |
| $dateDue = DateTime::createFromFormat('!Y-m-d', $due, $todoTimezone)->setTimezone(new DateTimeZone("UTC"))->format("Ymd\THis\Z"); | |
| } | |
| // IF WORD CONTAINS "@" | |
| if (strpos($word, "@") === 0) { | |
| //echo "--- this word has a context! <br>"; | |
| $context = str_replace("\n","",$word); | |
| } | |
| // IF WORD CONTAINS "+" | |
| if (strpos($word, "+") === 0) { | |
| //echo "--- this word has a project! <br>"; | |
| $project = $word; | |
| $projectName = substr($project, 1); | |
| $categories .= "CATEGORIES:$projectName\n"; | |
| } | |
| // IF WORD CONTAINS "t:" | |
| if (strpos($word, "t:") === 0) { | |
| //echo "--- this word contains t: <br>"; | |
| $threshold = $word; | |
| } | |
| // IF WORD CONTAINS "h:" | |
| if (strpos($word, "h:") === 0) { | |
| //echo "--- this word contains h: <br>"; | |
| $hide = $word; | |
| } | |
| // IS THIS A URL? | |
| if (strpos($word, "http://") === 0 || strpos($word, "https://") === 0) { | |
| //echo "--- this word is a URL!<br>"; | |
| $url = $word; | |
| } | |
| if ($isCurrentWordAdvancedWord === false) { | |
| $processingAdvancedWords = false; | |
| } | |
| } | |
| // put together a summary | |
| // summary: the name of the calendar event | |
| // description: more info when you click on the calendar event | |
| // BUILD THE SUMMARY | |
| // +projectname something something | |
| $summary = $line; | |
| if (strpos($summary, "x ") === 0) { | |
| $summary = substr($summary, 2); | |
| } | |
| // get rid of due date | |
| $summary = (str_replace("due:".$due,"",$summary)); | |
| // get rid of the added&done date | |
| $summary = (str_replace($dateAddedOrig,"",$summary)); | |
| $summary = (str_replace($dateDoneOrig,"",$summary)); | |
| $summary = (str_replace($priorityOrig,"",$summary)); | |
| // get rid of the context | |
| // $summary = (str_replace($context,"",$summary)); | |
| // get rid of the threshold | |
| $summary = (str_replace($threshold,"",$summary)); | |
| // get rid of the URL | |
| $summary = (str_replace($url,"",$summary)); | |
| // remove project.... | |
| $summary = (str_replace($project,"",$summary)); | |
| //... so we can make it first | |
| if ($project !== "") { | |
| $summary = $project . " " . $summary; | |
| } | |
| // remove newlines | |
| $summary = (str_replace("\n","",$summary)); | |
| // remove double spaces | |
| $summary = (str_replace(" "," ",$summary)); | |
| $summary = implode(" ", array_filter(explode(" ", $summary), function($x) { return $x !== ""; })); | |
| $summaryBase64 = base64_encode($summary); | |
| //print "summary: $summary <br>"; | |
| // create the ICS entry | |
| $ical = $ical . " | |
| BEGIN:VTODO | |
| SUMMARY:$summary | |
| DESCRIPTION:$trimmedLine | |
| UID:$summaryBase64 | |
| "; | |
| $ical .= $categories; | |
| if ($url !== "") { | |
| $ical = $ical . "URL:$url\n"; | |
| } | |
| if ($dateDone !== "") { | |
| $ical = $ical . "COMPLETED:$dateDone\n"; | |
| $ical = $ical . "DTSTAMP:$dateDone\n"; | |
| $ical = $ical . "LAST-MODIFIED:$dateDone\n"; | |
| } | |
| if ($dateAdded !== "") { | |
| $ical = $ical . "CREATED:$dateAdded\n"; | |
| $ical = $ical . "DTSTART:$dateAdded\n"; | |
| if ($dateDone === "") { | |
| $ical = $ical . "DTSTAMP:$dateAdded\n"; | |
| $ical = $ical . "LAST-MODIFIED:$dateAdded\n"; | |
| } | |
| } | |
| if ($dateDue !== "") { | |
| $ical = $ical . "DUE:$dateDue\n"; | |
| } | |
| if ($done) { | |
| $ical = $ical . "STATUS:COMPLETED\n"; | |
| } | |
| if ($priority !== "") { | |
| $ical = $ical . "PRIORITY:$priority\n"; | |
| } | |
| $ical .= "END:VTODO\n"; | |
| } | |
| fclose($todofile); // close the todofile | |
| $ical = $ical . " | |
| END:VCALENDAR"; | |
| // comment these headers out to load the text in a web browser | |
| // uncomment to generate an ics file when loading this script | |
| header('Content-Type: text/calendar; charset=utf-8'); | |
| header('Content-Disposition: attachment; filename=invite.ics'); | |
| echo $ical; | |
| exit; | |
| ?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment