icalendar.el 100 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356
  1. ;;; icalendar.el --- iCalendar implementation -*-coding: utf-8 -*-
  2. ;; Copyright (C) 2002-2012 Free Software Foundation, Inc.
  3. ;; Author: Ulf Jasper <ulf.jasper@web.de>
  4. ;; Created: August 2002
  5. ;; Keywords: calendar
  6. ;; Human-Keywords: calendar, diary, iCalendar, vCalendar
  7. ;; Version: 0.19
  8. ;; This file is part of GNU Emacs.
  9. ;; GNU Emacs is free software: you can redistribute it and/or modify
  10. ;; it under the terms of the GNU General Public License as published by
  11. ;; the Free Software Foundation, either version 3 of the License, or
  12. ;; (at your option) any later version.
  13. ;; GNU Emacs is distributed in the hope that it will be useful,
  14. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;; GNU General Public License for more details.
  17. ;; You should have received a copy of the GNU General Public License
  18. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  19. ;;; Commentary:
  20. ;; This package is documented in the Emacs Manual.
  21. ;; Please note:
  22. ;; - Diary entries which have a start time but no end time are assumed to
  23. ;; last for one hour when they are exported.
  24. ;; - Weekly diary entries are assumed to occur the first time in the first
  25. ;; week of the year 2000 when they are exported.
  26. ;; - Yearly diary entries are assumed to occur the first time in the year
  27. ;; 1900 when they are exported.
  28. ;; - Float diary entries are assumed to occur the first time on the
  29. ;; day when they are exported.
  30. ;;; History:
  31. ;; 0.07 onwards: see lisp/ChangeLog
  32. ;; 0.06: (2004-10-06)
  33. ;; - Bugfixes regarding icalendar-import-format-*.
  34. ;; - Fix in icalendar-convert-diary-to-ical -- thanks to Philipp Grau.
  35. ;; 0.05: (2003-06-19)
  36. ;; - New import format scheme: Replaced icalendar-import-prefix-*,
  37. ;; icalendar-import-ignored-properties, and
  38. ;; icalendar-import-separator with icalendar-import-format(-*).
  39. ;; - icalendar-import-file and icalendar-convert-diary-to-ical
  40. ;; have an extra parameter which should prevent them from
  41. ;; erasing their target files (untested!).
  42. ;; - Tested with Emacs 21.3.2
  43. ;; 0.04:
  44. ;; - Bugfix: import: double quoted param values did not work
  45. ;; - Read DURATION property when importing.
  46. ;; - Added parameter icalendar-duration-correction.
  47. ;; 0.03: (2003-05-07)
  48. ;; - Export takes care of european-calendar-style.
  49. ;; - Tested with Emacs 21.3.2 and XEmacs 21.4.12
  50. ;; 0.02:
  51. ;; - Should work in XEmacs now. Thanks to Len Trigg for the XEmacs patches!
  52. ;; - Added exporting from Emacs diary to ical.
  53. ;; - Some bugfixes, after testing with calendars from http://icalshare.com.
  54. ;; - Tested with Emacs 21.3.2 and XEmacs 21.4.12
  55. ;; 0.01: (2003-03-21)
  56. ;; - First published version. Trial version. Alpha version.
  57. ;; ======================================================================
  58. ;; To Do:
  59. ;; * Import from ical to diary:
  60. ;; + Need more properties for icalendar-import-format
  61. ;; (added all that Mozilla Calendar uses)
  62. ;; From iCal specifications (RFC2445: 4.8.1), icalendar.el lacks
  63. ;; ATTACH, CATEGORIES, COMMENT, GEO, PERCENT-COMPLETE (VTODO),
  64. ;; PRIORITY, RESOURCES) not considering date/time and time-zone
  65. ;; + check vcalendar version
  66. ;; + check (unknown) elements
  67. ;; + recurring events!
  68. ;; + works for european style calendars only! Does it?
  69. ;; + alarm
  70. ;; + exceptions in recurring events
  71. ;; + the parser is too soft
  72. ;; + error log is incomplete
  73. ;; + nice to have: #include "webcal://foo.com/some-calendar.ics"
  74. ;; + timezones probably still need some improvements.
  75. ;; * Export from diary to ical
  76. ;; + diary-date, diary-float, and self-made sexp entries are not
  77. ;; understood
  78. ;; * Other things
  79. ;; + clean up all those date/time parsing functions
  80. ;; + Handle todo items?
  81. ;; + Check iso 8601 for datetime and period
  82. ;; + Which chars to (un)escape?
  83. ;;; Code:
  84. (defconst icalendar-version "0.19"
  85. "Version number of icalendar.el.")
  86. ;; ======================================================================
  87. ;; Customizables
  88. ;; ======================================================================
  89. (defgroup icalendar nil
  90. "iCalendar support."
  91. :prefix "icalendar-"
  92. :group 'calendar)
  93. (defcustom icalendar-import-format
  94. "%s%d%l%o"
  95. "Format for importing events from iCalendar into Emacs diary.
  96. It defines how iCalendar events are inserted into diary file.
  97. This may either be a string or a function.
  98. In case of a formatting STRING the following specifiers can be used:
  99. %c Class, see `icalendar-import-format-class'
  100. %d Description, see `icalendar-import-format-description'
  101. %l Location, see `icalendar-import-format-location'
  102. %o Organizer, see `icalendar-import-format-organizer'
  103. %s Summary, see `icalendar-import-format-summary'
  104. %t Status, see `icalendar-import-format-status'
  105. %u URL, see `icalendar-import-format-url'
  106. A formatting FUNCTION will be called with a VEVENT as its only
  107. argument. It must return a string. See
  108. `icalendar-import-format-sample' for an example."
  109. :type '(choice
  110. (string :tag "String")
  111. (function :tag "Function"))
  112. :group 'icalendar)
  113. (defcustom icalendar-import-format-summary
  114. "%s"
  115. "Format string defining how the summary element is formatted.
  116. This applies only if the summary is not empty! `%s' is replaced
  117. by the summary."
  118. :type 'string
  119. :group 'icalendar)
  120. (defcustom icalendar-import-format-description
  121. "\n Desc: %s"
  122. "Format string defining how the description element is formatted.
  123. This applies only if the description is not empty! `%s' is
  124. replaced by the description."
  125. :type 'string
  126. :group 'icalendar)
  127. (defcustom icalendar-import-format-location
  128. "\n Location: %s"
  129. "Format string defining how the location element is formatted.
  130. This applies only if the location is not empty! `%s' is replaced
  131. by the location."
  132. :type 'string
  133. :group 'icalendar)
  134. (defcustom icalendar-import-format-organizer
  135. "\n Organizer: %s"
  136. "Format string defining how the organizer element is formatted.
  137. This applies only if the organizer is not empty! `%s' is
  138. replaced by the organizer."
  139. :type 'string
  140. :group 'icalendar)
  141. (defcustom icalendar-import-format-url
  142. "\n URL: %s"
  143. "Format string defining how the URL element is formatted.
  144. This applies only if the URL is not empty! `%s' is replaced by
  145. the URL."
  146. :type 'string
  147. :group 'icalendar)
  148. (defcustom icalendar-import-format-status
  149. "\n Status: %s"
  150. "Format string defining how the status element is formatted.
  151. This applies only if the status is not empty! `%s' is replaced by
  152. the status."
  153. :type 'string
  154. :group 'icalendar)
  155. (defcustom icalendar-import-format-class
  156. "\n Class: %s"
  157. "Format string defining how the class element is formatted.
  158. This applies only if the class is not empty! `%s' is replaced by
  159. the class."
  160. :type 'string
  161. :group 'icalendar)
  162. (defcustom icalendar-recurring-start-year
  163. 2005
  164. "Start year for recurring events.
  165. Some calendar browsers only propagate recurring events for
  166. several years beyond the start time. Set this string to a year
  167. just before the start of your personal calendar."
  168. :type 'integer
  169. :group 'icalendar)
  170. (defcustom icalendar-export-hidden-diary-entries
  171. t
  172. "Determines whether hidden diary entries are exported.
  173. If non-nil hidden diary entries (starting with `&') get exported,
  174. if nil they are ignored."
  175. :type 'boolean
  176. :group 'icalendar)
  177. (defcustom icalendar-uid-format
  178. "emacs%t%c"
  179. "Format of unique ID code (UID) for each iCalendar object.
  180. The following specifiers are available:
  181. %c COUNTER, an integer value that is increased each time a uid is
  182. generated. This may be necessary for systems which do not
  183. provide time-resolution finer than a second.
  184. %h HASH, a hash value of the diary entry,
  185. %s DTSTART, the start date (excluding time) of the diary entry,
  186. %t TIMESTAMP, a unique creation timestamp,
  187. %u USERNAME, the variable `user-login-name'.
  188. For example, a value of \"%s_%h@mydomain.com\" will generate a
  189. UID code for each entry composed of the time of the event, a hash
  190. code for the event, and your personal domain name."
  191. :type 'string
  192. :group 'icalendar)
  193. (defvar icalendar-debug nil
  194. "Enable icalendar debug messages.")
  195. ;; ======================================================================
  196. ;; NO USER SERVICEABLE PARTS BELOW THIS LINE
  197. ;; ======================================================================
  198. (defconst icalendar--weekday-array ["SU" "MO" "TU" "WE" "TH" "FR" "SA"])
  199. ;; ======================================================================
  200. ;; all the other libs we need
  201. ;; ======================================================================
  202. (require 'calendar)
  203. (require 'diary-lib)
  204. ;; ======================================================================
  205. ;; misc
  206. ;; ======================================================================
  207. (defun icalendar--dmsg (&rest args)
  208. "Print message ARGS if `icalendar-debug' is non-nil."
  209. (if icalendar-debug
  210. (apply 'message args)))
  211. ;; ======================================================================
  212. ;; Core functionality
  213. ;; Functions for parsing icalendars, importing and so on
  214. ;; ======================================================================
  215. (defun icalendar--get-unfolded-buffer (folded-ical-buffer)
  216. "Return a new buffer containing the unfolded contents of a buffer.
  217. Folding is the iCalendar way of wrapping long lines. In the
  218. created buffer all occurrences of CR LF BLANK are replaced by the
  219. empty string. Argument FOLDED-ICAL-BUFFER is the unfolded input
  220. buffer."
  221. (let ((unfolded-buffer (get-buffer-create " *icalendar-work*")))
  222. (save-current-buffer
  223. (set-buffer unfolded-buffer)
  224. (erase-buffer)
  225. (insert-buffer-substring folded-ical-buffer)
  226. (goto-char (point-min))
  227. (while (re-search-forward "\r?\n[ \t]" nil t)
  228. (replace-match "" nil nil)))
  229. unfolded-buffer))
  230. (defsubst icalendar--rris (regexp rep string &optional fixedcase literal)
  231. "Replace regular expression in string.
  232. Pass arguments REGEXP REP STRING FIXEDCASE LITERAL to
  233. `replace-regexp-in-string' (Emacs) or to `replace-in-string' (XEmacs)."
  234. (cond ((fboundp 'replace-regexp-in-string)
  235. ;; Emacs:
  236. (replace-regexp-in-string regexp rep string fixedcase literal))
  237. ((fboundp 'replace-in-string)
  238. ;; XEmacs:
  239. (save-match-data ;; apparently XEmacs needs save-match-data
  240. (replace-in-string string regexp rep literal)))))
  241. (defun icalendar--read-element (invalue inparams)
  242. "Recursively read the next iCalendar element in the current buffer.
  243. INVALUE gives the current iCalendar element we are reading.
  244. INPARAMS gives the current parameters.....
  245. This function calls itself recursively for each nested calendar element
  246. it finds."
  247. (let (element children line name params param param-name param-value
  248. value
  249. (continue t))
  250. (setq children '())
  251. (while (and continue
  252. (re-search-forward "^\\([A-Za-z0-9-]+\\)[;:]" nil t))
  253. (setq name (intern (match-string 1)))
  254. (backward-char 1)
  255. (setq params '())
  256. (setq line '())
  257. (while (looking-at ";")
  258. (re-search-forward ";\\([A-Za-z0-9-]+\\)=" nil nil)
  259. (setq param-name (intern (match-string 1)))
  260. (re-search-forward "\\(\\([^;,:\"]+\\)\\|\"\\([^\"]+\\)\"\\)[;:]"
  261. nil t)
  262. (backward-char 1)
  263. (setq param-value (or (match-string 2) (match-string 3)))
  264. (setq param (list param-name param-value))
  265. (while (looking-at ",")
  266. (re-search-forward "\\(\\([^;,:]+\\)\\|\"\\([^\"]+\\)\"\\)"
  267. nil t)
  268. (if (match-string 2)
  269. (setq param-value (match-string 2))
  270. (setq param-value (match-string 3)))
  271. (setq param (append param param-value)))
  272. (setq params (append params param)))
  273. (unless (looking-at ":")
  274. (error "Oops"))
  275. (forward-char 1)
  276. (re-search-forward "\\(.*\\)\\(\r?\n[ \t].*\\)*" nil t)
  277. (setq value (icalendar--rris "\r?\n[ \t]" "" (match-string 0)))
  278. (setq line (list name params value))
  279. (cond ((eq name 'BEGIN)
  280. (setq children
  281. (append children
  282. (list (icalendar--read-element (intern value)
  283. params)))))
  284. ((eq name 'END)
  285. (setq continue nil))
  286. (t
  287. (setq element (append element (list line))))))
  288. (if invalue
  289. (list invalue inparams element children)
  290. children)))
  291. ;; ======================================================================
  292. ;; helper functions for examining events
  293. ;; ======================================================================
  294. ;;(defsubst icalendar--get-all-event-properties (event)
  295. ;; "Return the list of properties in this EVENT."
  296. ;; (car (cddr event)))
  297. (defun icalendar--get-event-property (event prop)
  298. "For the given EVENT return the value of the first occurrence of PROP."
  299. (catch 'found
  300. (let ((props (car (cddr event))) pp)
  301. (while props
  302. (setq pp (car props))
  303. (if (eq (car pp) prop)
  304. (throw 'found (car (cddr pp))))
  305. (setq props (cdr props))))
  306. nil))
  307. (defun icalendar--get-event-property-attributes (event prop)
  308. "For the given EVENT return attributes of the first occurrence of PROP."
  309. (catch 'found
  310. (let ((props (car (cddr event))) pp)
  311. (while props
  312. (setq pp (car props))
  313. (if (eq (car pp) prop)
  314. (throw 'found (cadr pp)))
  315. (setq props (cdr props))))
  316. nil))
  317. (defun icalendar--get-event-properties (event prop)
  318. "For the given EVENT return a list of all values of the property PROP."
  319. (let ((props (car (cddr event))) pp result)
  320. (while props
  321. (setq pp (car props))
  322. (if (eq (car pp) prop)
  323. (setq result (append (split-string (car (cddr pp)) ",") result)))
  324. (setq props (cdr props)))
  325. result))
  326. ;; (defun icalendar--set-event-property (event prop new-value)
  327. ;; "For the given EVENT set the property PROP to the value NEW-VALUE."
  328. ;; (catch 'found
  329. ;; (let ((props (car (cddr event))) pp)
  330. ;; (while props
  331. ;; (setq pp (car props))
  332. ;; (when (eq (car pp) prop)
  333. ;; (setcdr (cdr pp) new-value)
  334. ;; (throw 'found (car (cddr pp))))
  335. ;; (setq props (cdr props)))
  336. ;; (setq props (car (cddr event)))
  337. ;; (setcar (cddr event)
  338. ;; (append props (list (list prop nil new-value)))))))
  339. (defun icalendar--get-children (node name)
  340. "Return all children of the given NODE which have a name NAME.
  341. For instance the VCALENDAR node can have VEVENT children as well as VTODO
  342. children."
  343. (let ((result nil)
  344. (children (cadr (cddr node))))
  345. (when (eq (car node) name)
  346. (setq result node))
  347. ;;(message "%s" node)
  348. (when children
  349. (let ((subresult
  350. (delq nil
  351. (mapcar (lambda (n)
  352. (icalendar--get-children n name))
  353. children))))
  354. (if subresult
  355. (if result
  356. (setq result (append result subresult))
  357. (setq result subresult)))))
  358. result))
  359. ;; private
  360. (defun icalendar--all-events (icalendar)
  361. "Return the list of all existing events in the given ICALENDAR."
  362. (let ((result '()))
  363. (mapc (lambda (elt)
  364. (setq result (append (icalendar--get-children elt 'VEVENT)
  365. result)))
  366. (nreverse icalendar))
  367. result))
  368. (defun icalendar--split-value (value-string)
  369. "Split VALUE-STRING at ';='."
  370. (let ((result '())
  371. param-name param-value)
  372. (when value-string
  373. (save-current-buffer
  374. (set-buffer (get-buffer-create " *icalendar-work*"))
  375. (set-buffer-modified-p nil)
  376. (erase-buffer)
  377. (insert value-string)
  378. (goto-char (point-min))
  379. (while
  380. (re-search-forward
  381. "\\([A-Za-z0-9-]+\\)=\\(\\([^;:]+\\)\\|\"\\([^\"]+\\)\"\\);?"
  382. nil t)
  383. (setq param-name (intern (match-string 1)))
  384. (setq param-value (match-string 2))
  385. (setq result
  386. (append result (list (list param-name param-value)))))))
  387. result))
  388. (defun icalendar--convert-tz-offset (alist dst-p)
  389. "Return a cons of two strings representing a timezone start.
  390. ALIST is an alist entry from a VTIMEZONE, like STANDARD.
  391. DST-P is non-nil if this is for daylight savings time.
  392. The strings are suitable for assembling into a TZ variable."
  393. (let ((offset (car (cddr (assq 'TZOFFSETTO alist))))
  394. (rrule-value (car (cddr (assq 'RRULE alist))))
  395. (dtstart (car (cddr (assq 'DTSTART alist)))))
  396. ;; FIXME: for now we only handle RRULE and not RDATE here.
  397. (when (and offset rrule-value dtstart)
  398. (let* ((rrule (icalendar--split-value rrule-value))
  399. (freq (cadr (assq 'FREQ rrule)))
  400. (bymonth (cadr (assq 'BYMONTH rrule)))
  401. (byday (cadr (assq 'BYDAY rrule))))
  402. ;; FIXME: we don't correctly handle WKST here.
  403. (if (and (string= freq "YEARLY") bymonth)
  404. (cons
  405. (concat
  406. ;; Fake a name.
  407. (if dst-p "DST" "STD")
  408. ;; For TZ, OFFSET is added to the local time. So,
  409. ;; invert the values.
  410. (if (eq (aref offset 0) ?-) "+" "-")
  411. (substring offset 1 3)
  412. ":"
  413. (substring offset 3 5))
  414. ;; The start time.
  415. (let* ((day (icalendar--get-weekday-number (substring byday -2)))
  416. (week (if (eq day -1)
  417. byday
  418. (substring byday 0 -2))))
  419. ;; "Translate" the iCalendar way to specify the last
  420. ;; (sun|mon|...)day in month to the tzset way.
  421. (if (string= week "-1") ; last day as iCalendar calls it
  422. (setq week "5")) ; last day as tzset calls it
  423. (concat "M" bymonth "." week "." (if (eq day -1) "0"
  424. (int-to-string day))
  425. ;; Start time.
  426. "/"
  427. (substring dtstart -6 -4)
  428. ":"
  429. (substring dtstart -4 -2)
  430. ":"
  431. (substring dtstart -2)))))))))
  432. (defun icalendar--parse-vtimezone (alist)
  433. "Turn a VTIMEZONE ALIST into a cons (ID . TZ-STRING).
  434. Return nil if timezone cannot be parsed."
  435. (let* ((tz-id (icalendar--get-event-property alist 'TZID))
  436. (daylight (cadr (cdar (icalendar--get-children alist 'DAYLIGHT))))
  437. (day (and daylight (icalendar--convert-tz-offset daylight t)))
  438. (standard (cadr (cdar (icalendar--get-children alist 'STANDARD))))
  439. (std (and standard (icalendar--convert-tz-offset standard nil))))
  440. (if (and tz-id std)
  441. (cons tz-id
  442. (if day
  443. (concat (car std) (car day)
  444. "," (cdr day) "," (cdr std))
  445. (car std))))))
  446. (defun icalendar--convert-all-timezones (icalendar)
  447. "Convert all timezones in the ICALENDAR into an alist.
  448. Each element of the alist is a cons (ID . TZ-STRING),
  449. like `icalendar--parse-vtimezone'."
  450. (let (result)
  451. (dolist (zone (icalendar--get-children (car icalendar) 'VTIMEZONE))
  452. (setq zone (icalendar--parse-vtimezone zone))
  453. (if zone
  454. (setq result (cons zone result))))
  455. result))
  456. (defun icalendar--find-time-zone (prop-list zone-map)
  457. "Return a timezone string for the time zone in PROP-LIST, or nil if none.
  458. ZONE-MAP is a timezone alist as returned by `icalendar--convert-all-timezones'."
  459. (let ((id (plist-get prop-list 'TZID)))
  460. (if id
  461. (cdr (assoc id zone-map)))))
  462. (defun icalendar--decode-isodatetime (isodatetimestring &optional day-shift
  463. zone)
  464. "Return ISODATETIMESTRING in format like `decode-time'.
  465. Converts from ISO-8601 to Emacs representation. If
  466. ISODATETIMESTRING specifies UTC time (trailing letter Z) the
  467. decoded time is given in the local time zone! If optional
  468. parameter DAY-SHIFT is non-nil the result is shifted by DAY-SHIFT
  469. days.
  470. ZONE, if provided, is the timezone, in any format understood by `encode-time'.
  471. FIXME: multiple comma-separated values should be allowed!"
  472. (icalendar--dmsg isodatetimestring)
  473. (if isodatetimestring
  474. ;; day/month/year must be present
  475. (let ((year (read (substring isodatetimestring 0 4)))
  476. (month (read (substring isodatetimestring 4 6)))
  477. (day (read (substring isodatetimestring 6 8)))
  478. (hour 0)
  479. (minute 0)
  480. (second 0))
  481. (when (> (length isodatetimestring) 12)
  482. ;; hour/minute present
  483. (setq hour (read (substring isodatetimestring 9 11)))
  484. (setq minute (read (substring isodatetimestring 11 13))))
  485. (when (> (length isodatetimestring) 14)
  486. ;; seconds present
  487. (setq second (read (substring isodatetimestring 13 15))))
  488. (when (and (> (length isodatetimestring) 15)
  489. ;; UTC specifier present
  490. (char-equal ?Z (aref isodatetimestring 15)))
  491. ;; if not UTC add current-time-zone offset
  492. (setq second (+ (car (current-time-zone)) second)))
  493. ;; shift if necessary
  494. (if day-shift
  495. (let ((mdy (calendar-gregorian-from-absolute
  496. (+ (calendar-absolute-from-gregorian
  497. (list month day year))
  498. day-shift))))
  499. (setq month (nth 0 mdy))
  500. (setq day (nth 1 mdy))
  501. (setq year (nth 2 mdy))))
  502. ;; create the decoded date-time
  503. ;; FIXME!?!
  504. (condition-case nil
  505. (decode-time (encode-time second minute hour day month year zone))
  506. (error
  507. (message "Cannot decode \"%s\"" isodatetimestring)
  508. ;; hope for the best...
  509. (list second minute hour day month year 0 nil 0))))
  510. ;; isodatetimestring == nil
  511. nil))
  512. (defun icalendar--decode-isoduration (isodurationstring
  513. &optional duration-correction)
  514. "Convert ISODURATIONSTRING into format provided by `decode-time'.
  515. Converts from ISO-8601 to Emacs representation. If ISODURATIONSTRING
  516. specifies UTC time (trailing letter Z) the decoded time is given in
  517. the local time zone!
  518. Optional argument DURATION-CORRECTION shortens result by one day.
  519. FIXME: TZID-attributes are ignored....!
  520. FIXME: multiple comma-separated values should be allowed!"
  521. (if isodurationstring
  522. (save-match-data
  523. (string-match
  524. (concat
  525. "^P[+-]?\\("
  526. "\\(\\([0-9]+\\)D\\)" ; days only
  527. "\\|"
  528. "\\(\\(\\([0-9]+\\)D\\)?T\\(\\([0-9]+\\)H\\)?" ; opt days
  529. "\\(\\([0-9]+\\)M\\)?\\(\\([0-9]+\\)S\\)?\\)" ; mand. time
  530. "\\|"
  531. "\\(\\([0-9]+\\)W\\)" ; weeks only
  532. "\\)$") isodurationstring)
  533. (let ((seconds 0)
  534. (minutes 0)
  535. (hours 0)
  536. (days 0)
  537. (months 0)
  538. (years 0))
  539. (cond
  540. ((match-beginning 2) ;days only
  541. (setq days (read (substring isodurationstring
  542. (match-beginning 3)
  543. (match-end 3))))
  544. (when duration-correction
  545. (setq days (1- days))))
  546. ((match-beginning 4) ;days and time
  547. (if (match-beginning 5)
  548. (setq days (* 7 (read (substring isodurationstring
  549. (match-beginning 6)
  550. (match-end 6))))))
  551. (if (match-beginning 7)
  552. (setq hours (read (substring isodurationstring
  553. (match-beginning 8)
  554. (match-end 8)))))
  555. (if (match-beginning 9)
  556. (setq minutes (read (substring isodurationstring
  557. (match-beginning 10)
  558. (match-end 10)))))
  559. (if (match-beginning 11)
  560. (setq seconds (read (substring isodurationstring
  561. (match-beginning 12)
  562. (match-end 12))))))
  563. ((match-beginning 13) ;weeks only
  564. (setq days (* 7 (read (substring isodurationstring
  565. (match-beginning 14)
  566. (match-end 14)))))))
  567. (list seconds minutes hours days months years)))
  568. ;; isodatetimestring == nil
  569. nil))
  570. (defun icalendar--add-decoded-times (time1 time2)
  571. "Add TIME1 to TIME2.
  572. Both times must be given in decoded form. One of these times must be
  573. valid (year > 1900 or something)."
  574. ;; FIXME: does this function exist already?
  575. (decode-time (encode-time
  576. (+ (nth 0 time1) (nth 0 time2))
  577. (+ (nth 1 time1) (nth 1 time2))
  578. (+ (nth 2 time1) (nth 2 time2))
  579. (+ (nth 3 time1) (nth 3 time2))
  580. (+ (nth 4 time1) (nth 4 time2))
  581. (+ (nth 5 time1) (nth 5 time2))
  582. nil
  583. nil
  584. ;;(or (nth 6 time1) (nth 6 time2)) ;; FIXME?
  585. )))
  586. (defun icalendar--datetime-to-american-date (datetime &optional separator)
  587. "Convert the decoded DATETIME to American-style format.
  588. Optional argument SEPARATOR gives the separator between month,
  589. day, and year. If nil a blank character is used as separator.
  590. American format: \"month day year\"."
  591. (if datetime
  592. (format "%d%s%d%s%d" (nth 4 datetime) ;month
  593. (or separator " ")
  594. (nth 3 datetime) ;day
  595. (or separator " ")
  596. (nth 5 datetime)) ;year
  597. ;; datetime == nil
  598. nil))
  599. (define-obsolete-function-alias 'icalendar--datetime-to-noneuropean-date
  600. 'icalendar--datetime-to-american-date "icalendar 0.19")
  601. (defun icalendar--datetime-to-european-date (datetime &optional separator)
  602. "Convert the decoded DATETIME to European format.
  603. Optional argument SEPARATOR gives the separator between month,
  604. day, and year. If nil a blank character is used as separator.
  605. European format: (day month year).
  606. FIXME"
  607. (if datetime
  608. (format "%d%s%d%s%d" (nth 3 datetime) ;day
  609. (or separator " ")
  610. (nth 4 datetime) ;month
  611. (or separator " ")
  612. (nth 5 datetime)) ;year
  613. ;; datetime == nil
  614. nil))
  615. (defun icalendar--datetime-to-iso-date (datetime &optional separator)
  616. "Convert the decoded DATETIME to ISO format.
  617. Optional argument SEPARATOR gives the separator between month,
  618. day, and year. If nil a blank character is used as separator.
  619. ISO format: (year month day)."
  620. (if datetime
  621. (format "%d%s%d%s%d" (nth 5 datetime) ;year
  622. (or separator " ")
  623. (nth 4 datetime) ;month
  624. (or separator " ")
  625. (nth 3 datetime)) ;day
  626. ;; datetime == nil
  627. nil))
  628. (defun icalendar--date-style ()
  629. "Return current calendar date style.
  630. Convenience function to handle transition from old
  631. `european-calendar-style' to new `calendar-date-style'."
  632. (if (boundp 'calendar-date-style)
  633. calendar-date-style
  634. (if (with-no-warnings european-calendar-style)
  635. 'european
  636. 'american)))
  637. (defun icalendar--datetime-to-diary-date (datetime &optional separator)
  638. "Convert the decoded DATETIME to diary format.
  639. Optional argument SEPARATOR gives the separator between month,
  640. day, and year. If nil a blank character is used as separator.
  641. Call icalendar--datetime-to-*-date according to the current
  642. calendar date style."
  643. (funcall (intern-soft (format "icalendar--datetime-to-%s-date"
  644. (icalendar--date-style)))
  645. datetime separator))
  646. (defun icalendar--datetime-to-colontime (datetime)
  647. "Extract the time part of a decoded DATETIME into 24-hour format.
  648. Note that this silently ignores seconds."
  649. (format "%02d:%02d" (nth 2 datetime) (nth 1 datetime)))
  650. (defun icalendar--get-month-number (monthname)
  651. "Return the month number for the given MONTHNAME."
  652. (catch 'found
  653. (let ((num 1)
  654. (m (downcase monthname)))
  655. (mapc (lambda (month)
  656. (let ((mm (downcase month)))
  657. (if (or (string-equal mm m)
  658. (string-equal (substring mm 0 3) m))
  659. (throw 'found num))
  660. (setq num (1+ num))))
  661. calendar-month-name-array))
  662. ;; Error:
  663. -1))
  664. (defun icalendar--get-weekday-number (abbrevweekday)
  665. "Return the number for the ABBREVWEEKDAY."
  666. (if abbrevweekday
  667. (catch 'found
  668. (let ((num 0)
  669. (aw (downcase abbrevweekday)))
  670. (mapc (lambda (day)
  671. (let ((d (downcase day)))
  672. (if (string-equal d aw)
  673. (throw 'found num))
  674. (setq num (1+ num))))
  675. icalendar--weekday-array)))
  676. ;; Error:
  677. -1))
  678. (defun icalendar--get-weekday-numbers (abbrevweekdays)
  679. "Return the list of numbers for the comma-separated ABBREVWEEKDAYS."
  680. (when abbrevweekdays
  681. (let* ((num -1)
  682. (weekday-alist (mapcar (lambda (day)
  683. (progn
  684. (setq num (1+ num))
  685. (cons (downcase day) num)))
  686. icalendar--weekday-array)))
  687. (delq nil
  688. (mapcar (lambda (abbrevday)
  689. (cdr (assoc abbrevday weekday-alist)))
  690. (split-string (downcase abbrevweekdays) ","))))))
  691. (defun icalendar--get-weekday-abbrev (weekday)
  692. "Return the abbreviated WEEKDAY."
  693. (catch 'found
  694. (let ((num 0)
  695. (w (downcase weekday)))
  696. (mapc (lambda (day)
  697. (let ((d (downcase day)))
  698. (if (or (string-equal d w)
  699. (string-equal (substring d 0 3) w))
  700. (throw 'found (aref icalendar--weekday-array num)))
  701. (setq num (1+ num))))
  702. calendar-day-name-array))
  703. ;; Error:
  704. nil))
  705. (defun icalendar--date-to-isodate (date &optional day-shift)
  706. "Convert DATE to iso-style date.
  707. DATE must be a list of the form (month day year).
  708. If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days."
  709. (let ((mdy (calendar-gregorian-from-absolute
  710. (+ (calendar-absolute-from-gregorian date)
  711. (or day-shift 0)))))
  712. (format "%04d%02d%02d" (nth 2 mdy) (nth 0 mdy) (nth 1 mdy))))
  713. (defun icalendar--datestring-to-isodate (datestring &optional day-shift)
  714. "Convert diary-style DATESTRING to iso-style date.
  715. If DAY-SHIFT is non-nil, the result is shifted by DAY-SHIFT days
  716. -- DAY-SHIFT must be either nil or an integer. This function
  717. tries to figure the date style from DATESTRING itself. If that
  718. is not possible it uses the current calendar date style."
  719. (let ((day -1) month year)
  720. (save-match-data
  721. (cond ( ;; iso-style numeric date
  722. (string-match (concat "\\s-*"
  723. "\\([0-9]\\{4\\}\\)[ \t/]\\s-*"
  724. "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
  725. "0?\\([1-9][0-9]?\\)")
  726. datestring)
  727. (setq year (read (substring datestring (match-beginning 1)
  728. (match-end 1))))
  729. (setq month (read (substring datestring (match-beginning 2)
  730. (match-end 2))))
  731. (setq day (read (substring datestring (match-beginning 3)
  732. (match-end 3)))))
  733. ( ;; non-iso numeric date -- must rely on configured
  734. ;; calendar style
  735. (string-match (concat "\\s-*"
  736. "0?\\([1-9][0-9]?\\)[ \t/]\\s-*"
  737. "0?\\([1-9][0-9]?\\),?[ \t/]\\s-*"
  738. "\\([0-9]\\{4\\}\\)")
  739. datestring)
  740. (setq day (read (substring datestring (match-beginning 1)
  741. (match-end 1))))
  742. (setq month (read (substring datestring (match-beginning 2)
  743. (match-end 2))))
  744. (setq year (read (substring datestring (match-beginning 3)
  745. (match-end 3))))
  746. (if (eq (icalendar--date-style) 'american)
  747. (let ((x month))
  748. (setq month day)
  749. (setq day x))))
  750. ( ;; date contains month names -- iso style
  751. (string-match (concat "\\s-*"
  752. "\\([0-9]\\{4\\}\\)[ \t/]\\s-*"
  753. "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
  754. "0?\\([123]?[0-9]\\)")
  755. datestring)
  756. (setq year (read (substring datestring (match-beginning 1)
  757. (match-end 1))))
  758. (setq month (icalendar--get-month-number
  759. (substring datestring (match-beginning 2)
  760. (match-end 2))))
  761. (setq day (read (substring datestring (match-beginning 3)
  762. (match-end 3)))))
  763. ( ;; date contains month names -- european style
  764. (string-match (concat "\\s-*"
  765. "0?\\([123]?[0-9]\\)[ \t/]\\s-*"
  766. "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
  767. "\\([0-9]\\{4\\}\\)")
  768. datestring)
  769. (setq day (read (substring datestring (match-beginning 1)
  770. (match-end 1))))
  771. (setq month (icalendar--get-month-number
  772. (substring datestring (match-beginning 2)
  773. (match-end 2))))
  774. (setq year (read (substring datestring (match-beginning 3)
  775. (match-end 3)))))
  776. ( ;; date contains month names -- american style
  777. (string-match (concat "\\s-*"
  778. "\\([A-Za-z][^ ]+\\)[ \t/]\\s-*"
  779. "0?\\([123]?[0-9]\\),?[ \t/]\\s-*"
  780. "\\([0-9]\\{4\\}\\)")
  781. datestring)
  782. (setq day (read (substring datestring (match-beginning 2)
  783. (match-end 2))))
  784. (setq month (icalendar--get-month-number
  785. (substring datestring (match-beginning 1)
  786. (match-end 1))))
  787. (setq year (read (substring datestring (match-beginning 3)
  788. (match-end 3)))))
  789. (t
  790. nil)))
  791. (if (> day 0)
  792. (let ((mdy (calendar-gregorian-from-absolute
  793. (+ (calendar-absolute-from-gregorian (list month day
  794. year))
  795. (or day-shift 0)))))
  796. (icalendar--dmsg (format "%04d%02d%02d" (nth 2 mdy) (nth 0 mdy) (nth 1 mdy)))
  797. (format "%04d%02d%02d" (nth 2 mdy) (nth 0 mdy) (nth 1 mdy)))
  798. nil)))
  799. (defun icalendar--diarytime-to-isotime (timestring ampmstring)
  800. "Convert a time like 9:30pm to an iso-conform string like T213000.
  801. In this example the TIMESTRING would be \"9:30\" and the AMPMSTRING
  802. would be \"pm\"."
  803. (if timestring
  804. (let ((starttimenum (read (icalendar--rris ":" "" timestring))))
  805. ;; take care of am/pm style
  806. ;; Be sure *not* to convert 12:00pm - 12:59pm to 2400-2459
  807. (if (and ampmstring (string= "pm" ampmstring) (< starttimenum 1200))
  808. (setq starttimenum (+ starttimenum 1200)))
  809. ;; Similar effect with 12:00am - 12:59am (need to convert to 0000-0059)
  810. (if (and ampmstring (string= "am" ampmstring) (>= starttimenum 1200))
  811. (setq starttimenum (- starttimenum 1200)))
  812. (format "T%04d00" starttimenum))
  813. nil))
  814. (defun icalendar--convert-string-for-export (string)
  815. "Escape comma and other critical characters in STRING."
  816. (icalendar--rris "," "\\\\," string))
  817. (defun icalendar--convert-string-for-import (string)
  818. "Remove escape chars for comma, semicolon etc. from STRING."
  819. (icalendar--rris
  820. "\\\\n" "\n " (icalendar--rris
  821. "\\\\\"" "\"" (icalendar--rris
  822. "\\\\;" ";" (icalendar--rris
  823. "\\\\," "," string)))))
  824. ;; ======================================================================
  825. ;; Export -- convert emacs-diary to iCalendar
  826. ;; ======================================================================
  827. ;;;###autoload
  828. (defun icalendar-export-file (diary-filename ical-filename)
  829. "Export diary file to iCalendar format.
  830. All diary entries in the file DIARY-FILENAME are converted to iCalendar
  831. format. The result is appended to the file ICAL-FILENAME."
  832. (interactive "FExport diary data from file: \n\
  833. Finto iCalendar file: ")
  834. (save-current-buffer
  835. (set-buffer (find-file diary-filename))
  836. (icalendar-export-region (point-min) (point-max) ical-filename)))
  837. (defalias 'icalendar-convert-diary-to-ical 'icalendar-export-file)
  838. (make-obsolete 'icalendar-convert-diary-to-ical 'icalendar-export-file "22.1")
  839. (defvar icalendar--uid-count 0
  840. "Auxiliary counter for creating unique ids.")
  841. (defun icalendar--create-uid (entry-full contents)
  842. "Construct a unique iCalendar UID for a diary entry.
  843. ENTRY-FULL is the full diary entry string. CONTENTS is the
  844. current iCalendar object, as a string. Increase
  845. `icalendar--uid-count'. Returns the UID string."
  846. (let ((uid icalendar-uid-format))
  847. (if
  848. ;; Allow other apps (such as org-mode) to create its own uid
  849. (get-text-property 0 'uid entry-full)
  850. (setq uid (get-text-property 0 'uid entry-full))
  851. (setq uid (replace-regexp-in-string
  852. "%c"
  853. (format "%d" icalendar--uid-count)
  854. uid t t))
  855. (setq icalendar--uid-count (1+ icalendar--uid-count))
  856. (setq uid (replace-regexp-in-string
  857. "%t"
  858. (format "%d%d%d" (car (current-time))
  859. (cadr (current-time))
  860. (car (cddr (current-time))))
  861. uid t t))
  862. (setq uid (replace-regexp-in-string
  863. "%h"
  864. (format "%d" (abs (sxhash entry-full))) uid t t))
  865. (setq uid (replace-regexp-in-string
  866. "%u" (or user-login-name "UNKNOWN_USER") uid t t))
  867. (let ((dtstart (if (string-match "^DTSTART[^:]*:\\([0-9]*\\)" contents)
  868. (substring contents (match-beginning 1) (match-end 1))
  869. "DTSTART")))
  870. (setq uid (replace-regexp-in-string "%s" dtstart uid t t))))
  871. ;; Return the UID string
  872. uid))
  873. ;;;###autoload
  874. (defun icalendar-export-region (min max ical-filename)
  875. "Export region in diary file to iCalendar format.
  876. All diary entries in the region from MIN to MAX in the current buffer are
  877. converted to iCalendar format. The result is appended to the file
  878. ICAL-FILENAME.
  879. This function attempts to return t if something goes wrong. In this
  880. case an error string which describes all the errors and problems is
  881. written into the buffer `*icalendar-errors*'."
  882. (interactive "r
  883. FExport diary data into iCalendar file: ")
  884. (let ((result "")
  885. (start 0)
  886. (entry-main "")
  887. (entry-rest "")
  888. (entry-full "")
  889. (header "")
  890. (contents-n-summary)
  891. (contents)
  892. (found-error nil)
  893. (nonmarker (concat "^" (regexp-quote diary-nonmarking-symbol)
  894. "?"))
  895. (other-elements nil))
  896. ;; prepare buffer with error messages
  897. (save-current-buffer
  898. (set-buffer (get-buffer-create "*icalendar-errors*"))
  899. (erase-buffer))
  900. ;; here we go
  901. (save-excursion
  902. (goto-char min)
  903. (while (re-search-forward
  904. ;; possibly ignore hidden entries beginning with "&"
  905. (if icalendar-export-hidden-diary-entries
  906. "^\\([^ \t\n#].+\\)\\(\\(\n[ \t].*\\)*\\)"
  907. "^\\([^ \t\n&#].+\\)\\(\\(\n[ \t].*\\)*\\)") max t)
  908. (setq entry-main (match-string 1))
  909. (if (match-beginning 2)
  910. (setq entry-rest (match-string 2))
  911. (setq entry-rest ""))
  912. (setq entry-full (concat entry-main entry-rest))
  913. (condition-case error-val
  914. (progn
  915. (setq contents-n-summary
  916. (icalendar--convert-to-ical nonmarker entry-main))
  917. (setq other-elements (icalendar--parse-summary-and-rest
  918. entry-full))
  919. (setq contents (concat (car contents-n-summary)
  920. "\nSUMMARY:" (cadr contents-n-summary)))
  921. (let ((cla (cdr (assoc 'cla other-elements)))
  922. (des (cdr (assoc 'des other-elements)))
  923. (loc (cdr (assoc 'loc other-elements)))
  924. (org (cdr (assoc 'org other-elements)))
  925. (sta (cdr (assoc 'sta other-elements)))
  926. (sum (cdr (assoc 'sum other-elements)))
  927. (url (cdr (assoc 'url other-elements))))
  928. (if cla
  929. (setq contents (concat contents "\nCLASS:" cla)))
  930. (if des
  931. (setq contents (concat contents "\nDESCRIPTION:" des)))
  932. (if loc
  933. (setq contents (concat contents "\nLOCATION:" loc)))
  934. (if org
  935. (setq contents (concat contents "\nORGANIZER:" org)))
  936. (if sta
  937. (setq contents (concat contents "\nSTATUS:" sta)))
  938. ;;(if sum
  939. ;; (setq contents (concat contents "\nSUMMARY:" sum)))
  940. (if url
  941. (setq contents (concat contents "\nURL:" url))))
  942. (setq header (concat "\nBEGIN:VEVENT\nUID:"
  943. (icalendar--create-uid entry-full contents)))
  944. (setq result (concat result header contents "\nEND:VEVENT")))
  945. ;; handle errors
  946. (error
  947. (setq found-error t)
  948. (save-current-buffer
  949. (set-buffer (get-buffer-create "*icalendar-errors*"))
  950. (insert (format "Error in line %d -- %s: `%s'\n"
  951. (count-lines (point-min) (point))
  952. error-val
  953. entry-main))))))
  954. ;; we're done, insert everything into the file
  955. (save-current-buffer
  956. (let ((coding-system-for-write 'utf-8))
  957. (set-buffer (find-file ical-filename))
  958. (goto-char (point-max))
  959. (insert "BEGIN:VCALENDAR")
  960. (insert "\nPRODID:-//Emacs//NONSGML icalendar.el//EN")
  961. (insert "\nVERSION:2.0")
  962. (insert result)
  963. (insert "\nEND:VCALENDAR\n")
  964. ;; save the diary file
  965. (save-buffer)
  966. (unless found-error
  967. (bury-buffer)))))
  968. found-error))
  969. (defun icalendar--convert-to-ical (nonmarker entry-main)
  970. "Convert a diary entry to iCalendar format.
  971. NONMARKER is a regular expression matching the start of non-marking
  972. entries. ENTRY-MAIN is the first line of the diary entry."
  973. (or
  974. ;; anniversaries -- %%(diary-anniversary ...)
  975. (icalendar--convert-anniversary-to-ical nonmarker entry-main)
  976. ;; cyclic events -- %%(diary-cyclic ...)
  977. (icalendar--convert-cyclic-to-ical nonmarker entry-main)
  978. ;; diary-date -- %%(diary-date ...)
  979. (icalendar--convert-date-to-ical nonmarker entry-main)
  980. ;; float events -- %%(diary-float ...)
  981. (icalendar--convert-float-to-ical nonmarker entry-main)
  982. ;; block events -- %%(diary-block ...)
  983. (icalendar--convert-block-to-ical nonmarker entry-main)
  984. ;; other sexp diary entries
  985. (icalendar--convert-sexp-to-ical nonmarker entry-main)
  986. ;; weekly by day -- Monday 8:30 Team meeting
  987. (icalendar--convert-weekly-to-ical nonmarker entry-main)
  988. ;; yearly by day -- 1 May Tag der Arbeit
  989. (icalendar--convert-yearly-to-ical nonmarker entry-main)
  990. ;; "ordinary" events, start and end time given
  991. ;; 1 Feb 2003 blah
  992. (icalendar--convert-ordinary-to-ical nonmarker entry-main)
  993. ;; everything else
  994. ;; Oops! what's that?
  995. (error "Could not parse entry")))
  996. (defun icalendar--parse-summary-and-rest (summary-and-rest)
  997. "Parse SUMMARY-AND-REST from a diary to fill iCalendar properties.
  998. Returns an alist."
  999. (save-match-data
  1000. (if (functionp icalendar-import-format)
  1001. ;; can't do anything
  1002. nil
  1003. ;; split summary-and-rest
  1004. (let* ((s icalendar-import-format)
  1005. (p-cla (or (string-match "%c" icalendar-import-format) -1))
  1006. (p-des (or (string-match "%d" icalendar-import-format) -1))
  1007. (p-loc (or (string-match "%l" icalendar-import-format) -1))
  1008. (p-org (or (string-match "%o" icalendar-import-format) -1))
  1009. (p-sum (or (string-match "%s" icalendar-import-format) -1))
  1010. (p-sta (or (string-match "%t" icalendar-import-format) -1))
  1011. (p-url (or (string-match "%u" icalendar-import-format) -1))
  1012. (p-list (sort (list p-cla p-des p-loc p-org p-sta p-sum p-url) '<))
  1013. (ct 0)
  1014. pos-cla pos-des pos-loc pos-org pos-sta pos-sum pos-url)
  1015. (dotimes (i (length p-list))
  1016. ;; Use 'ct' to keep track of current position in list
  1017. (cond ((and (>= p-cla 0) (= (nth i p-list) p-cla))
  1018. (setq ct (+ ct 1))
  1019. (setq pos-cla (* 2 ct)))
  1020. ((and (>= p-des 0) (= (nth i p-list) p-des))
  1021. (setq ct (+ ct 1))
  1022. (setq pos-des (* 2 ct)))
  1023. ((and (>= p-loc 0) (= (nth i p-list) p-loc))
  1024. (setq ct (+ ct 1))
  1025. (setq pos-loc (* 2 ct)))
  1026. ((and (>= p-org 0) (= (nth i p-list) p-org))
  1027. (setq ct (+ ct 1))
  1028. (setq pos-org (* 2 ct)))
  1029. ((and (>= p-sta 0) (= (nth i p-list) p-sta))
  1030. (setq ct (+ ct 1))
  1031. (setq pos-sta (* 2 ct)))
  1032. ((and (>= p-sum 0) (= (nth i p-list) p-sum))
  1033. (setq ct (+ ct 1))
  1034. (setq pos-sum (* 2 ct)))
  1035. ((and (>= p-url 0) (= (nth i p-list) p-url))
  1036. (setq ct (+ ct 1))
  1037. (setq pos-url (* 2 ct)))) )
  1038. (mapc (lambda (ij)
  1039. (setq s (icalendar--rris (car ij) (cadr ij) s t t)))
  1040. (list
  1041. ;; summary must be first! because of %s
  1042. (list "%s"
  1043. (concat "\\(" icalendar-import-format-summary "\\)??"))
  1044. (list "%c"
  1045. (concat "\\(" icalendar-import-format-class "\\)??"))
  1046. (list "%d"
  1047. (concat "\\(" icalendar-import-format-description "\\)??"))
  1048. (list "%l"
  1049. (concat "\\(" icalendar-import-format-location "\\)??"))
  1050. (list "%o"
  1051. (concat "\\(" icalendar-import-format-organizer "\\)??"))
  1052. (list "%t"
  1053. (concat "\\(" icalendar-import-format-status "\\)??"))
  1054. (list "%u"
  1055. (concat "\\(" icalendar-import-format-url "\\)??"))))
  1056. ;; Need the \' regexp in order to detect multi-line items
  1057. (setq s (concat "\\`"
  1058. (icalendar--rris "%s" "\\(.*?\\)" s nil t)
  1059. "\\'"))
  1060. (if (string-match s summary-and-rest)
  1061. (let (cla des loc org sta sum url)
  1062. (if (and pos-sum (match-beginning pos-sum))
  1063. (setq sum (substring summary-and-rest
  1064. (match-beginning pos-sum)
  1065. (match-end pos-sum))))
  1066. (if (and pos-cla (match-beginning pos-cla))
  1067. (setq cla (substring summary-and-rest
  1068. (match-beginning pos-cla)
  1069. (match-end pos-cla))))
  1070. (if (and pos-des (match-beginning pos-des))
  1071. (setq des (substring summary-and-rest
  1072. (match-beginning pos-des)
  1073. (match-end pos-des))))
  1074. (if (and pos-loc (match-beginning pos-loc))
  1075. (setq loc (substring summary-and-rest
  1076. (match-beginning pos-loc)
  1077. (match-end pos-loc))))
  1078. (if (and pos-org (match-beginning pos-org))
  1079. (setq org (substring summary-and-rest
  1080. (match-beginning pos-org)
  1081. (match-end pos-org))))
  1082. (if (and pos-sta (match-beginning pos-sta))
  1083. (setq sta (substring summary-and-rest
  1084. (match-beginning pos-sta)
  1085. (match-end pos-sta))))
  1086. (if (and pos-url (match-beginning pos-url))
  1087. (setq url (substring summary-and-rest
  1088. (match-beginning pos-url)
  1089. (match-end pos-url))))
  1090. (list (if cla (cons 'cla cla) nil)
  1091. (if des (cons 'des des) nil)
  1092. (if loc (cons 'loc loc) nil)
  1093. (if org (cons 'org org) nil)
  1094. (if sta (cons 'sta sta) nil)
  1095. ;;(if sum (cons 'sum sum) nil)
  1096. (if url (cons 'url url) nil))))))))
  1097. ;; subroutines for icalendar-export-region
  1098. (defun icalendar--convert-ordinary-to-ical (nonmarker entry-main)
  1099. "Convert \"ordinary\" diary entry to iCalendar format.
  1100. NONMARKER is a regular expression matching the start of non-marking
  1101. entries. ENTRY-MAIN is the first line of the diary entry."
  1102. (if (string-match
  1103. (concat nonmarker
  1104. "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)\\s-*" ; date
  1105. "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?" ; start time
  1106. "\\("
  1107. "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?" ; end time
  1108. "\\)?"
  1109. "\\s-*\\(.*?\\) ?$")
  1110. entry-main)
  1111. (let* ((datetime (substring entry-main (match-beginning 1)
  1112. (match-end 1)))
  1113. (startisostring (icalendar--datestring-to-isodate
  1114. datetime))
  1115. (endisostring (icalendar--datestring-to-isodate
  1116. datetime 1))
  1117. (endisostring1)
  1118. (starttimestring (icalendar--diarytime-to-isotime
  1119. (if (match-beginning 3)
  1120. (substring entry-main
  1121. (match-beginning 3)
  1122. (match-end 3))
  1123. nil)
  1124. (if (match-beginning 4)
  1125. (substring entry-main
  1126. (match-beginning 4)
  1127. (match-end 4))
  1128. nil)))
  1129. (endtimestring (icalendar--diarytime-to-isotime
  1130. (if (match-beginning 6)
  1131. (substring entry-main
  1132. (match-beginning 6)
  1133. (match-end 6))
  1134. nil)
  1135. (if (match-beginning 7)
  1136. (substring entry-main
  1137. (match-beginning 7)
  1138. (match-end 7))
  1139. nil)))
  1140. (summary (icalendar--convert-string-for-export
  1141. (substring entry-main (match-beginning 8)
  1142. (match-end 8)))))
  1143. (icalendar--dmsg "ordinary %s" entry-main)
  1144. (unless startisostring
  1145. (error "Could not parse date"))
  1146. ;; If only start-date is specified, then end-date is next day,
  1147. ;; otherwise it is same day.
  1148. (setq endisostring1 (if starttimestring
  1149. startisostring
  1150. endisostring))
  1151. (when starttimestring
  1152. (unless endtimestring
  1153. (let ((time
  1154. (read (icalendar--rris "^T0?" ""
  1155. starttimestring))))
  1156. (if (< time 230000)
  1157. ;; Case: ends on same day
  1158. (setq endtimestring (format "T%06d"
  1159. (+ 10000 time)))
  1160. ;; Case: ends on next day
  1161. (setq endtimestring (format "T%06d"
  1162. (- time 230000)))
  1163. (setq endisostring1 endisostring)) )))
  1164. (list (concat "\nDTSTART;"
  1165. (if starttimestring "VALUE=DATE-TIME:"
  1166. "VALUE=DATE:")
  1167. startisostring
  1168. (or starttimestring "")
  1169. "\nDTEND;"
  1170. (if endtimestring "VALUE=DATE-TIME:"
  1171. "VALUE=DATE:")
  1172. endisostring1
  1173. (or endtimestring ""))
  1174. summary))
  1175. ;; no match
  1176. nil))
  1177. (defun icalendar-first-weekday-of-year (abbrevweekday year)
  1178. "Find the first ABBREVWEEKDAY in a given YEAR.
  1179. Returns day number."
  1180. (let* ((day-of-week-jan01 (calendar-day-of-week (list 1 1 year)))
  1181. (result (+ 1
  1182. (- (icalendar--get-weekday-number abbrevweekday)
  1183. day-of-week-jan01))))
  1184. (cond ((<= result 0)
  1185. (setq result (+ result 7)))
  1186. ((> result 7)
  1187. (setq result (- result 7))))
  1188. result))
  1189. (defun icalendar--convert-weekly-to-ical (nonmarker entry-main)
  1190. "Convert weekly diary entry to iCalendar format.
  1191. NONMARKER is a regular expression matching the start of non-marking
  1192. entries. ENTRY-MAIN is the first line of the diary entry."
  1193. (if (and (string-match (concat nonmarker
  1194. "\\([a-z]+\\)\\s-+"
  1195. "\\(\\([0-9][0-9]?:[0-9][0-9]\\)"
  1196. "\\([ap]m\\)?"
  1197. "\\(-"
  1198. "\\([0-9][0-9]?:[0-9][0-9]\\)"
  1199. "\\([ap]m\\)?\\)?"
  1200. "\\)?"
  1201. "\\s-*\\(.*?\\) ?$")
  1202. entry-main)
  1203. (icalendar--get-weekday-abbrev
  1204. (substring entry-main (match-beginning 1)
  1205. (match-end 1))))
  1206. (let* ((day (icalendar--get-weekday-abbrev
  1207. (substring entry-main (match-beginning 1)
  1208. (match-end 1))))
  1209. (starttimestring (icalendar--diarytime-to-isotime
  1210. (if (match-beginning 3)
  1211. (substring entry-main
  1212. (match-beginning 3)
  1213. (match-end 3))
  1214. nil)
  1215. (if (match-beginning 4)
  1216. (substring entry-main
  1217. (match-beginning 4)
  1218. (match-end 4))
  1219. nil)))
  1220. (endtimestring (icalendar--diarytime-to-isotime
  1221. (if (match-beginning 6)
  1222. (substring entry-main
  1223. (match-beginning 6)
  1224. (match-end 6))
  1225. nil)
  1226. (if (match-beginning 7)
  1227. (substring entry-main
  1228. (match-beginning 7)
  1229. (match-end 7))
  1230. nil)))
  1231. (summary (icalendar--convert-string-for-export
  1232. (substring entry-main (match-beginning 8)
  1233. (match-end 8)))))
  1234. (icalendar--dmsg "weekly %s" entry-main)
  1235. (when starttimestring
  1236. (unless endtimestring
  1237. (let ((time (read
  1238. (icalendar--rris "^T0?" ""
  1239. starttimestring))))
  1240. (setq endtimestring (format "T%06d"
  1241. (+ 10000 time))))))
  1242. (list (concat "\nDTSTART;"
  1243. (if starttimestring
  1244. "VALUE=DATE-TIME:"
  1245. "VALUE=DATE:")
  1246. ;; Find the first requested weekday of the
  1247. ;; start year
  1248. (funcall 'format "%04d%02d%02d"
  1249. icalendar-recurring-start-year 1
  1250. (icalendar-first-weekday-of-year
  1251. day icalendar-recurring-start-year))
  1252. (or starttimestring "")
  1253. "\nDTEND;"
  1254. (if endtimestring
  1255. "VALUE=DATE-TIME:"
  1256. "VALUE=DATE:")
  1257. (funcall 'format "%04d%02d%02d"
  1258. ;; end is non-inclusive!
  1259. icalendar-recurring-start-year 1
  1260. (+ (icalendar-first-weekday-of-year
  1261. day icalendar-recurring-start-year)
  1262. (if endtimestring 0 1)))
  1263. (or endtimestring "")
  1264. "\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY="
  1265. day)
  1266. summary))
  1267. ;; no match
  1268. nil))
  1269. (defun icalendar--convert-yearly-to-ical (nonmarker entry-main)
  1270. "Convert yearly diary entry to iCalendar format.
  1271. NONMARKER is a regular expression matching the start of non-marking
  1272. entries. ENTRY-MAIN is the first line of the diary entry."
  1273. (if (string-match (concat nonmarker
  1274. (if (eq (icalendar--date-style) 'european)
  1275. "\\([0-9]+[0-9]?\\)\\s-+\\([a-z]+\\)\\s-+"
  1276. "\\([a-z]+\\)\\s-+\\([0-9]+[0-9]?\\)\\s-+")
  1277. "\\*?\\s-*"
  1278. "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
  1279. "\\("
  1280. "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
  1281. "\\)?"
  1282. "\\s-*\\([^0-9]+.*?\\) ?$" ; must not match years
  1283. )
  1284. entry-main)
  1285. (let* ((daypos (if (eq (icalendar--date-style) 'european) 1 2))
  1286. (monpos (if (eq (icalendar--date-style) 'european) 2 1))
  1287. (day (read (substring entry-main
  1288. (match-beginning daypos)
  1289. (match-end daypos))))
  1290. (month (icalendar--get-month-number
  1291. (substring entry-main
  1292. (match-beginning monpos)
  1293. (match-end monpos))))
  1294. (starttimestring (icalendar--diarytime-to-isotime
  1295. (if (match-beginning 4)
  1296. (substring entry-main
  1297. (match-beginning 4)
  1298. (match-end 4))
  1299. nil)
  1300. (if (match-beginning 5)
  1301. (substring entry-main
  1302. (match-beginning 5)
  1303. (match-end 5))
  1304. nil)))
  1305. (endtimestring (icalendar--diarytime-to-isotime
  1306. (if (match-beginning 7)
  1307. (substring entry-main
  1308. (match-beginning 7)
  1309. (match-end 7))
  1310. nil)
  1311. (if (match-beginning 8)
  1312. (substring entry-main
  1313. (match-beginning 8)
  1314. (match-end 8))
  1315. nil)))
  1316. (summary (icalendar--convert-string-for-export
  1317. (substring entry-main (match-beginning 9)
  1318. (match-end 9)))))
  1319. (icalendar--dmsg "yearly %s" entry-main)
  1320. (when starttimestring
  1321. (unless endtimestring
  1322. (let ((time (read
  1323. (icalendar--rris "^T0?" ""
  1324. starttimestring))))
  1325. (setq endtimestring (format "T%06d"
  1326. (+ 10000 time))))))
  1327. (list (concat "\nDTSTART;"
  1328. (if starttimestring "VALUE=DATE-TIME:"
  1329. "VALUE=DATE:")
  1330. (format "1900%02d%02d" month day)
  1331. (or starttimestring "")
  1332. "\nDTEND;"
  1333. (if endtimestring "VALUE=DATE-TIME:"
  1334. "VALUE=DATE:")
  1335. ;; end is not included! shift by one day
  1336. (icalendar--date-to-isodate
  1337. (list month day 1900)
  1338. (if endtimestring 0 1))
  1339. (or endtimestring "")
  1340. "\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH="
  1341. (format "%d" month)
  1342. ";BYMONTHDAY="
  1343. (format "%d" day))
  1344. summary))
  1345. ;; no match
  1346. nil))
  1347. (defun icalendar--convert-sexp-to-ical (nonmarker entry-main)
  1348. "Convert complex sexp diary entry to iCalendar format -- unsupported!
  1349. FIXME!
  1350. NONMARKER is a regular expression matching the start of non-marking
  1351. entries. ENTRY-MAIN is the first line of the diary entry."
  1352. (cond ((string-match (concat nonmarker
  1353. "%%(and \\(([^)]+)\\))\\(\\s-*.*?\\) ?$")
  1354. entry-main)
  1355. ;; simple sexp entry as generated by icalendar.el: strip off the
  1356. ;; unnecessary (and)
  1357. (icalendar--dmsg "diary-sexp from icalendar.el %s" entry-main)
  1358. (icalendar--convert-to-ical
  1359. nonmarker
  1360. (concat "%%"
  1361. (substring entry-main (match-beginning 1) (match-end 1))
  1362. (substring entry-main (match-beginning 2) (match-end 2)))))
  1363. ((string-match (concat nonmarker
  1364. "%%([^)]+)\\s-*.*")
  1365. entry-main)
  1366. (icalendar--dmsg "diary-sexp %s" entry-main)
  1367. (error "Sexp-entries are not supported yet"))
  1368. (t
  1369. ;; no match
  1370. nil)))
  1371. (defun icalendar--convert-block-to-ical (nonmarker entry-main)
  1372. "Convert block diary entry to iCalendar format.
  1373. NONMARKER is a regular expression matching the start of non-marking
  1374. entries. ENTRY-MAIN is the first line of the diary entry."
  1375. (if (string-match (concat nonmarker
  1376. "%%(diary-block \\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\)"
  1377. " +\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
  1378. "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
  1379. "\\("
  1380. "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
  1381. "\\)?"
  1382. "\\s-*\\(.*?\\) ?$")
  1383. entry-main)
  1384. (let* ((startstring (substring entry-main
  1385. (match-beginning 1)
  1386. (match-end 1)))
  1387. (endstring (substring entry-main
  1388. (match-beginning 2)
  1389. (match-end 2)))
  1390. (startisostring (icalendar--datestring-to-isodate
  1391. startstring))
  1392. (endisostring (icalendar--datestring-to-isodate
  1393. endstring))
  1394. (endisostring+1 (icalendar--datestring-to-isodate
  1395. endstring 1))
  1396. (starttimestring (icalendar--diarytime-to-isotime
  1397. (if (match-beginning 4)
  1398. (substring entry-main
  1399. (match-beginning 4)
  1400. (match-end 4))
  1401. nil)
  1402. (if (match-beginning 5)
  1403. (substring entry-main
  1404. (match-beginning 5)
  1405. (match-end 5))
  1406. nil)))
  1407. (endtimestring (icalendar--diarytime-to-isotime
  1408. (if (match-beginning 7)
  1409. (substring entry-main
  1410. (match-beginning 7)
  1411. (match-end 7))
  1412. nil)
  1413. (if (match-beginning 8)
  1414. (substring entry-main
  1415. (match-beginning 8)
  1416. (match-end 8))
  1417. nil)))
  1418. (summary (icalendar--convert-string-for-export
  1419. (substring entry-main (match-beginning 9)
  1420. (match-end 9)))))
  1421. (icalendar--dmsg "diary-block %s" entry-main)
  1422. (when starttimestring
  1423. (unless endtimestring
  1424. (let ((time
  1425. (read (icalendar--rris "^T0?" ""
  1426. starttimestring))))
  1427. (setq endtimestring (format "T%06d"
  1428. (+ 10000 time))))))
  1429. (if starttimestring
  1430. ;; with time -> write rrule
  1431. (list (concat "\nDTSTART;VALUE=DATE-TIME:"
  1432. startisostring
  1433. starttimestring
  1434. "\nDTEND;VALUE=DATE-TIME:"
  1435. startisostring
  1436. endtimestring
  1437. "\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL="
  1438. endisostring)
  1439. summary)
  1440. ;; no time -> write long event
  1441. (list (concat "\nDTSTART;VALUE=DATE:" startisostring
  1442. "\nDTEND;VALUE=DATE:" endisostring+1)
  1443. summary)))
  1444. ;; no match
  1445. nil))
  1446. (defun icalendar--convert-float-to-ical (nonmarker entry-main)
  1447. "Convert float diary entry to iCalendar format -- partially unsupported!
  1448. FIXME! DAY from diary-float yet unimplemented.
  1449. NONMARKER is a regular expression matching the start of non-marking
  1450. entries. ENTRY-MAIN is the first line of the diary entry."
  1451. (if (string-match (concat nonmarker "%%\\((diary-float .+\\) ?$") entry-main)
  1452. (with-temp-buffer
  1453. (insert (match-string 1 entry-main))
  1454. (goto-char (point-min))
  1455. (let* ((sexp (read (current-buffer))) ;using `read' here
  1456. ;easier than regexp
  1457. ;matching, esp. with
  1458. ;different forms of
  1459. ;MONTH
  1460. (month (nth 1 sexp))
  1461. (dayname (nth 2 sexp))
  1462. (n (nth 3 sexp))
  1463. (day (nth 4 sexp))
  1464. (summary
  1465. (replace-regexp-in-string
  1466. "\\(^\s+\\|\s+$\\)" ""
  1467. (buffer-substring (point) (point-max)))))
  1468. (when day
  1469. (progn
  1470. (icalendar--dmsg "diary-float %s" entry-main)
  1471. (error "Don't know if or how to implement day in `diary-float'")))
  1472. (list (concat
  1473. ;;Start today (yes this is an arbitrary choice):
  1474. "\nDTSTART;VALUE=DATE:"
  1475. (format-time-string "%Y%m%d" (current-time))
  1476. ;;BUT remove today if `diary-float'
  1477. ;;expression does not hold true for today:
  1478. (when
  1479. (null (let ((date (calendar-current-date))
  1480. (entry entry-main))
  1481. (diary-float month dayname n)))
  1482. (concat
  1483. "\nEXDATE;VALUE=DATE:"
  1484. (format-time-string "%Y%m%d" (current-time))))
  1485. "\nRRULE:"
  1486. (if (or (numberp month) (listp month))
  1487. "FREQ=YEARLY;BYMONTH="
  1488. "FREQ=MONTHLY")
  1489. (when
  1490. (listp month)
  1491. (mapconcat
  1492. (lambda (m)
  1493. (number-to-string m))
  1494. (cadr month) ","))
  1495. (when
  1496. (numberp month)
  1497. (number-to-string month))
  1498. ";BYDAY="
  1499. (number-to-string n)
  1500. (aref icalendar--weekday-array dayname))
  1501. summary)))
  1502. ;; no match
  1503. nil))
  1504. (defun icalendar--convert-date-to-ical (nonmarker entry-main)
  1505. "Convert `diary-date' diary entry to iCalendar format -- unsupported!
  1506. FIXME!
  1507. NONMARKER is a regular expression matching the start of non-marking
  1508. entries. ENTRY-MAIN is the first line of the diary entry."
  1509. (if (string-match (concat nonmarker
  1510. "%%(diary-date \\([^)]+\\))\\s-*\\(.*?\\) ?$")
  1511. entry-main)
  1512. (progn
  1513. (icalendar--dmsg "diary-date %s" entry-main)
  1514. (error "`diary-date' is not supported yet"))
  1515. ;; no match
  1516. nil))
  1517. (defun icalendar--convert-cyclic-to-ical (nonmarker entry-main)
  1518. "Convert `diary-cyclic' diary entry to iCalendar format.
  1519. NONMARKER is a regular expression matching the start of non-marking
  1520. entries. ENTRY-MAIN is the first line of the diary entry."
  1521. (if (string-match (concat nonmarker
  1522. "%%(diary-cyclic \\([^ ]+\\) +"
  1523. "\\([^ /]+[ /]+[^ /]+[ /]+[^ ]+\\))\\s-*"
  1524. "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
  1525. "\\("
  1526. "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
  1527. "\\)?"
  1528. "\\s-*\\(.*?\\) ?$")
  1529. entry-main)
  1530. (let* ((frequency (substring entry-main (match-beginning 1)
  1531. (match-end 1)))
  1532. (datetime (substring entry-main (match-beginning 2)
  1533. (match-end 2)))
  1534. (startisostring (icalendar--datestring-to-isodate
  1535. datetime))
  1536. (endisostring (icalendar--datestring-to-isodate
  1537. datetime))
  1538. (endisostring+1 (icalendar--datestring-to-isodate
  1539. datetime 1))
  1540. (starttimestring (icalendar--diarytime-to-isotime
  1541. (if (match-beginning 4)
  1542. (substring entry-main
  1543. (match-beginning 4)
  1544. (match-end 4))
  1545. nil)
  1546. (if (match-beginning 5)
  1547. (substring entry-main
  1548. (match-beginning 5)
  1549. (match-end 5))
  1550. nil)))
  1551. (endtimestring (icalendar--diarytime-to-isotime
  1552. (if (match-beginning 7)
  1553. (substring entry-main
  1554. (match-beginning 7)
  1555. (match-end 7))
  1556. nil)
  1557. (if (match-beginning 8)
  1558. (substring entry-main
  1559. (match-beginning 8)
  1560. (match-end 8))
  1561. nil)))
  1562. (summary (icalendar--convert-string-for-export
  1563. (substring entry-main (match-beginning 9)
  1564. (match-end 9)))))
  1565. (icalendar--dmsg "diary-cyclic %s" entry-main)
  1566. (when starttimestring
  1567. (unless endtimestring
  1568. (let ((time
  1569. (read (icalendar--rris "^T0?" ""
  1570. starttimestring))))
  1571. (setq endtimestring (format "T%06d"
  1572. (+ 10000 time))))))
  1573. (list (concat "\nDTSTART;"
  1574. (if starttimestring "VALUE=DATE-TIME:"
  1575. "VALUE=DATE:")
  1576. startisostring
  1577. (or starttimestring "")
  1578. "\nDTEND;"
  1579. (if endtimestring "VALUE=DATE-TIME:"
  1580. "VALUE=DATE:")
  1581. (if endtimestring endisostring endisostring+1)
  1582. (or endtimestring "")
  1583. "\nRRULE:FREQ=DAILY;INTERVAL=" frequency
  1584. ;; strange: korganizer does not expect
  1585. ;; BYSOMETHING here...
  1586. )
  1587. summary))
  1588. ;; no match
  1589. nil))
  1590. (defun icalendar--convert-anniversary-to-ical (nonmarker entry-main)
  1591. "Convert `diary-anniversary' diary entry to iCalendar format.
  1592. NONMARKER is a regular expression matching the start of non-marking
  1593. entries. ENTRY-MAIN is the first line of the diary entry."
  1594. (if (string-match (concat nonmarker
  1595. "%%(diary-anniversary \\([^)]+\\))\\s-*"
  1596. "\\(\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?"
  1597. "\\("
  1598. "-\\([0-9][0-9]?:[0-9][0-9]\\)\\([ap]m\\)?\\)?"
  1599. "\\)?"
  1600. "\\s-*\\(.*?\\) ?$")
  1601. entry-main)
  1602. (let* ((datetime (substring entry-main (match-beginning 1)
  1603. (match-end 1)))
  1604. (startisostring (icalendar--datestring-to-isodate
  1605. datetime))
  1606. (endisostring (icalendar--datestring-to-isodate
  1607. datetime 1))
  1608. (starttimestring (icalendar--diarytime-to-isotime
  1609. (if (match-beginning 3)
  1610. (substring entry-main
  1611. (match-beginning 3)
  1612. (match-end 3))
  1613. nil)
  1614. (if (match-beginning 4)
  1615. (substring entry-main
  1616. (match-beginning 4)
  1617. (match-end 4))
  1618. nil)))
  1619. (endtimestring (icalendar--diarytime-to-isotime
  1620. (if (match-beginning 6)
  1621. (substring entry-main
  1622. (match-beginning 6)
  1623. (match-end 6))
  1624. nil)
  1625. (if (match-beginning 7)
  1626. (substring entry-main
  1627. (match-beginning 7)
  1628. (match-end 7))
  1629. nil)))
  1630. (summary (icalendar--convert-string-for-export
  1631. (substring entry-main (match-beginning 8)
  1632. (match-end 8)))))
  1633. (icalendar--dmsg "diary-anniversary %s" entry-main)
  1634. (when starttimestring
  1635. (unless endtimestring
  1636. (let ((time
  1637. (read (icalendar--rris "^T0?" ""
  1638. starttimestring))))
  1639. (setq endtimestring (format "T%06d"
  1640. (+ 10000 time))))))
  1641. (list (concat "\nDTSTART;"
  1642. (if starttimestring "VALUE=DATE-TIME:"
  1643. "VALUE=DATE:")
  1644. startisostring
  1645. (or starttimestring "")
  1646. "\nDTEND;"
  1647. (if endtimestring "VALUE=DATE-TIME:"
  1648. "VALUE=DATE:")
  1649. endisostring
  1650. (or endtimestring "")
  1651. "\nRRULE:FREQ=YEARLY;INTERVAL=1"
  1652. ;; the following is redundant,
  1653. ;; but korganizer seems to expect this... ;(
  1654. ;; and evolution doesn't understand it... :(
  1655. ;; so... who is wrong?!
  1656. ";BYMONTH="
  1657. (substring startisostring 4 6)
  1658. ";BYMONTHDAY="
  1659. (substring startisostring 6 8))
  1660. summary))
  1661. ;; no match
  1662. nil))
  1663. ;; ======================================================================
  1664. ;; Import -- convert iCalendar to emacs-diary
  1665. ;; ======================================================================
  1666. ;;;###autoload
  1667. (defun icalendar-import-file (ical-filename diary-filename
  1668. &optional non-marking)
  1669. "Import an iCalendar file and append to a diary file.
  1670. Argument ICAL-FILENAME output iCalendar file.
  1671. Argument DIARY-FILENAME input `diary-file'.
  1672. Optional argument NON-MARKING determines whether events are created as
  1673. non-marking or not."
  1674. (interactive "fImport iCalendar data from file: \n\
  1675. Finto diary file:
  1676. p")
  1677. ;; clean up the diary file
  1678. (save-current-buffer
  1679. ;; now load and convert from the ical file
  1680. (set-buffer (find-file ical-filename))
  1681. (icalendar-import-buffer diary-filename t non-marking)))
  1682. ;;;###autoload
  1683. (defun icalendar-import-buffer (&optional diary-file do-not-ask
  1684. non-marking)
  1685. "Extract iCalendar events from current buffer.
  1686. This function searches the current buffer for the first iCalendar
  1687. object, reads it and adds all VEVENT elements to the diary
  1688. DIARY-FILE.
  1689. It will ask for each appointment whether to add it to the diary
  1690. unless DO-NOT-ASK is non-nil. When called interactively,
  1691. DO-NOT-ASK is nil, so that you are asked for each event.
  1692. NON-MARKING determines whether diary events are created as
  1693. non-marking.
  1694. Return code t means that importing worked well, return code nil
  1695. means that an error has occurred. Error messages will be in the
  1696. buffer `*icalendar-errors*'."
  1697. (interactive)
  1698. (save-current-buffer
  1699. ;; prepare ical
  1700. (message "Preparing iCalendar...")
  1701. (set-buffer (icalendar--get-unfolded-buffer (current-buffer)))
  1702. (goto-char (point-min))
  1703. (message "Preparing iCalendar...done")
  1704. (if (re-search-forward "^BEGIN:VCALENDAR\\s-*$" nil t)
  1705. (let (ical-contents ical-errors)
  1706. ;; read ical
  1707. (message "Reading iCalendar...")
  1708. (beginning-of-line)
  1709. (setq ical-contents (icalendar--read-element nil nil))
  1710. (message "Reading iCalendar...done")
  1711. ;; convert ical
  1712. (message "Converting iCalendar...")
  1713. (setq ical-errors (icalendar--convert-ical-to-diary
  1714. ical-contents
  1715. diary-file do-not-ask non-marking))
  1716. (when diary-file
  1717. ;; save the diary file if it is visited already
  1718. (let ((b (find-buffer-visiting diary-file)))
  1719. (when b
  1720. (save-current-buffer
  1721. (set-buffer b)
  1722. (save-buffer)))))
  1723. (message "Converting iCalendar...done")
  1724. ;; return t if no error occurred
  1725. (not ical-errors))
  1726. (message
  1727. "Current buffer does not contain iCalendar contents!")
  1728. ;; return nil, i.e. import did not work
  1729. nil)))
  1730. (defalias 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer)
  1731. (make-obsolete 'icalendar-extract-ical-from-buffer 'icalendar-import-buffer "22.1")
  1732. (defun icalendar--format-ical-event (event)
  1733. "Create a string representation of an iCalendar EVENT."
  1734. (if (functionp icalendar-import-format)
  1735. (funcall icalendar-import-format event)
  1736. (let ((string icalendar-import-format)
  1737. (conversion-list
  1738. '(("%c" CLASS icalendar-import-format-class)
  1739. ("%d" DESCRIPTION icalendar-import-format-description)
  1740. ("%l" LOCATION icalendar-import-format-location)
  1741. ("%o" ORGANIZER icalendar-import-format-organizer)
  1742. ("%s" SUMMARY icalendar-import-format-summary)
  1743. ("%t" STATUS icalendar-import-format-status)
  1744. ("%u" URL icalendar-import-format-url))))
  1745. ;; convert the specifiers in the format string
  1746. (mapc (lambda (i)
  1747. (let* ((spec (car i))
  1748. (prop (cadr i))
  1749. (format (car (cddr i)))
  1750. (contents (icalendar--get-event-property event prop))
  1751. (formatted-contents ""))
  1752. (when (and contents (> (length contents) 0))
  1753. (setq formatted-contents
  1754. (icalendar--rris "%s"
  1755. (icalendar--convert-string-for-import
  1756. contents)
  1757. (symbol-value format)
  1758. t t)))
  1759. (setq string (icalendar--rris spec
  1760. formatted-contents
  1761. string
  1762. t t))))
  1763. conversion-list)
  1764. string)))
  1765. (defun icalendar--convert-ical-to-diary (ical-list diary-file
  1766. &optional do-not-ask
  1767. non-marking)
  1768. "Convert iCalendar data to an Emacs diary file.
  1769. Import VEVENTS from the iCalendar object ICAL-LIST and saves them to a
  1770. DIARY-FILE. If DO-NOT-ASK is nil the user is asked for each event
  1771. whether to actually import it. NON-MARKING determines whether diary
  1772. events are created as non-marking.
  1773. This function attempts to return t if something goes wrong. In this
  1774. case an error string which describes all the errors and problems is
  1775. written into the buffer `*icalendar-errors*'."
  1776. (let* ((ev (icalendar--all-events ical-list))
  1777. (error-string "")
  1778. (event-ok t)
  1779. (found-error nil)
  1780. (zone-map (icalendar--convert-all-timezones ical-list))
  1781. e diary-string)
  1782. ;; step through all events/appointments
  1783. (while ev
  1784. (setq e (car ev))
  1785. (setq ev (cdr ev))
  1786. (setq event-ok nil)
  1787. (condition-case error-val
  1788. (let* ((dtstart (icalendar--get-event-property e 'DTSTART))
  1789. (dtstart-zone (icalendar--find-time-zone
  1790. (icalendar--get-event-property-attributes
  1791. e 'DTSTART)
  1792. zone-map))
  1793. (dtstart-dec (icalendar--decode-isodatetime dtstart nil
  1794. dtstart-zone))
  1795. (start-d (icalendar--datetime-to-diary-date
  1796. dtstart-dec))
  1797. (start-t (icalendar--datetime-to-colontime dtstart-dec))
  1798. (dtend (icalendar--get-event-property e 'DTEND))
  1799. (dtend-zone (icalendar--find-time-zone
  1800. (icalendar--get-event-property-attributes
  1801. e 'DTEND)
  1802. zone-map))
  1803. (dtend-dec (icalendar--decode-isodatetime dtend
  1804. nil dtend-zone))
  1805. (dtend-1-dec (icalendar--decode-isodatetime dtend -1
  1806. dtend-zone))
  1807. end-d
  1808. end-1-d
  1809. end-t
  1810. (summary (icalendar--convert-string-for-import
  1811. (or (icalendar--get-event-property e 'SUMMARY)
  1812. "No summary")))
  1813. (rrule (icalendar--get-event-property e 'RRULE))
  1814. (rdate (icalendar--get-event-property e 'RDATE))
  1815. (duration (icalendar--get-event-property e 'DURATION)))
  1816. (icalendar--dmsg "%s: `%s'" start-d summary)
  1817. ;; check whether start-time is missing
  1818. (if (and dtstart
  1819. (string=
  1820. (cadr (icalendar--get-event-property-attributes
  1821. e 'DTSTART))
  1822. "DATE"))
  1823. (setq start-t nil))
  1824. (when duration
  1825. (let ((dtend-dec-d (icalendar--add-decoded-times
  1826. dtstart-dec
  1827. (icalendar--decode-isoduration duration)))
  1828. (dtend-1-dec-d (icalendar--add-decoded-times
  1829. dtstart-dec
  1830. (icalendar--decode-isoduration duration
  1831. t))))
  1832. (if (and dtend-dec (not (eq dtend-dec dtend-dec-d)))
  1833. (message "Inconsistent endtime and duration for %s"
  1834. summary))
  1835. (setq dtend-dec dtend-dec-d)
  1836. (setq dtend-1-dec dtend-1-dec-d)))
  1837. (setq end-d (if dtend-dec
  1838. (icalendar--datetime-to-diary-date dtend-dec)
  1839. start-d))
  1840. (setq end-1-d (if dtend-1-dec
  1841. (icalendar--datetime-to-diary-date dtend-1-dec)
  1842. start-d))
  1843. (setq end-t (if (and
  1844. dtend-dec
  1845. (not (string=
  1846. (cadr
  1847. (icalendar--get-event-property-attributes
  1848. e 'DTEND))
  1849. "DATE")))
  1850. (icalendar--datetime-to-colontime dtend-dec)
  1851. start-t))
  1852. (icalendar--dmsg "start-d: %s, end-d: %s" start-d end-d)
  1853. (cond
  1854. ;; recurring event
  1855. (rrule
  1856. (setq diary-string
  1857. (icalendar--convert-recurring-to-diary e dtstart-dec start-t
  1858. end-t))
  1859. (setq event-ok t))
  1860. (rdate
  1861. (icalendar--dmsg "rdate event")
  1862. (setq diary-string "")
  1863. (mapc (lambda (datestring)
  1864. (setq diary-string
  1865. (concat diary-string
  1866. (format "......"))))
  1867. (icalendar--split-value rdate)))
  1868. ;; non-recurring event
  1869. ;; all-day event
  1870. ((not (string= start-d end-d))
  1871. (setq diary-string
  1872. (icalendar--convert-non-recurring-all-day-to-diary
  1873. e start-d end-1-d))
  1874. (setq event-ok t))
  1875. ;; not all-day
  1876. ((and start-t (or (not end-t)
  1877. (not (string= start-t end-t))))
  1878. (setq diary-string
  1879. (icalendar--convert-non-recurring-not-all-day-to-diary
  1880. e dtstart-dec dtend-dec start-t end-t))
  1881. (setq event-ok t))
  1882. ;; all-day event
  1883. (t
  1884. (icalendar--dmsg "all day event")
  1885. (setq diary-string (icalendar--datetime-to-diary-date
  1886. dtstart-dec "/"))
  1887. (setq event-ok t)))
  1888. ;; add all other elements unless the user doesn't want to have
  1889. ;; them
  1890. (if event-ok
  1891. (progn
  1892. (setq diary-string
  1893. (concat diary-string " "
  1894. (icalendar--format-ical-event e)))
  1895. (if do-not-ask (setq summary nil))
  1896. ;; add entry to diary and store actual name of diary
  1897. ;; file (in case it was nil)
  1898. (setq diary-file
  1899. (icalendar--add-diary-entry diary-string diary-file
  1900. non-marking summary)))
  1901. ;; event was not ok
  1902. (setq found-error t)
  1903. (setq error-string
  1904. (format "%s\nCannot handle this event:%s"
  1905. error-string e))))
  1906. ;; FIXME: inform user about ignored event properties
  1907. ;; handle errors
  1908. (error
  1909. (message "Ignoring event \"%s\"" e)
  1910. (setq found-error t)
  1911. (setq error-string (format "%s\n%s\nCannot handle this event: %s"
  1912. error-val error-string e))
  1913. (message "%s" error-string))))
  1914. ;; insert final newline
  1915. (if diary-file
  1916. (let ((b (find-buffer-visiting diary-file)))
  1917. (when b
  1918. (save-current-buffer
  1919. (set-buffer b)
  1920. (goto-char (point-max))
  1921. (insert "\n")))))
  1922. (if found-error
  1923. (save-current-buffer
  1924. (set-buffer (get-buffer-create "*icalendar-errors*"))
  1925. (erase-buffer)
  1926. (insert error-string)))
  1927. (message "Converting iCalendar...done")
  1928. found-error))
  1929. ;; subroutines for importing
  1930. (defun icalendar--convert-recurring-to-diary (e dtstart-dec start-t end-t)
  1931. "Convert recurring iCalendar event E to diary format.
  1932. DTSTART-DEC is the DTSTART property of E.
  1933. START-T is the event's start time in diary format.
  1934. END-T is the event's end time in diary format."
  1935. (icalendar--dmsg "recurring event")
  1936. (let* ((rrule (icalendar--get-event-property e 'RRULE))
  1937. (rrule-props (icalendar--split-value rrule))
  1938. (frequency (cadr (assoc 'FREQ rrule-props)))
  1939. (until (cadr (assoc 'UNTIL rrule-props)))
  1940. (count (cadr (assoc 'COUNT rrule-props)))
  1941. (interval (read (or (cadr (assoc 'INTERVAL rrule-props)) "1")))
  1942. (dtstart-conv (icalendar--datetime-to-diary-date dtstart-dec))
  1943. (until-conv (icalendar--datetime-to-diary-date
  1944. (icalendar--decode-isodatetime until)))
  1945. (until-1-conv (icalendar--datetime-to-diary-date
  1946. (icalendar--decode-isodatetime until -1)))
  1947. (result ""))
  1948. ;; FIXME FIXME interval!!!!!!!!!!!!!
  1949. (when count
  1950. (if until
  1951. (message "Must not have UNTIL and COUNT -- ignoring COUNT element!")
  1952. (let ((until-1 0))
  1953. (cond ((string-equal frequency "DAILY")
  1954. (setq until (icalendar--add-decoded-times
  1955. dtstart-dec
  1956. (list 0 0 0 (* (read count) interval) 0 0)))
  1957. (setq until-1 (icalendar--add-decoded-times
  1958. dtstart-dec
  1959. (list 0 0 0 (* (- (read count) 1) interval)
  1960. 0 0)))
  1961. )
  1962. ((string-equal frequency "WEEKLY")
  1963. (setq until (icalendar--add-decoded-times
  1964. dtstart-dec
  1965. (list 0 0 0 (* (read count) 7 interval) 0 0)))
  1966. (setq until-1 (icalendar--add-decoded-times
  1967. dtstart-dec
  1968. (list 0 0 0 (* (- (read count) 1) 7
  1969. interval) 0 0)))
  1970. )
  1971. ((string-equal frequency "MONTHLY")
  1972. (setq until (icalendar--add-decoded-times
  1973. dtstart-dec (list 0 0 0 0 (* (- (read count) 1)
  1974. interval) 0)))
  1975. (setq until-1 (icalendar--add-decoded-times
  1976. dtstart-dec (list 0 0 0 0 (* (- (read count) 1)
  1977. interval) 0)))
  1978. )
  1979. ((string-equal frequency "YEARLY")
  1980. (setq until (icalendar--add-decoded-times
  1981. dtstart-dec (list 0 0 0 0 0 (* (- (read count) 1)
  1982. interval))))
  1983. (setq until-1 (icalendar--add-decoded-times
  1984. dtstart-dec
  1985. (list 0 0 0 0 0 (* (- (read count) 1)
  1986. interval))))
  1987. )
  1988. (t
  1989. (message "Cannot handle COUNT attribute for `%s' events."
  1990. frequency)))
  1991. (setq until-conv (icalendar--datetime-to-diary-date until))
  1992. (setq until-1-conv (icalendar--datetime-to-diary-date until-1))
  1993. ))
  1994. )
  1995. (cond ((string-equal frequency "WEEKLY")
  1996. (let* ((byday (cadr (assoc 'BYDAY rrule-props)))
  1997. (weekdays
  1998. (icalendar--get-weekday-numbers byday))
  1999. (weekday-clause
  2000. (when (> (length weekdays) 1)
  2001. (format "(memq (calendar-day-of-week date) '%s) "
  2002. weekdays))))
  2003. (if (not start-t)
  2004. (progn
  2005. ;; weekly and all-day
  2006. (icalendar--dmsg "weekly all-day")
  2007. (if until
  2008. (setq result
  2009. (format
  2010. (concat "%%%%(and "
  2011. "%s"
  2012. "(diary-block %s %s))")
  2013. (or weekday-clause
  2014. (format "(diary-cyclic %d %s) "
  2015. (* interval 7)
  2016. dtstart-conv))
  2017. dtstart-conv
  2018. (if count until-1-conv until-conv)
  2019. ))
  2020. (setq result
  2021. (format "%%%%(and %s(diary-cyclic %d %s))"
  2022. (or weekday-clause "")
  2023. (if weekday-clause 1 (* interval 7))
  2024. dtstart-conv))))
  2025. ;; weekly and not all-day
  2026. (icalendar--dmsg "weekly not-all-day")
  2027. (if until
  2028. (setq result
  2029. (format
  2030. (concat "%%%%(and "
  2031. "%s"
  2032. "(diary-block %s %s)) "
  2033. "%s%s%s")
  2034. (or weekday-clause
  2035. (format "(diary-cyclic %d %s) "
  2036. (* interval 7)
  2037. dtstart-conv))
  2038. dtstart-conv
  2039. until-conv
  2040. (or start-t "")
  2041. (if end-t "-" "") (or end-t "")))
  2042. ;; no limit
  2043. ;; FIXME!!!!
  2044. ;; DTSTART;VALUE=DATE-TIME:20030919T090000
  2045. ;; DTEND;VALUE=DATE-TIME:20030919T113000
  2046. (setq result
  2047. (format
  2048. "%%%%(and %s(diary-cyclic %d %s)) %s%s%s"
  2049. (or weekday-clause "")
  2050. (if weekday-clause 1 (* interval 7))
  2051. dtstart-conv
  2052. (or start-t "")
  2053. (if end-t "-" "") (or end-t "")))))))
  2054. ;; yearly
  2055. ((string-equal frequency "YEARLY")
  2056. (icalendar--dmsg "yearly")
  2057. (if until
  2058. (let ((day (nth 3 dtstart-dec))
  2059. (month (nth 4 dtstart-dec)))
  2060. (setq result (concat "%%(and (diary-date "
  2061. (cond ((eq (icalendar--date-style) 'iso)
  2062. (format "t %d %d" month day))
  2063. ((eq (icalendar--date-style) 'european)
  2064. (format "%d %d t" day month))
  2065. ((eq (icalendar--date-style) 'american)
  2066. (format "%d %d t" month day)))
  2067. ") (diary-block "
  2068. dtstart-conv
  2069. " "
  2070. until-conv
  2071. ")) "
  2072. (or start-t "")
  2073. (if end-t "-" "")
  2074. (or end-t ""))))
  2075. (setq result (format
  2076. "%%%%(and (diary-anniversary %s)) %s%s%s"
  2077. dtstart-conv
  2078. (or start-t "")
  2079. (if end-t "-" "") (or end-t "")))))
  2080. ;; monthly
  2081. ((string-equal frequency "MONTHLY")
  2082. (icalendar--dmsg "monthly")
  2083. (setq result
  2084. (format
  2085. "%%%%(and (diary-date %s) (diary-block %s %s)) %s%s%s"
  2086. (let ((day (nth 3 dtstart-dec)))
  2087. (cond ((eq (icalendar--date-style) 'iso)
  2088. (format "t t %d" day))
  2089. ((eq (icalendar--date-style) 'european)
  2090. (format "%d t t" day))
  2091. ((eq (icalendar--date-style) 'american)
  2092. (format "t %d t" day))))
  2093. dtstart-conv
  2094. (if until
  2095. until-conv
  2096. (if (eq (icalendar--date-style) 'iso) "9999 1 1" "1 1 9999")) ;; FIXME: should be unlimited
  2097. (or start-t "")
  2098. (if end-t "-" "") (or end-t ""))))
  2099. ;; daily
  2100. ((and (string-equal frequency "DAILY"))
  2101. (if until
  2102. (setq result
  2103. (format
  2104. (concat "%%%%(and (diary-cyclic %s %s) "
  2105. "(diary-block %s %s)) %s%s%s")
  2106. interval dtstart-conv dtstart-conv
  2107. (if count until-1-conv until-conv)
  2108. (or start-t "")
  2109. (if end-t "-" "") (or end-t "")))
  2110. (setq result
  2111. (format
  2112. "%%%%(and (diary-cyclic %s %s)) %s%s%s"
  2113. interval
  2114. dtstart-conv
  2115. (or start-t "")
  2116. (if end-t "-" "") (or end-t ""))))))
  2117. ;; Handle exceptions from recurrence rules
  2118. (let ((ex-dates (icalendar--get-event-properties e 'EXDATE)))
  2119. (while ex-dates
  2120. (let* ((ex-start (icalendar--decode-isodatetime
  2121. (car ex-dates)))
  2122. (ex-d (icalendar--datetime-to-diary-date
  2123. ex-start)))
  2124. (setq result
  2125. (icalendar--rris "^%%(\\(and \\)?"
  2126. (format
  2127. "%%%%(and (not (diary-date %s)) "
  2128. ex-d)
  2129. result)))
  2130. (setq ex-dates (cdr ex-dates))))
  2131. ;; FIXME: exception rules are not recognized
  2132. (if (icalendar--get-event-property e 'EXRULE)
  2133. (setq result
  2134. (concat result
  2135. "\n Exception rules: "
  2136. (icalendar--get-event-properties
  2137. e 'EXRULE))))
  2138. result))
  2139. (defun icalendar--convert-non-recurring-all-day-to-diary (event start-d end-d)
  2140. "Convert non-recurring iCalendar EVENT to diary format.
  2141. DTSTART is the decoded DTSTART property of E.
  2142. Argument START-D gives the first day.
  2143. Argument END-D gives the last day."
  2144. (icalendar--dmsg "non-recurring all-day event")
  2145. (format "%%%%(and (diary-block %s %s))" start-d end-d))
  2146. (defun icalendar--convert-non-recurring-not-all-day-to-diary (event dtstart-dec
  2147. dtend-dec
  2148. start-t
  2149. end-t)
  2150. "Convert recurring icalendar EVENT to diary format.
  2151. DTSTART-DEC is the decoded DTSTART property of E.
  2152. DTEND-DEC is the decoded DTEND property of E.
  2153. START-T is the event's start time in diary format.
  2154. END-T is the event's end time in diary format."
  2155. (icalendar--dmsg "not all day event")
  2156. (cond (end-t
  2157. (format "%s %s-%s"
  2158. (icalendar--datetime-to-diary-date
  2159. dtstart-dec "/")
  2160. start-t end-t))
  2161. (t
  2162. (format "%s %s"
  2163. (icalendar--datetime-to-diary-date
  2164. dtstart-dec "/")
  2165. start-t))))
  2166. (defun icalendar--add-diary-entry (string diary-file non-marking
  2167. &optional summary)
  2168. "Add STRING to the diary file DIARY-FILE.
  2169. STRING must be a properly formatted valid diary entry. NON-MARKING
  2170. determines whether diary events are created as non-marking. If
  2171. SUMMARY is not nil it must be a string that gives the summary of the
  2172. entry. In this case the user will be asked whether he wants to insert
  2173. the entry."
  2174. (when (or (not summary)
  2175. (y-or-n-p (format "Add appointment for `%s' to diary? "
  2176. summary)))
  2177. (when summary
  2178. (setq non-marking
  2179. (y-or-n-p (format "Make appointment non-marking? "))))
  2180. (save-window-excursion
  2181. (unless diary-file
  2182. (setq diary-file
  2183. (read-file-name "Add appointment to this diary file: ")))
  2184. ;; Note: diary-make-entry will add a trailing blank char.... :(
  2185. (funcall (if (fboundp 'diary-make-entry)
  2186. 'diary-make-entry
  2187. 'make-diary-entry)
  2188. string non-marking diary-file)))
  2189. ;; Würgaround to remove the trailing blank char
  2190. (with-current-buffer (find-file diary-file)
  2191. (goto-char (point-max))
  2192. (if (= (char-before) ? )
  2193. (delete-char -1)))
  2194. ;; return diary-file in case it has been changed interactively
  2195. diary-file)
  2196. ;; ======================================================================
  2197. ;; Examples
  2198. ;; ======================================================================
  2199. (defun icalendar-import-format-sample (event)
  2200. "Example function for formatting an iCalendar EVENT."
  2201. (format (concat "SUMMARY=`%s' DESCRIPTION=`%s' LOCATION=`%s' ORGANIZER=`%s' "
  2202. "STATUS=`%s' URL=`%s' CLASS=`%s'")
  2203. (or (icalendar--get-event-property event 'SUMMARY) "")
  2204. (or (icalendar--get-event-property event 'DESCRIPTION) "")
  2205. (or (icalendar--get-event-property event 'LOCATION) "")
  2206. (or (icalendar--get-event-property event 'ORGANIZER) "")
  2207. (or (icalendar--get-event-property event 'STATUS) "")
  2208. (or (icalendar--get-event-property event 'URL) "")
  2209. (or (icalendar--get-event-property event 'CLASS) "")))
  2210. (provide 'icalendar)
  2211. ;;; icalendar.el ends here