diff --git a/bin/ical2org b/bin/ical2org deleted file mode 100755 index 30e53d6..0000000 --- a/bin/ical2org +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env perl -# by PerlStalker, http://perlstalker.vuser.org/blog/2014/06/04/importing-ical-into-org-mode/ -# with minor modifications by exot - -use warnings; -use strict; - -use Data::ICal; -use Data::Dumper; -use DateTime::Format::ICal; - -use Getopt::Long; -my $category = undef; -my $filetags = undef; - -GetOptions( - 'category|c=s' => \$category, - 'filetags|f=s' => \$filetags -); - -my $cal = Data::ICal->new(data => join '', ); - -#print Dumper $cal; -my %gprops = %{ $cal->properties }; - -print "#+TITLE: ical entries\n"; -print "#+AUTHOR: ".$gprops{'x-wr-calname'}[0]->decoded_value."\n" if defined $gprops{'x-wr-calname'}; -print "#+EMAIL: \n"; -print "#+DESCRIPTION: Converted using ical2org.pl\n"; -print "#+CATEGORY: $category\n" if defined($category); -print "#+FILETAGS: $filetags\n" if defined($filetags); -print "#+STARTUP: overview\n"; -print "\n"; - -#print "* COMMENT original iCal properties\n"; -#print Dumper \%gprops; -#print "Timezone: ", $gprops{'x-wr-timezone'}[0]->value, "\n"; - -#foreach my $prop (values %gprops) { -# foreach my $p (@{ $prop }) { -# print $p->key, ':', $p->value, "\n"; -# } -#} - -my $error_code = 0; - -foreach my $entry (@{ $cal->entries }) { - next if not $entry->isa('Data::ICal::Entry::Event'); - # print 'Entry: ', Dumper $entry; - - eval { handle_entry($entry) }; - if ($@) { - print STDERR $@; - $error_code = 1; - } -} - -exit $error_code; - -sub org_date_range { - my $start = shift; - my $end = shift; - - my $str = sprintf('<%04d-%02d-%02d %s %02d:%02d>', - $start->year, - $start->month, - $start->day, - $start->day_abbr, - $start->hour, - $start->minute - ); - $str .= '--'; - $str .= sprintf('<%04d-%02d-%02d %s %02d:%02d>', - $end->year, - $end->month, - $end->day, - $end->day_abbr, - $end->hour, - $end->minute - ); - - return $str; -} - -sub handle_entry { - my $entry = shift; - - my %props = %{ $entry->properties }; - - # skip entries with no start - next if not $props{dtstart}[0]; - - my $dtstart = DateTime::Format::ICal->parse_datetime($props{dtstart}[0]->value); - my ($duration, $dtend); - - if (not $props{dtend}[0]) { - $duration = DateTime::Format::ICal->parse_duration($props{duration}[0]->value); - $dtend = $dtstart->clone->add_duration($duration); - } else { - $dtend = DateTime::Format::ICal->parse_datetime($props{dtend}[0]->value); - $duration = $dtend->subtract_datetime($dtstart); - } - - if (defined $props{rrule}) { - #print " REPEATABLE\n"; - # Bad: There may be multiple rrules but I'm ignoring them - my $set = DateTime::Format::ICal->parse_recurrence(recurrence => $props{rrule}[0]->value, - dtstart => $dtstart, - dtend => DateTime->now->add(weeks => 1), - ); - - my $itr = $set->iterator; - while (my $dt = $itr->next) { - $dt->set_time_zone($props{dtstart}[0]->parameters->{'TZID'} || $gprops{'x-wr-timezone'}[0]->value); - print "* ".$props{summary}[0]->decoded_value."\n"; - my $end = $dt->clone->add_duration($duration); - print ' ', org_date_range($dt, $end), "\n"; - #print $dt, "\n"; - print " :PROPERTIES:\n"; - printf " :ID: %s\n", $props{uid}[0]->value; - - if (defined $props{location}) { - printf " :LOCATION: %s\n", $props{location}[0]->value; - } - - if (defined $props{status}) { - printf " :STATUS: %s\n", $props{status}[0]->value; - } - - print " :END:\n"; - - if ($props{description}) { - print "\n", $props{description}[0]->decoded_value, "\n"; - } - } - } - else { - - print "* ".$props{summary}[0]->decoded_value."\n"; - - # my $tz = $gprops{'x-wr-timezone'}[0]->value; - # $dtstart->set_time_zone($props{dtstart}[0]->parameters->{'TZID'} || $tz); - # $dtend->set_time_zone($props{dtend}[0]->parameters->{'TZID'} || $tz); - - print ' ', org_date_range($dtstart, $dtend), "\n"; - - print " :PROPERTIES:\n"; - printf " :ID: %s\n", $props{uid}[0]->value; - - if (defined $props{location}) { - printf " :LOCATION: %s\n", $props{location}[0]->value; - } - - if (defined $props{status}) { - printf " :STATUS: %s\n", $props{status}[0]->value; - } - - print " :END:\n"; - - if ($props{description}) { - print "\n", $props{description}[0]->decoded_value, "\n"; - } - - } - -# print Dumper \%props; -} diff --git a/bin/ical2org.awk b/bin/ical2org.awk deleted file mode 100755 index 76b9ff3..0000000 --- a/bin/ical2org.awk +++ /dev/null @@ -1,426 +0,0 @@ -# awk script for converting an iCal formatted file to a sequence of org-mode headings. -# this may not work in general but seems to work for day and timed events from Google's -# calendar, which is really all I need right now... -# -# usage: -# awk -f THISFILE < icalinputfile.ics > orgmodeentries.org --assign NAME=category -# -# where the category is used to define a CATEGORY for all entries in -# the file and also assign that label as a tag to each entry -# -# Note: change org meta information generated below for author and -# email entries! -# -# Known bugs: -# - not so much a bug as a possible assumption: date entries with no time -# specified are assumed to be independent of the time zone. -# -# Eric S Fraga -# 20100629 - initial version -# 20100708 - added end times to timed events -# - adjust times according to time zone information -# - fixed incorrect transfer for entries with ":" embedded within the text -# - added support for multi-line summary entries (which become headlines) -# 20100709 - incorporated time zone identification -# - fixed processing of continuation lines as Google seems to -# have changed, in the last day, the number of spaces at -# the start of the line for each continuation... -# - remove backslashes used to protect commas in iCal text entries -# no further revision log after this as the file was moved into a git -# repository... -# -# Last change: 2016.05.26 08:47:12 -#---------------------------------------------------------------------------------- - -# a function to take the iCal formatted date+time, convert it into an -# internal form (seconds since time 0), and adjust according to the -# local time zone (specified by +-seconds calculated in the BEGIN -# section) - -function datetimestamp(input) -{ - # convert the iCal Date+Time entry to a format that mktime can understand - datespec = gensub("([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])T([0-9][0-9])([0-9][0-9])([0-9][0-9]).*[\r]*", "\\1 \\2 \\3 \\4 \\5 \\6", "g", input); - # print "date spec : " datespec; convert this date+time into - # seconds from the beginning of time and include adjustment for - # time zone, as determined in the BEGIN section below. The - # adjustment is only included if the time stamp has a Z at the - # end. Of course, we should actually incorporate the time zone - # information in the time stamp line but ... - if (0 < index(input,"Z")) { - # For time - # zone adjustment, I have not tested edge effects, specifically - # what happens when UTC time is a different day to local time and - # especially when an event with a duration crosses midnight in UTC - # time. It should work but... - timestamp = mktime(datespec) + seconds; - } - else { - timestamp = mktime(datespec); - } - # print "date spec: " datespec; - #timestamp = mktime(datespec); - # print "adjusted : " timestamp - # print "Time stamp : " strftime("%Y-%m-%d %H:%M", timestamp); - return timestamp; -} - -# version of above but for dates only -function datestamp(input) -{ - # create a date using midnight as the time - datespec = gensub( "([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9]).*[\r]*", "\\1 \\2 \\3 0 0 0", "g", input ); - # convert to internal representation - timestamp = mktime(datespec); - # and finally convert to something org understands - datestring = strftime("%Y-%m-%d %a", timestamp); - #print "In datestamp: datespec=" datespec " timestamp=" timestamp " datestring=" datestring; - return datestring; -} - -# start of the output file now -BEGIN { - # use a colon to separate the type of data line from the actual contents - FS = ":"; - - # determine the number of seconds to use for adjusting for time - # zone difference from UTC. This is used in the function - # datetimestamp above. The time zone information returned by - # strftime() is in hours * 100 so we multiply by 36 to get - # seconds. This does not work for time zones that are not an - # integral multiple of hours (e.g. Newfoundland) - seconds = gensub("([+-])0", "\\1", 1, strftime("%z")) * 36; - - date1 = ""; # for start of an event - date2 = ""; # for end of an event, if specified - entry = "" - first = 1; # true until an event has been found - headline = "" - icalentry = "" # the full entry for inspection - id = "" - indescription = 0; - inevent = 0; # we have VEVENTS but also other items which we do not process - location = ""; # outlook entries, at least, often include a location - repeat = ""; # is item repeated? if so, how often - time1 = ""; # for start of an event, if specified - time2 = ""; # for end of an event, if specified - todotype = ""; # type of TODO - - if (NAME == "") - NAME = "ical2org"; - - print "# -*- mode: auto-revert; mode: org; -*-" # suggested by Henrik Holmboe - print "#+TITLE: Main Google calendar entries" - print "#+AUTHOR: Eric S Fraga" - print "#+EMAIL: e.fraga@ucl.ac.uk" - print "#+DESCRIPTION: converted using the ical2org awk script" - print "#+CATEGORY: " NAME - print " " -} - -# continuation lines (at least from Google) start with two spaces -# if the continuation is after a description or a summary, append the entry -# to the respective variable - -/^[ ]+/ { - if (indescription) { - entry = entry gensub("\r", "", "g", gensub("^[ ]+", "", 1, $0)); - } else if (insummary) { - summary = summary gensub("\r", "", "g", gensub("^[ ]+", "", 1, $0)) - } else if (inuid) { - id = id gensub("\r", "", "g", gensub("^[ ]+", "", 1, $0)) - } - icalentry = icalentry "\n" $0 -} - -/^BEGIN:VEVENT/ { - # start of an event. if this is the first, output the preamble from the iCal file - if (first) { - print "* COMMENT original iCal preamble" - print gensub("\r", "", "g", icalentry) - icalentry = "" - } - havesummary = 0; - inevent = 1; - first = false; - repeat = ""; -} - -/^BEGIN:VTODO/ { - if (first){ - print "* COMMENT original iCal preamble"; - print gensub("\r", "", "g", icalentry); - icalentry = ""; - first = false; - } - havesummary = 0; - intodo = 1; - repeat = ""; - todotype = ""; -} -# any line that starts at the left with a non-space character is a new data field - -/^[A-Z]/ { - # we ignore DTSTAMP lines as they change every time you download - # the iCal format file which leads to a change in the converted - # org file as I output the original input. This change, which is - # really content free, makes a revision control system update the - # repository and confuses. - if (! index("DTSTAMP", $1)) icalentry = icalentry "\n" $0 - # this line terminates the collection of description and summary entries - indescription = 0; - if (insummary) { - havesummary = 1; - } - insummary = 0; -} - -# this type of entry represents a day entry, not timed, with date -# stamp YYYYMMDD. For a todo item, this indicates a scheduled item. - -/^DTSTART;VALUE=DATE/ { - # print "DTSTART date only entry: " $0; - # date1 = gensub("([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9]).*[\r]*", "\\1-\\2-\\3", "g", $2) - date1 = datestamp($2); - time1 = "" -} - -# this represents a timed entry with date and time stamp -# YYYYMMDDTHHMMSS we ignore the seconds. This entry may have a time -# zone specification which is currently ignored although it should be -# possible, not easy, to incorporate. We assume that this information -# is only relevant for appointments and not TODO items. We expect -# TODO items to have only a date for the START field and that date -# will be the scheduled date. See above. - -/^DTSTART(;TZID.*)?:/ { - if (inevent) { - # print "DTSTART line: " $0; - # print "checking start time: " $2; - date1 = strftime("%Y-%m-%d %a", datetimestamp($2)); - time1 = strftime(" %H:%M", datetimestamp($2)); - # print "====> time: " time1; - # print date; - } -} - -# and the same for the end date; here we extract only the time and append this to the -# date+time found by the DTSTART entry. We assume that entry was there, of course. -# should probably add some error checking here! In time... - -/^DTEND;VALUE=DATE/ { - if (inevent) { - # date2 = gensub("([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9]).*[\r]", "\\1-\\2-\\3", "g", $2) - date2 = datestamp($2); - time2 = "" - } -} - -/^DTEND(;TZID=[^:]*)?:/ { - if (inevent) { - # print $0 - date2 = strftime("%Y-%m-%d %a", datetimestamp($2)); - time2 = strftime("%H:%M", datetimestamp($2)); - } -} - -# TODO items may (should?) have a DUE date/time. -/^DUE(;TZID=[^:]*)?:/ { - if (intodo){ - date2 = strftime("%Y-%m-%d %a", datetimestamp($2)); - time2 = strftime("%H:%M", datetimestamp($2)); - } -} -# deadline with only a date -/^DUE;VALUE=DATE/ { - # print "DUE;VALUE=DATE entry:" $0 - # print "... date part is >" $2 "<" - # print "... date2 before " date2 - if (intodo) { - #date2 = gensub("([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9]).*[\r]*", "\\1-\\2-\\3", "g", $2) - date2 = datestamp($2); - time2 = "" - } - # print "... date2 after " date2 -} -# The description will the contents of the entry in org-mode. -# this line may be continued. - -/^DESCRIPTION/ { - $1 = ""; - entry = entry "\n" gensub("\r", "", "g", $0); - indescription = 1; -} - -/^LOCATION/ { - $1 = ""; - location = gensub("\r", "", "g", $0); -} - -# the status of a TODO item: we know about NEEDS-ACTION and -# COMPLETED. There may be others... -/^STATUS/ { - if ($2 == "NEEDS-ACTION") - todotype = "TODO"; - else if ($2 == "COMPLETED") - todotype = "DONE"; - else - todotype = "UNKNOWN"; -} -# is there a repetition rule. I don't know how general this is but -# Microsoft's Outlook calendar uses this for repeats - -/^RRULE/ { - # print ">>> Checking rule with string: " $2; - i = match($2,"FREQ=[A-Z]+;"); - # printf(">>> Index=%d start=%d length=%d\n\n", i, RSTART, RLENGTH); - frequency = substr($2, RSTART+5, RLENGTH-6); - # print ">>> Frequency is " frequency "\n\n"; - i = match($2,"INTERVAL=[0-9]+;"); - interval = 1; # default interval if none is found - if (i>0) { - interval = substr($2, RSTART+9, RLENGTH-10); - } - period = ""; - if (frequency == "DAILY") { - period = "d"; - } - else if (frequency == "WEEKLY") { - period = "w"; - } - else if(frequency == "MONTHLY") { - period = "m"; - } - else if(frequency == "YEARLY") { - period = "y"; - } - if (period != "") { - repeat = sprintf(" +%d%s", interval, period); - } - # print ">>> Repeat is " repeat; -} - -# the summary will be the org heading - -/^SUMMARY/ { - $1 = ""; - if (!havesummary) { - summary = gensub("\r", "", "g", $0); - insummary = 1; - } -} - -# the unique ID will be stored as a property of the entry - -/^UID/ { - $1 = ""; - id = gensub("\r", "", "g", $0); - inuid = 1; -} - -# when we reach the end of the event line, we output everything we -# have collected so far, creating a top level org headline with the -# date/time stamp, unique ID property and the contents, if any - -/^END:VEVENT/ { - # translate \n sequences to actual newlines and unprotect commas (,) - print "* " gensub("\\\\,", ",", "g", gensub("\\\\n", " ", "g", summary)) " :" NAME ":" - print ":PROPERTIES:" - print ":ID: " id - if (location != "") { - print ":LOCATION: " gensub("\\\\,", ",", "g", location); - } - print ":END:" - if (date1 == date2) { - if (time2 == "") - print " <" date1 time1 repeat ">" - else - print " <" date1 time1 "-" time2 repeat ">" - } - else { - if (time1 == "") - print "<" date1 ">--<" date2 ">" - else - print " <" date1 time1 ">--<" date2 " " time2 ">" - } - # for the entry, convert all embedded "\n" strings to actual newlines - print "" - # translate \n sequences to actual newlines and unprotect commas (,) - print gensub("\\\\,", ",", "g", gensub("\\\\n", "\n", "g", entry)); - print "** COMMENT original iCal entry" - print gensub("\r", "", "g", icalentry) - summary = "" - date = "" - date1 = "" - date2 = "" - time1 = "" - time2 = "" - entry = "" - icalentry = "" - indescription = 0 - inevent = 0 - insummary = 0 - period = ""; - repeat = ""; -} - -# the end of a TODO item is similar to an event except that the dates -# are used for scheduling and deadline information - -/^END:VTODO/ { - # translate \n sequences to actual newlines and unprotect commas (,) - print "* " todotype " " gensub("\\\\,", ",", "g", gensub("\\\\n", " ", "g", summary)) " :" NAME ":" - # scheduling and deadline information come immediately after the - # headline, before properties - if (date1 != "") { - if (date2 != "") - if (time2 != "") - print "SCHEDULED: <" date1 time1 "> DEADLINE: <" date2 " " time2 "> " - else - print "SCHEDULED: <" date1 time1 "> DEADLINE: <" date2 "> " - else - print "SCHEDULED: <" date1 time1 "> " - } else if (date2 != "") { - if (time2 != "") - print "DEADLINE: <" date2 " " time2 "> " - else - print "DEADLINE: <" date2 "> " - } - # now come the properties which include the ID always and possibly - # a location - print ":PROPERTIES:" - print ":ID: " id - if (location != "") { - print ":LOCATION: " gensub("\\\\,", ",", "g", location); - } - print ":END:" - # now the entry; we put in a blank line just because that's the - # way I like it, ah ha ah ha... ;-) - print "" - # translate \n sequences to actual newlines and unprotect commas (,) - print gensub("\\\\,", ",", "g", gensub("\\\\n", "\n", "g", entry)); - print "** COMMENT original iCal entry" - print gensub("\r", "", "g", icalentry) - summary = ""; - date = ""; - date1 = ""; - date2 = ""; - time1 = ""; - time2 = ""; - entry = ""; - icalentry = ""; - indescription = 0; - inevent = 0; - insummary = 0; - intodo = 0; - period = ""; - repeat = ""; -} - -# Local Variables: -# time-stamp-line-limit: 1000 -# time-stamp-format: "%04y.%02m.%02d %02H:%02M:%02S" -# time-stamp-active: t -# time-stamp-start: "Last change:[ \t]+" -# time-stamp-end: "$" -# End: