From: Andrew Engelbrecht <andrew@fsf.org> Date: Thu, 17 Mar 2022 23:29:03 +0000 (-0400) Subject: Initial .csv -> .ics file generator for LibrePlanet X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=5b89310875edaf6fb884ee87fbe9d465a28eb22b;p=lp-csv-to-ics.git Initial .csv -> .ics file generator for LibrePlanet As input, the csv comes from a custom Drupal view. --- diff --git a/ICS.php b/ICS.php new file mode 100644 index 0000000..3d2473a --- /dev/null +++ b/ICS.php @@ -0,0 +1,157 @@ +<?php + +/** + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to <http://unlicense.org> + * + * ICS.php + * ============================================================================= + * Use this class to create an .ics file. + * + * + * Usage + * ----------------------------------------------------------------------------- + * Basic usage - generate ics file contents (see below for available properties): + * $ics = new ICS($props); + * $ics_file_contents = $ics->to_string(); + * + * Setting properties after instantiation + * $ics = new ICS(); + * $ics->set('summary', 'My awesome event'); + * + * You can also set multiple properties at the same time by using an array: + * $ics->set(array( + * 'dtstart' => 'now + 30 minutes', + * 'dtend' => 'now + 1 hour' + * )); + * + * Available properties + * ----------------------------------------------------------------------------- + * description + * String description of the event. + * dtend + * A date/time stamp designating the end of the event. You can use either a + * DateTime object or a PHP datetime format string (e.g. "now + 1 hour"). + * dtstart + * A date/time stamp designating the start of the event. You can use either a + * DateTime object or a PHP datetime format string (e.g. "now + 1 hour"). + * location + * String address or description of the location of the event. + * summary + * String short summary of the event - usually used as the title. + * url + * A url to attach to the the event. Make sure to add the protocol (http:// + * or https://). + */ + +class ICS { + const DT_FORMAT = 'Ymd\THis\Z'; + + protected $properties = array(); + private $available_properties = array( + 'description', + 'dtend', + 'dtstart', + 'location', + 'summary', + 'url' + ); + + public function __construct($props) { + $this->set($props); + } + + public function set($key, $val = false) { + if (is_array($key)) { + foreach ($key as $k => $v) { + $this->set($k, $v); + } + } else { + if (in_array($key, $this->available_properties)) { + $this->properties[$key] = $this->sanitize_val($val, $key); + } + } + } + + public function to_string() { + $rows = $this->build_props(); + return implode("\r\n", $rows); + } + + private function build_props() { + // Build ICS properties - add header + $ics_props = array( + 'BEGIN:VCALENDAR', + 'VERSION:2.0', + 'PRODID:-//hacksw/handcal//NONSGML v1.0//EN', + 'CALSCALE:GREGORIAN', + 'BEGIN:VEVENT' + ); + + // Build ICS properties - add header + $props = array(); + foreach($this->properties as $k => $v) { + $props[strtoupper($k . ($k === 'url' ? ';VALUE=URI' : ''))] = $v; + } + + // Set some default values + $props['DTSTAMP'] = $this->format_timestamp('now'); + $props['UID'] = uniqid(); + + // Append properties + foreach ($props as $k => $v) { + $ics_props[] = "$k:$v"; + } + + // Build ICS properties - add footer + $ics_props[] = 'END:VEVENT'; + $ics_props[] = 'END:VCALENDAR'; + + return $ics_props; + } + + private function sanitize_val($val, $key = false) { + switch($key) { + case 'dtend': + case 'dtstamp': + case 'dtstart': + $val = $this->format_timestamp($val); + break; + default: + $val = $this->escape_string($val); + } + + return $val; + } + + private function format_timestamp($timestamp) { + $dt = new DateTime($timestamp); + return $dt->format(self::DT_FORMAT); + } + + private function escape_string($str) { + return preg_replace('/([\,;])/','\\\$1', $str); + } +} diff --git a/generate-ics.php b/generate-ics.php new file mode 100755 index 0000000..375dbb9 --- /dev/null +++ b/generate-ics.php @@ -0,0 +1,59 @@ +#! /usr/bin/php +<?php + +// Copyright 2022 Andrew Engelbrecht +// GPL-3.0-or-later + +// expected format: +// +// "Saturday","10:00 - 10:15 EDT - Welcome address","Welcome Address by FSF","Jupiter","LibrePlanet special sessions","LibrePlanet 2022","","","http://libreplanet.org/2022/speakers/#5910" + +include 'ICS.php'; + +$csv = array_map('str_getcsv', explode("\n", stream_get_contents(STDIN))); + +foreach ($csv as $linenum => $line) { + + if (count($line) <= 1) { + continue; + } + + // get the date stamp + if ($line[0] == "Saturday") { + $date = "2022-03-19"; + } else if ($line[0] == "Sunday") { + $date = "2022-03-20"; + } else { + throw new Exception("Invalid date: " . $line[0]); + } + + // grab the time string + $timestamps = array(); + preg_match('/([0-9][0-9]:[0-9][0-9]) - ([0-9][0-9]:[0-9][0-9]) EDT.*/', $line[1], $timestamps); + + if (count($timestamps) < 3) { + throw new Exception("Invalid time range, title: " . $line[0]); + } + + // parse and adjust time zone by 4 hours (to UTC) + $start_time = intval(str_replace(':', '', $timestamps[1])) + 400; + $end_time = intval(str_replace(':', '', $timestamps[2])) + 400; + + if ($start_time >= 2400 || $end_time >= 2400) { + throw new Exception("Invalid times when hackishly converting to UTC: " . $start_time . " - " . $end_time); + } + + $ics = new ICS(array( + 'location' => $line[3], + 'description' => $line[2], + 'dtstart' => $date . "T" . $start_time . "Z", + 'dtend' => $date . "T" . $end_time . "Z", + 'summary' => $line[4], + 'url' => str_replace("http:", "https:", $line[8]), + )); + + echo $ics->to_string(); + + echo "\n\n"; +} +