├── .gitignore
├── License.txt
├── Readme.org
├── doc
├── org-caldav.org
└── org-caldav.texi
├── org-caldav-tests.el
└── org-caldav.el
/.gitignore:
--------------------------------------------------------------------------------
1 | *.elc
2 |
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | {one line to give the program's name and a brief idea of what it does.}
635 | Copyright (C) {year} {name of author}
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | {project} Copyright (C) {year} {fullname}
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/Readme.org:
--------------------------------------------------------------------------------
1 | #+TITLE: org-caldav
2 |
3 | Caldav sync for Emacs Orgmode
4 |
5 | For documentation, see the manual at [[file:doc/org-caldav.org]]. The
6 | manual is also available at
7 | [[https://jackkamm.neocities.org/org-caldav-manual]].
8 |
--------------------------------------------------------------------------------
/doc/org-caldav.org:
--------------------------------------------------------------------------------
1 | #+TITLE: org-caldav
2 |
3 | #+TEXINFO_DIR_CATEGORY: Emacs
4 | #+TEXINFO_DIR_TITLE: Org-caldav: (org-caldav).
5 | #+TEXINFO_DIR_DESC: Caldav sync for Emacs Orgmode
6 |
7 | Caldav sync for Emacs Orgmode
8 |
9 | * Prerequisites
10 |
11 | - Emacs >= 26.3
12 | - Org >= 9.1
13 | - [[#caldav-servers][Compatible CalDav server]]
14 |
15 | *IMPORTANT*: Before using this code, please make sure you have backups
16 | of your precious Org files. Also, it is recommended to create a new,
17 | empty calendar on your server for using this package.
18 |
19 | *ALSO IMPORTANT*: When using this package, possibly all Org entries will
20 | get an UID property (see the docstring of ~org-icalendar-store-UID~ for
21 | further details). If you don't want this, then /do not use this
22 | package/; there is just no way around that. It is the only reliable way
23 | to uniquely identify Org entries.
24 |
25 | * Basic setup
26 | :PROPERTIES:
27 | :CUSTOM_ID: setup
28 | :END:
29 |
30 | At a minimum, set ~org-caldav-url~, ~org-caldav-calendar-id~,
31 | ~org-caldav-inbox~, ~org-caldav-files~, and ~org-icalendar-timezone~
32 | ([[#required-configs][Required configuration settings]]).
33 |
34 | ** Minimal example
35 |
36 | This example configures a single Org file =/path/to/inbox.org= to sync
37 | with a single calendar (with ID =CALENDAR-ID=) located on a Nextcloud
38 | server at =NEXTCLOUD-SERVER-URL=.
39 |
40 | #+begin_src emacs-lisp
41 | (require 'org-caldav)
42 |
43 | ;; URL of the caldav server
44 | (setq org-caldav-url "https://NEXTCLOUD-SERVER-URL/remote.php/dav/calendars/USERID")
45 |
46 | ;; calendar ID on server
47 | (setq org-caldav-calendar-id "CALENDAR-ID")
48 |
49 | ;; Org filename where new entries from calendar stored
50 | (setq org-caldav-inbox "/path/to/inbox.org")
51 |
52 | ;; Additional Org files to check for calendar events
53 | (setq org-caldav-files nil)
54 |
55 | ;; Usually a good idea to set the timezone manually
56 | (setq org-icalendar-timezone "Europe/Berlin")
57 | #+end_src
58 |
59 | ** Required configuration settings
60 | :PROPERTIES:
61 | :CUSTOM_ID: required-configs
62 | :END:
63 |
64 | - Set ~org-caldav-url~ to the base address of your CalDAV server:
65 |
66 | - Owncloud/Nextcloud (9.x and above):
67 | https://OWNCLOUD-SERVER-URL/remote.php/dav/calendars/USERID
68 | - Google: Set to symbol ~'google~. See [[#gcal-sync][Syncing to Google Calendar]]
69 | for additional required setup.
70 |
71 | - Set ~org-caldav-calendar-id~ to the calendar ID of your new calendar:
72 |
73 | - Own/NextCloud: Click on that little symbol next to the calendar name
74 | and inspect the link of the calendar; the last element of the shown
75 | path is the calendar-id. This should /usually/ be the same as the
76 | name of the calendar, but not necessarily: Owncloud might replace
77 | certain characters (upper to lowercase, for instance), or it might
78 | even be entirely different if the calendar was created by another
79 | CalDAV application.
80 | - Google: Click on 'calendar settings' and the id will be shown next
81 | to "Calendar Address". It is of the form
82 | ~ID@group.calendar.google.com~. Do /not/ omit the domain!
83 |
84 | - Set ~org-caldav-inbox~ to an org filename where new entries from the
85 | calendar should be stored. Just to be safe, it's a good idea to use
86 | an empty, dedicated Org file for that.
87 |
88 | - Set ~org-caldav-files~ to the list of org files you would like to
89 | sync. The above ~org-caldav-inbox~ will be automatically added, so you
90 | don't have to add it here.
91 |
92 | - It is usually a good idea to manually set ~org-icalendar-timezone~ to
93 | the timezone of your remote calendar. It should be a simple string
94 | like "Europe/Berlin". If that doesn't work and your events are
95 | shifted by a few hours, try the setting "UTC" (the SOGo calendar
96 | server seems to need this).
97 |
98 | * Usage
99 |
100 | First, create a calendar on the CalDav server, and configure which Org
101 | files to sync it with (see [[#setup][Basic setup]]).
102 |
103 | Then, do:
104 |
105 | ~M-x org-caldav-sync~
106 |
107 | to sync between Org and CalDav.
108 |
109 | You will be prompted to manually enter the username/password on each
110 | sync; see [[#authinfo][Storing authentication information]] on how to save the
111 | password and avoid manual entry.
112 |
113 | If you have many calendar items, the first sync can easily take
114 | several minutes, especially if using a slow CalDav implementation like
115 | Google's. If you have to abort the initial sync for some reason, just
116 | start ~org-caldav-sync~ again in the same Emacs session and you should
117 | get asked if you'd like to resume. Likewise for any errors --
118 | especially when using Google Calendar, it is not unusual to get stuff
119 | like '409' errors during the initial sync. Just run ~org-caldav-sync~
120 | again until all events are uploaded.
121 |
122 | * Advanced configuration
123 | :PROPERTIES:
124 | :CUSTOM_ID: advanced-config
125 | :END:
126 |
127 | Before reading this section, first consult the section on [[#setup][Basic setup]]
128 | (and in particular [[#required-configs][Required configuration settings]]).
129 |
130 | Additional, advanced configuration options are listed below in this
131 | section.
132 |
133 | Note that org-caldav uses [[https://orgmode.org/manual/iCalendar-Export.html][ox-icalendar.el]] to export from Org to
134 | iCalendar, so it's worth checking the options there as well. For
135 | example, use ~org-icalendar-alarm-time~ to add a reminder to your
136 | entries.
137 |
138 | ** Sync direction (one-way sync)
139 |
140 | By default, org-caldav does two-way syncing, that means it does not
141 | matter where and how you change an entry. You can also move Org
142 | entries freely from one file to another, as long as they are all
143 | listed in ~org-caldav-files~.
144 |
145 | To do one-way sync only, set ~org-caldav-sync-direction~ to
146 | ~'org->cal~ or ~'cal->org~, depending on which direction you'd like to
147 | have. If you choose ~'org->cal~, then ~org-caldav-inbox~ won't matter
148 | and can be ~nil~. Likewise, if you choose ~'cal->org~, then
149 | ~org-caldav-files~ will be ignored and only the calendar will be
150 | imported into the inbox.
151 |
152 | WARNING: It is NOT safe to switch the same calendar between 1-way and
153 | 2-way sync modes. Doing so may cause unexpected behavior, such as
154 | deleting all events in the calendar.
155 |
156 | ** Filtering entries
157 | :PROPERTIES:
158 | :CUSTOM_ID: filter-entries
159 | :END:
160 |
161 | There are several possibilities to choose which entries should be
162 | synced and which not:
163 |
164 | - If you only want to sync manually marked entries, use
165 | ~org-caldav-select-tags~, which is directly mapped to
166 | ~org-export-select-tags~, so see its doc-string on how it works.
167 |
168 | - If you want to exclude certain tags, use ~org-caldav-exclude-tags~,
169 | which is mapped to ~org-icalendar-exclude~ tags.
170 |
171 | - If you want more fine grained control, use
172 | ~org-caldav-skip-conditions~. The syntax of the conditions is
173 | described in the doc-string of ~org-agenda-skip-if~.
174 |
175 | - In case you just want to keep your remote calendar clean, set
176 | ~org-caldav-days-in-past~ to the number of days you want to keep in
177 | the past on the remote calendar. This does not affect your org files,
178 | it works just as a filter for entries older than N days.
179 |
180 | Note however that the normal ~org-agenda-skip-function(-global)~ will
181 | *not* have any effect on the icalendar exporter (this used to be the
182 | case, but changed with the new exporters).
183 |
184 | ** Syncing deletions
185 |
186 | If you delete entries in your Org files, the corresponding iCalendar
187 | entries will by default get deleted. You can change that behavior with
188 | ~org-caldav-delete-calendar-entries~ to never delete, or to ask before
189 | deletion.
190 |
191 | You must be careful to not simply remove previously synced files from
192 | ~org-caldav-files~, as org-caldav would view all the entries from those
193 | files as deleted and hence by default also delete them from the
194 | calendar. However, org-caldav should be able to detect this situation
195 | and warn you with the message 'Previously synced file(s) are missing',
196 | asking you whether to continue nonetheless.
197 |
198 | If you delete events in your calendar, you will by default get asked
199 | if you'd like to delete the corresponding Org event. You can change
200 | that behavior through ~org-caldav-delete-org-entries~.
201 |
202 | If you answer a deletion request with "no", the event should get
203 | re-synced to the calendar next time you call ~org-caldav-sync~.
204 |
205 | ** Storing authentication information in authinfo/netrc
206 | :PROPERTIES:
207 | :CUSTOM_ID: authinfo
208 | :END:
209 |
210 | If you don't want to enter your user/password every time, you can
211 | store it permanently in an authinfo file. In Emacs, the auth-source
212 | package takes care of that, but the syntax for https authentication is
213 | a bit peculiar. You have to use a line like the following
214 |
215 | #+begin_example
216 | machine www.google.com:443 port https login username password secret
217 | #+end_example
218 |
219 | Note that you have to specify the port number in the URL and /also/
220 | specify 'https' for the port. This is not a bug. For more information,
221 | see (info "auth"), especially section "Help for users".
222 |
223 | Since you are storing your password in a file you should encrypt it
224 | using GnuPG. Emacs will prompt you for a decryption key when it tries
225 | to read the file.
226 |
227 | ** Syncing with more than one calendar
228 | :PROPERTIES:
229 | :CUSTOM_ID: sync-multiple
230 | :END:
231 |
232 | This can be done by setting the variable ~org-caldav-calendars~. It
233 | should be a list of plists (a 'plist' is simply a list with alternating
234 | :key's and values). Through these plists, you can override the global
235 | values of variables like ~org-caldav-calendar-id~, and calling
236 | ~org-caldav-sync~ will go through these plists in order.
237 |
238 | Example:
239 |
240 | #+begin_src emacs-lisp
241 | (setq org-caldav-calendars
242 | '((:calendar-id "work@whatever" :files ("~/org/work.org")
243 | :inbox "~/org/fromwork.org")
244 | (:calendar-id "stuff@mystuff"
245 | :files ("~/org/sports.org" "~/org/play.org")
246 | :skip-conditions (regexp "soccer")
247 | :inbox "~/org/fromstuff.org")) )
248 | #+end_src
249 |
250 | This means that you have two calendars with IDs "work@whatever" and
251 | "stuff@mystuff". Both will be accessed through the global value of
252 | org-caldav-url, since the key :url isn't specified. The calendar
253 | "work@whatever" will be synced with the file 'work.org' and inbox
254 | 'fromwork.org', while "stuff@mystuff" with 'sports.org' and
255 | 'play.org', /unless/ there's the string 'soccer' in the heading, and
256 | and inbox is 'fromstuff.org'. See the doc-string of
257 | ~org-caldav-calendars~ for more details on which keys you can use.
258 |
259 | ** Customizing the inbox
260 | :PROPERTIES:
261 | :CUSTOM_ID: custom-inbox
262 | :END:
263 |
264 | See the doc-string of ~org-caldav-inbox~ if you want more flexibility in
265 | where new items should be put. Instead of simply providing a file, you
266 | can also choose an existing entry or headline, or put the entry under a
267 | datetree.
268 |
269 | ** Syncing TODOs between Org and CalDav
270 | :PROPERTIES:
271 | :CUSTOM_ID: sync-todo
272 | :END:
273 |
274 | This feature is relatively new and less well tested, so it is
275 | recommended to have backups before using it. It has been tested on
276 | nextcloud and radicale.
277 |
278 | To sync TODO's between Org and the CalDav server, do:
279 |
280 | #+begin_src emacs-lisp
281 | (setq org-icalendar-include-todo 'all
282 | org-caldav-sync-todo t)
283 | #+end_src
284 |
285 | The first instructs the Org exporter to include TODOs; the second
286 | tells org-caldav to import icalendar VTODOs as Org TODOs.
287 |
288 | Other customizations to consider (see their documentation for more
289 | details):
290 |
291 | - ~org-caldav-todo-priority~ to control how priority levels map between
292 | iCalendar and Org.
293 | - ~org-caldav-todo-percent-states~ to convert between
294 | ~org-todo-keywords~ and iCalendar's percent-complete property.
295 | - ~org-caldav-todo-deadline-schedule-warning-days~ to auto-create
296 | SCHEDULED timestamps when a DEADLINE is present (this might be useful
297 | for users of the OpenTasks app).
298 |
299 | If you find that some Org entries get an extra tag which equals their
300 | CATEGORY, this might be caused by the CATEGORY being exported to
301 | iCalendar, and then re-imported to Org as a tag. In that case, do
302 |
303 | #+begin_src emacs-lisp
304 | (setq org-icalendar-categories '(local-tags))
305 | #+end_src
306 |
307 | to prevent the CATEGORY from being exported to iCalendar. This problem
308 | only seems to affect some CalDav servers: in particular, NextCloud
309 | is affected, but Radicale does not seem to experience this problem.
310 |
311 | ** Behavior of recurring TODO deadlines without a start time
312 | :PROPERTIES:
313 | :CUSTOM_ID: recur-deadline
314 | :END:
315 |
316 | Technically, the [[https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.2.4][iCalendar spec]] requires repeating events and todos
317 | (i.e. having an ~RRULE~ property) to have a starting time (~DTSTART~
318 | in iCalendar, equivalent to ~SCHEDULED~ in Org TODOs). This means
319 | that, a TODO with a repeating ~DEADLINE~ but without a ~SCHEDULED~
320 | property, such as below, is not allowed by the iCalendar spec:
321 |
322 | #+begin_src org
323 | ,* TODO An example todo with a repeating deadline and no start time
324 | DEADLINE: <2024-09-15 Sun +1w>
325 | #+end_src
326 |
327 | This is a clear shortcoming of the iCalendar spec, because it /does/
328 | allow tasks to have a standalone deadline without a starting time, but
329 | /doesn't/ allow such tasks to repeat.
330 |
331 | By default, ~ox-icalendar~ follows the iCalendar spec, and when
332 | exporting a TODO with a repeating ~DEADLINE~ but no ~SCHEDULED~
333 | timestamp, will add a start time based on
334 | ~org-deadline-warning-days~. On future syncs, this start time will be
335 | inserted into Org as a ~SCHEDULED~ timestamp.
336 |
337 | However, in practice, many iCalendar implementations ignore this
338 | limitation, and allow Todos with ~DEADLINE~ (~DUE~) times to have
339 | repeaters (~RRULE~), even if they are missing ~SCHEDULED~ (~DTSTART~)
340 | times. If your CalDav server allows this, then you may set the
341 | variable ~org-icalendar-todo-unscheduled-start~ to ~nil~. This will
342 | prevent ~ox-icalendar~ from adding a start time to such TODOs, thus
343 | preventing the ~SCHEDULED~ timestamp from being inserted on future
344 | syncs.
345 |
346 | See [[#recur-event-todo][Repeating events and todos]] for more details about how org-caldav
347 | handles repeating events and todos.
348 |
349 | * Compatible CalDav servers
350 | :PROPERTIES:
351 | :CUSTOM_ID: caldav-servers
352 | :END:
353 |
354 | - *Owncloud* and *Nextcloud*: Regularly tested.
355 |
356 | - *Radicale* and *Baikal*: Works. If you get problems with 'Digest'
357 | authentication, switch back to 'Basic' (make sure to use https,
358 | though!). If you get asked for password repeatedly, put it in
359 | ~.authinfo~ file ([[#authinfo][Storing authentication information]]).
360 |
361 | - *SOGo* and *Kolab*: Reported to be working
362 | (https://docs.kolab.org/client-configuration/emacs.html)
363 |
364 | - *Google Calendar*: Should work, but you need to register an
365 | application with the Google Developer Console for OAuth2
366 | authentication (see below), because Google explicitly forbids to put
367 | client id/secrets into open source software (see
368 | https://developers.google.com/terms, section 4b, paragraph 1). Instead
369 | of doing that though, I'd rather suggest you choose another service
370 | provider.
371 |
372 | ** Syncing to Google Calendar
373 | :PROPERTIES:
374 | :CUSTOM_ID: gcal-sync
375 | :END:
376 |
377 | NOTE: Using org-caldav with Google Calendar may be currently
378 | broken. See [[https://github.com/dengste/org-caldav/issues/284]]
379 |
380 | The CalDAV endpoint for Google Calendar requires OAuth2
381 | authentication. So first, you need to install the oauth2 library from
382 | GNU ELPA, and afterwards you need to acquire an application ID and
383 | secret from the Google Developer Console. For details on how to do
384 | this, follow the Google documentation at
385 |
386 | https://developers.google.com/google-apps/calendar/caldav/v2/guide#creating_your_client_id
387 |
388 | Put the client ID and secret into ~org-caldav-oauth2-client-id~ and
389 | ~org-caldav-oauth2-client-secret~, respectively. Then set
390 | ~org-caldav-url~ to the symbol ~'google~, and look up the
391 | ~org-caldav-calendar-id~ as described above.
392 |
393 | On first connection, the oauth2 library should redirect you to the
394 | Google OAuth2 authentication site. This requires a javascript enabled
395 | browser, so make sure that ~browse-url-browser-function~ is set to
396 | something like ~browse-url-firefox~ (the internal eww or w3m browsers
397 | will *not* work). After authentication, you will be given a key that
398 | you have to paste into the Emacs prompt. The oauth2 library will save
399 | this key in Emacs' secure plist store, which is encrypted with
400 | GnuPG. If you have not yet used a secure plist store, you will be
401 | asked for its encryption passphrase. In the future, you should only
402 | need to enter that passphrase again to connect with Google Calendar.
403 |
404 | By default, plstore will *not* cache your entered password, so it will
405 | possibly ask you *many* times. To activate caching, use
406 |
407 | #+begin_src emacs-lisp
408 | (setq plstore-cache-passphrase-for-symmetric-encryption t)
409 | #+end_src
410 |
411 | * Implementation details
412 |
413 | ** Org and the iCalendar format
414 |
415 | An Org entry can store much more information than an iCalendar entry,
416 | so there is no one-to-one correspondence between the two formats which
417 | makes syncing a bit difficult.
418 |
419 | - Org to iCalendar
420 |
421 | This package uses the org-icalendar package to do the export to the
422 | iCalendar format (.ics files). By default, it uses the title of the
423 | Org entry as SUMMARY and puts the entry's body into DESCRIPTION,
424 | snipping stuff like properties and timestamps (you can override that
425 | with properties of the same name, but IMO it makes stuff just more
426 | complicated). The variable ~org-icalendar-include-body~
427 | denotes how many characters from the body should be included as
428 | DESCRIPTION (by default all characters are included).
429 |
430 | - iCalendar to Org
431 |
432 | If you create a new iCalendar entry in your calendar, you'll get an
433 | Org entry with SUMMARY as heading, DESCRIPTION as body and the
434 | timestamp. However, if you /change/ an existing entry in the calendar,
435 | things get more complicated and the variable
436 | ~org-caldav-sync-changes-to-org~ comes into play. Its default is the
437 | symbol "title-and-timestamp", which means that only the entry's
438 | heading is synced (with SUMMARY) and the timestamp gets updated, but
439 | /not/ the entry's body with DESCRIPTION. The simple reason is that
440 | you might loose data, since DESCRIPTION is rather limited in what it
441 | can store. Still, you can set the variable to the symbol "all", which
442 | will completely /replace/ an existing Org entry with the entry that
443 | gets generated from the calendar's event. You can also limit syncing
444 | to heading and/or timestamp only.
445 |
446 | To be extra safe, org-caldav will by default backup entries it
447 | changes. See the variable ~org-caldav-backup-file~ for details.
448 |
449 | - Org sexp entries
450 |
451 | A special case are sexp entries like
452 |
453 | #+begin_src org
454 | %%(diary-anniversary 2 2 1969) Foo's birthday
455 |
456 | ,* Regular meeting
457 | <%%(diary-float t 4 2)>
458 | #+end_src
459 |
460 | As you can see, they can appear in two different ways: plain by
461 | themselves, or inside an Org entry. If they are inside an Org entry,
462 | there's a good chance they will be exported (see below) and have an ID
463 | property, so they can be found by org-caldav. We can sync the title,
464 | but syncing the timestamp with the s-expression is just infeasible, so
465 | this will generate a sync error (which are /not/ critical; you'll just
466 | see them at the end of the sync, just so that you're aware that some
467 | stuff wasn't synced properly).
468 |
469 | However, sexp-entries are insanely flexible, and there are limits as
470 | to what the icalendar exporter will handle. For example, this here
471 |
472 | #+begin_src org
473 | ,** Regular event
474 | <%%(memq (calendar-day-of-week date) '(1 3 5))>
475 | #+end_src
476 |
477 | will not be exported at all.
478 |
479 | If the sexp entry is not inside an Org entry but stands by itself,
480 | they still will be exported, but they won't get an ID (since IDs are
481 | properties linked to Org entries). In practice, that means that you
482 | can delete and change them inside Org and this will be synced, but if
483 | you /change/ them in the /calendar/, this will /not/ get synced
484 | back. Org-caldav just cannot find those entries, so this will generate
485 | a one-time sync error instead (again: those are not critical, just
486 | FYI). If you don't want those entries to be exported at all, just set
487 | ~org-icalendar-include-sexps~ to nil.
488 |
489 | ** Conflict handling
490 |
491 | Now that's an easy one: Org always wins. That means, if you change an
492 | entry in Org /and/ in the calendar, the changes in the calendar will
493 | be lost. I might implement proper conflict handling some day, but
494 | don't hold your breath (patches are welcome, of course).
495 |
496 | ** Repeating events and todos
497 | :PROPERTIES:
498 | :CUSTOM_ID: recur-event-todo
499 | :END:
500 |
501 | Org-caldav has basic support for repeating events and todos. In
502 | particular, simple Org timestamp repeaters such as ~+3d~ or ~+1m~ can
503 | be succesfully sync'd bidirectionally.
504 |
505 | However, complex iCalender recurrences, such as "repeat on the 2nd
506 | Tuesday of each month until X date", are not supported.
507 |
508 | For Org TODOs with both ~SCHEDULED~ and ~DEADLINE~ timestamps, each of
509 | the timestamps must have the same repeater, otherwise the behavior is
510 | undefined.
511 |
512 | Furthermore, the behavior of Org TODOs with a repeating ~DEADLINE~
513 | timestamp, but no ~SCHEDULED~ timestamp, has some subtleties; see the
514 | configuration section: [[#recur-deadline][Behavior of recurring TODO deadlines without a start time]].
515 |
516 | ** How syncing happens (a.k.a. David's little CalDAV rant)
517 |
518 | (This is probably not interesting, so you can skip this.)
519 |
520 | CalDAV is a mess.
521 |
522 | First off, it is based on WebDAV, which has its own fair share of
523 | problems. The main design flaw of CalDAV however, is that UID and
524 | resource name (the "filename", if you want) are two different
525 | things. I know that there are reasons for that (not everything has a
526 | UID, like timezones, and you can put several events in one resource),
527 | but this is typical over-engineering to allow some marginal use cases
528 | pretty much no one needs. Another problem is that you have to do
529 | additional round-trips to get Etag and sequence number, which makes
530 | CalDAV pretty slow.
531 |
532 | Org-caldav takes the easy route: it assumes that every resource
533 | contains one event, and that UID and resource name are identical. In
534 | fact, Google's CalDAV interface even enforces the latter. And while
535 | Owncloud does not enforce it, at least it just does it if you create
536 | items in its web interface.
537 |
538 | However, the CalDAV standard does not demand this, so I guess there
539 | are servers out there with which org-caldav does not work. Patches
540 | welcome.
541 |
542 | Now, all this would be bad enough if it weren't for the sloppy server
543 | implementations which make implementing a CalDAV client a living hell
544 | and led to several rewrites of the code. Especially Google, the 500
545 | pound gorilla in the room, doesn't really care much for CalDAV. I
546 | guess they really like their own shiny REST-based calendar API better,
547 | and I can't blame them for that.
548 |
549 | * Tips, Tricks, and Troubleshooting
550 |
551 | ** Standalone import of ICS files
552 |
553 | org-caldav can also be used for standalone import of ICS files to Org.
554 |
555 | In particular, see ~org-caldav-import-ics-buffer-to-org~ to import
556 | iCalendar entries (e.g. from e-mail attachments) directly to your
557 | ~org-caldav-inbox~.
558 |
559 | Also, see ~org-caldav-convert-ics-to-datetree~ to convert an iCalendar
560 | file into an Org datetree in a separate buffer (use
561 | ~org-caldav-datetree-treetype~ to control the style of datetree).
562 |
563 | ** Storage of sync information and sync from different computers
564 |
565 | The current sync state is stored in a file ~org-caldav-SOMEID.el~ in
566 | the ~/.emacs.d directory. You can change the location through the
567 | variable ~org-caldav-save-directory~. SOMEID directly depends on the
568 | calendar id (it's a snipped MD5).
569 |
570 | If you sync your Org files across different machines and want to use
571 | org-caldav on all of them, don't forget to sync the org sync state,
572 | too. Probably your best bet is to set ~org-caldav-save-directory~ to the
573 | path you have your Org files in, so that it gets copied alongside with
574 | them.
575 |
576 | ** Starting from scratch
577 |
578 | If your sync state somehow gets broken, you can make a clean slate by
579 | doing
580 |
581 | #+begin_example
582 | C-u M-x org-caldav-delete-everything
583 | #+end_example
584 |
585 | The function has to be called with a prefix so that you don't call it
586 | by accident. This will delete everything in the calendar along with
587 | the current sync state. You can then call ~org-caldav-sync~ afterwards
588 | and it will completely put all Org events into the now empty
589 | calendar. Needless to say, don't do that if you have new events in
590 | your calendar which are not synced yet...
591 |
592 | Deleting many events can be slow, though; in that case, just delete
593 | the calendar and re-create it, delete the sync state file in
594 | ~/.emacs.d and restart Emacs.
595 |
596 | ** Timezone problems
597 |
598 | Timezone handling is plain horrible, and it seems every CalDAV server
599 | does it slightly differently, also using non-standard headers like
600 | X-WR-TIMEZONE. If you see items being shifted by a few hours, make
601 | really really sure you have properly set ~org-icalendar-timezone~, and
602 | that your calendar is configured to use the same one.
603 |
604 | If it still does not work, you can try setting ~org-icalendar-timezone~
605 | to the string "UTC". This will put all events using UTC times and the
606 | server should transpose the time to the timezone you have set in your
607 | calendar preferences. For some servers (like SOGo) this might work
608 | better than setting a "real" timezone.
609 |
610 | ** Troubleshooting
611 |
612 | If org-caldav reports a problem with the given URL, please
613 | triple-check that the URL is correct. It must point to a valid
614 | calendar on your CalDAV server.
615 |
616 | If the error is that the URL does not seem to accept DAV requests, you
617 | can additionally check with 'curl' by doing
618 |
619 | #+begin_src shell
620 | curl -D - -X OPTIONS --basic -u mylogin:mypassword URL
621 | #+end_src
622 |
623 | The output of this command must contain a 'DAV' header like this:
624 |
625 | #+begin_example
626 | DAV: 1, 3, extended-mkcol, access-control, ... etc. ...
627 | #+end_example
628 |
629 | By default, org-caldav will put all kinds of debug output into the
630 | buffer ~*org-caldav-debug*~. Look there if you're getting sync errors
631 | or if something plain doesn't work. If you're using an authinfo file
632 | and authentication doesn't work, set auth-info-debug to t and look in
633 | the ~*Messages*~ buffer. When you report a bug, please try to post the
634 | relevant portion of the ~*org-caldav-debug*~ buffer since it might be
635 | helpful to see what's going wrong. If Emacs throws an error, do
636 |
637 | #+begin_example
638 | M-x toggle-debug-on-error
639 | #+end_example
640 |
641 | and try to replicate the error to get a backtrace.
642 |
643 | You can also turn on excessive debugging by setting the variable
644 | ~org-caldav-debug-level~ to 2. This will also output the /contents/ of
645 | the events into the debug buffer. If you send such a buffer in a bug
646 | report, please make very sure you have removed personal information
647 | from those events.
648 |
649 | * Known Bugs
650 |
651 | - Syncing is currently pretty slow since everything is done
652 | synchronously.
653 |
654 | - Pretty much everything besides SUMMARY, DESCRIPTION, LOCATION and time
655 | is ignored in iCalendar.
656 |
657 |
--------------------------------------------------------------------------------
/doc/org-caldav.texi:
--------------------------------------------------------------------------------
1 | \input texinfo @c -*- texinfo -*-
2 | @c %**start of header
3 | @setfilename org-caldav.info
4 | @settitle org-caldav
5 | @documentencoding UTF-8
6 | @documentlanguage en
7 | @c %**end of header
8 |
9 | @dircategory Emacs
10 | @direntry
11 | * Org-caldav: (org-caldav). Caldav sync for Emacs Orgmode.
12 | @end direntry
13 |
14 | @finalout
15 | @titlepage
16 | @title org-caldav
17 | @author Jack Kamm
18 | @end titlepage
19 |
20 | @contents
21 |
22 | @ifnottex
23 | @node Top
24 | @top org-caldav
25 |
26 | Caldav sync for Emacs Orgmode
27 |
28 | @end ifnottex
29 |
30 | @menu
31 | * Prerequisites::
32 | * Basic setup::
33 | * Usage::
34 | * Advanced configuration::
35 | * Compatible CalDav servers::
36 | * Implementation details::
37 | * Tips, Tricks, and Troubleshooting: Tips Tricks and Troubleshooting.
38 | * Known Bugs::
39 |
40 | @detailmenu
41 | --- The Detailed Node Listing ---
42 |
43 | Basic setup
44 |
45 | * Minimal example::
46 | * Required configuration settings::
47 |
48 | Advanced configuration
49 |
50 | * Sync direction (one-way sync)::
51 | * Filtering entries::
52 | * Syncing deletions::
53 | * Storing authentication information in authinfo/netrc::
54 | * Syncing with more than one calendar::
55 | * Customizing the inbox::
56 | * Syncing TODOs between Org and CalDav::
57 | * Behavior of recurring TODO deadlines without a start time::
58 |
59 | Compatible CalDav servers
60 |
61 | * Syncing to Google Calendar::
62 |
63 | Implementation details
64 |
65 | * Org and the iCalendar format::
66 | * Conflict handling::
67 | * Repeating events and todos::
68 | * How syncing happens (a.k.a. David's little CalDAV rant): How syncing happens (aka David's little CalDAV rant).
69 |
70 | Tips, Tricks, and Troubleshooting
71 |
72 | * Standalone import of ICS files::
73 | * Storage of sync information and sync from different computers::
74 | * Starting from scratch::
75 | * Timezone problems::
76 | * Troubleshooting::
77 |
78 | @end detailmenu
79 | @end menu
80 |
81 | @node Prerequisites
82 | @chapter Prerequisites
83 |
84 | @itemize
85 | @item
86 | Emacs >= 26.3
87 | @item
88 | Org >= 9.1
89 | @item
90 | @ref{Compatible CalDav servers, , Compatible CalDav server}
91 | @end itemize
92 |
93 | @strong{IMPORTANT}: Before using this code, please make sure you have backups
94 | of your precious Org files. Also, it is recommended to create a new,
95 | empty calendar on your server for using this package.
96 |
97 | @strong{ALSO IMPORTANT}: When using this package, possibly all Org entries will
98 | get an UID property (see the docstring of @code{org-icalendar-store-UID} for
99 | further details). If you don't want this, then @emph{do not use this
100 | package}; there is just no way around that. It is the only reliable way
101 | to uniquely identify Org entries.
102 |
103 | @node Basic setup
104 | @chapter Basic setup
105 |
106 | At a minimum, set @code{org-caldav-url}, @code{org-caldav-calendar-id},
107 | @code{org-caldav-inbox}, @code{org-caldav-files}, and @code{org-icalendar-timezone}
108 | (@ref{Required configuration settings}).
109 |
110 | @menu
111 | * Minimal example::
112 | * Required configuration settings::
113 | @end menu
114 |
115 | @node Minimal example
116 | @section Minimal example
117 |
118 | This example configures a single Org file @samp{/path/to/inbox.org} to sync
119 | with a single calendar (with ID @samp{CALENDAR-ID}) located on a Nextcloud
120 | server at @samp{NEXTCLOUD-SERVER-URL}.
121 |
122 | @lisp
123 | (require 'org-caldav)
124 |
125 | ;; URL of the caldav server
126 | (setq org-caldav-url "https://NEXTCLOUD-SERVER-URL/remote.php/dav/calendars/USERID")
127 |
128 | ;; calendar ID on server
129 | (setq org-caldav-calendar-id "CALENDAR-ID")
130 |
131 | ;; Org filename where new entries from calendar stored
132 | (setq org-caldav-inbox "/path/to/inbox.org")
133 |
134 | ;; Additional Org files to check for calendar events
135 | (setq org-caldav-files nil)
136 |
137 | ;; Usually a good idea to set the timezone manually
138 | (setq org-icalendar-timezone "Europe/Berlin")
139 | @end lisp
140 |
141 | @node Required configuration settings
142 | @section Required configuration settings
143 |
144 | @itemize
145 | @item
146 | Set @code{org-caldav-url} to the base address of your CalDAV server:
147 |
148 | @itemize
149 | @item
150 | Owncloud/Nextcloud (9.x and above):
151 | @uref{https://OWNCLOUD-SERVER-URL/remote.php/dav/calendars/USERID}
152 | @item
153 | Google: Set to symbol @code{'google}. See @ref{Syncing to Google Calendar}
154 | for additional required setup.
155 | @end itemize
156 |
157 | @item
158 | Set @code{org-caldav-calendar-id} to the calendar ID of your new calendar:
159 |
160 | @itemize
161 | @item
162 | Own/NextCloud: Click on that little symbol next to the calendar name
163 | and inspect the link of the calendar; the last element of the shown
164 | path is the calendar-id. This should @emph{usually} be the same as the
165 | name of the calendar, but not necessarily: Owncloud might replace
166 | certain characters (upper to lowercase, for instance), or it might
167 | even be entirely different if the calendar was created by another
168 | CalDAV application.
169 | @item
170 | Google: Click on 'calendar settings' and the id will be shown next
171 | to "Calendar Address". It is of the form
172 | @code{ID@@group.calendar.google.com}. Do @emph{not} omit the domain!
173 | @end itemize
174 |
175 | @item
176 | Set @code{org-caldav-inbox} to an org filename where new entries from the
177 | calendar should be stored. Just to be safe, it's a good idea to use
178 | an empty, dedicated Org file for that.
179 |
180 | @item
181 | Set @code{org-caldav-files} to the list of org files you would like to
182 | sync. The above @code{org-caldav-inbox} will be automatically added, so you
183 | don't have to add it here.
184 |
185 | @item
186 | It is usually a good idea to manually set @code{org-icalendar-timezone} to
187 | the timezone of your remote calendar. It should be a simple string
188 | like "Europe/Berlin". If that doesn't work and your events are
189 | shifted by a few hours, try the setting "UTC" (the SOGo calendar
190 | server seems to need this).
191 | @end itemize
192 |
193 | @node Usage
194 | @chapter Usage
195 |
196 | First, create a calendar on the CalDav server, and configure which Org
197 | files to sync it with (see @ref{Basic setup}).
198 |
199 | Then, do:
200 |
201 | @code{M-x org-caldav-sync}
202 |
203 | to sync between Org and CalDav.
204 |
205 | You will be prompted to manually enter the username/password on each
206 | sync; see @ref{Storing authentication information in authinfo/netrc, , Storing authentication information} on how to save the
207 | password and avoid manual entry.
208 |
209 | If you have many calendar items, the first sync can easily take
210 | several minutes, especially if using a slow CalDav implementation like
211 | Google's. If you have to abort the initial sync for some reason, just
212 | start @code{org-caldav-sync} again in the same Emacs session and you should
213 | get asked if you'd like to resume. Likewise for any errors --
214 | especially when using Google Calendar, it is not unusual to get stuff
215 | like '409' errors during the initial sync. Just run @code{org-caldav-sync}
216 | again until all events are uploaded.
217 |
218 | @node Advanced configuration
219 | @chapter Advanced configuration
220 |
221 | Before reading this section, first consult the section on @ref{Basic setup}
222 | (and in particular @ref{Required configuration settings}).
223 |
224 | Additional, advanced configuration options are listed below in this
225 | section.
226 |
227 | Note that org-caldav uses @uref{https://orgmode.org/manual/iCalendar-Export.html, ox-icalendar.el} to export from Org to
228 | iCalendar, so it's worth checking the options there as well. For
229 | example, use @code{org-icalendar-alarm-time} to add a reminder to your
230 | entries.
231 |
232 | @menu
233 | * Sync direction (one-way sync)::
234 | * Filtering entries::
235 | * Syncing deletions::
236 | * Storing authentication information in authinfo/netrc::
237 | * Syncing with more than one calendar::
238 | * Customizing the inbox::
239 | * Syncing TODOs between Org and CalDav::
240 | * Behavior of recurring TODO deadlines without a start time::
241 | @end menu
242 |
243 | @node Sync direction (one-way sync)
244 | @section Sync direction (one-way sync)
245 |
246 | By default, org-caldav does two-way syncing, that means it does not
247 | matter where and how you change an entry. You can also move Org
248 | entries freely from one file to another, as long as they are all
249 | listed in @code{org-caldav-files}.
250 |
251 | To do one-way sync only, set @code{org-caldav-sync-direction} to
252 | @code{'org->cal} or @code{'cal->org}, depending on which direction you'd like to
253 | have. If you choose @code{'org->cal}, then @code{org-caldav-inbox} won't matter
254 | and can be @code{nil}. Likewise, if you choose @code{'cal->org}, then
255 | @code{org-caldav-files} will be ignored and only the calendar will be
256 | imported into the inbox.
257 |
258 | WARNING: It is NOT safe to switch the same calendar between 1-way and
259 | 2-way sync modes. Doing so may cause unexpected behavior, such as
260 | deleting all events in the calendar.
261 |
262 | @node Filtering entries
263 | @section Filtering entries
264 |
265 | There are several possibilities to choose which entries should be
266 | synced and which not:
267 |
268 | @itemize
269 | @item
270 | If you only want to sync manually marked entries, use
271 | @code{org-caldav-select-tags}, which is directly mapped to
272 | @code{org-export-select-tags}, so see its doc-string on how it works.
273 |
274 | @item
275 | If you want to exclude certain tags, use @code{org-caldav-exclude-tags},
276 | which is mapped to @code{org-icalendar-exclude} tags.
277 |
278 | @item
279 | If you want more fine grained control, use
280 | @code{org-caldav-skip-conditions}. The syntax of the conditions is
281 | described in the doc-string of @code{org-agenda-skip-if}.
282 |
283 | @item
284 | In case you just want to keep your remote calendar clean, set
285 | @code{org-caldav-days-in-past} to the number of days you want to keep in
286 | the past on the remote calendar. This does not affect your org files,
287 | it works just as a filter for entries older than N days.
288 | @end itemize
289 |
290 | Note however that the normal @code{org-agenda-skip-function(-global)} will
291 | @strong{not} have any effect on the icalendar exporter (this used to be the
292 | case, but changed with the new exporters).
293 |
294 | @node Syncing deletions
295 | @section Syncing deletions
296 |
297 | If you delete entries in your Org files, the corresponding iCalendar
298 | entries will by default get deleted. You can change that behavior with
299 | @code{org-caldav-delete-calendar-entries} to never delete, or to ask before
300 | deletion.
301 |
302 | You must be careful to not simply remove previously synced files from
303 | @code{org-caldav-files}, as org-caldav would view all the entries from those
304 | files as deleted and hence by default also delete them from the
305 | calendar. However, org-caldav should be able to detect this situation
306 | and warn you with the message 'Previously synced file(s) are missing',
307 | asking you whether to continue nonetheless.
308 |
309 | If you delete events in your calendar, you will by default get asked
310 | if you'd like to delete the corresponding Org event. You can change
311 | that behavior through @code{org-caldav-delete-org-entries}.
312 |
313 | If you answer a deletion request with "no", the event should get
314 | re-synced to the calendar next time you call @code{org-caldav-sync}.
315 |
316 | @node Storing authentication information in authinfo/netrc
317 | @section Storing authentication information in authinfo/netrc
318 |
319 | If you don't want to enter your user/password every time, you can
320 | store it permanently in an authinfo file. In Emacs, the auth-source
321 | package takes care of that, but the syntax for https authentication is
322 | a bit peculiar. You have to use a line like the following
323 |
324 | @example
325 | machine www.google.com:443 port https login username password secret
326 | @end example
327 |
328 | Note that you have to specify the port number in the URL and @emph{also}
329 | specify 'https' for the port. This is not a bug. For more information,
330 | see (info "auth"), especially section "Help for users".
331 |
332 | Since you are storing your password in a file you should encrypt it
333 | using GnuPG@. Emacs will prompt you for a decryption key when it tries
334 | to read the file.
335 |
336 | @node Syncing with more than one calendar
337 | @section Syncing with more than one calendar
338 |
339 | This can be done by setting the variable @code{org-caldav-calendars}. It
340 | should be a list of plists (a 'plist' is simply a list with alternating
341 | :key's and values). Through these plists, you can override the global
342 | values of variables like @code{org-caldav-calendar-id}, and calling
343 | @code{org-caldav-sync} will go through these plists in order.
344 |
345 | Example:
346 |
347 | @lisp
348 | (setq org-caldav-calendars
349 | '((:calendar-id "work@@whatever" :files ("~/org/work.org")
350 | :inbox "~/org/fromwork.org")
351 | (:calendar-id "stuff@@mystuff"
352 | :files ("~/org/sports.org" "~/org/play.org")
353 | :skip-conditions (regexp "soccer")
354 | :inbox "~/org/fromstuff.org")) )
355 | @end lisp
356 |
357 | This means that you have two calendars with IDs "work@@whatever" and
358 | "stuff@@mystuff". Both will be accessed through the global value of
359 | org-caldav-url, since the key :url isn't specified. The calendar
360 | "work@@whatever" will be synced with the file 'work.org' and inbox
361 | 'fromwork.org', while "stuff@@mystuff" with 'sports.org' and
362 | 'play.org', @emph{unless} there's the string 'soccer' in the heading, and
363 | and inbox is 'fromstuff.org'. See the doc-string of
364 | @code{org-caldav-calendars} for more details on which keys you can use.
365 |
366 | @node Customizing the inbox
367 | @section Customizing the inbox
368 |
369 | See the doc-string of @code{org-caldav-inbox} if you want more flexibility in
370 | where new items should be put. Instead of simply providing a file, you
371 | can also choose an existing entry or headline, or put the entry under a
372 | datetree.
373 |
374 | @node Syncing TODOs between Org and CalDav
375 | @section Syncing TODOs between Org and CalDav
376 |
377 | This feature is relatively new and less well tested, so it is
378 | recommended to have backups before using it. It has been tested on
379 | nextcloud and radicale.
380 |
381 | To sync TODO's between Org and the CalDav server, do:
382 |
383 | @lisp
384 | (setq org-icalendar-include-todo 'all
385 | org-caldav-sync-todo t)
386 | @end lisp
387 |
388 | The first instructs the Org exporter to include TODOs; the second
389 | tells org-caldav to import icalendar VTODOs as Org TODOs.
390 |
391 | Other customizations to consider (see their documentation for more
392 | details):
393 |
394 | @itemize
395 | @item
396 | @code{org-caldav-todo-priority} to control how priority levels map between
397 | iCalendar and Org.
398 | @item
399 | @code{org-caldav-todo-percent-states} to convert between
400 | @code{org-todo-keywords} and iCalendar's percent-complete property.
401 | @item
402 | @code{org-caldav-todo-deadline-schedule-warning-days} to auto-create
403 | SCHEDULED timestamps when a DEADLINE is present (this might be useful
404 | for users of the OpenTasks app).
405 | @end itemize
406 |
407 | If you find that some Org entries get an extra tag which equals their
408 | CATEGORY, this might be caused by the CATEGORY being exported to
409 | iCalendar, and then re-imported to Org as a tag. In that case, do
410 |
411 | @lisp
412 | (setq org-icalendar-categories '(local-tags))
413 | @end lisp
414 |
415 | to prevent the CATEGORY from being exported to iCalendar. This problem
416 | only seems to affect some CalDav servers: in particular, NextCloud
417 | is affected, but Radicale does not seem to experience this problem.
418 |
419 | @node Behavior of recurring TODO deadlines without a start time
420 | @section Behavior of recurring TODO deadlines without a start time
421 |
422 | Technically, the @uref{https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.2.4, iCalendar spec} requires repeating events and todos
423 | (i.e. having an @code{RRULE} property) to have a starting time (@code{DTSTART}
424 | in iCalendar, equivalent to @code{SCHEDULED} in Org TODOs). This means
425 | that, a TODO with a repeating @code{DEADLINE} but without a @code{SCHEDULED}
426 | property, such as below, is not allowed by the iCalendar spec:
427 |
428 | @example
429 | * TODO An example todo with a repeating deadline and no start time
430 | DEADLINE: <2024-09-15 Sun +1w>
431 | @end example
432 |
433 | This is a clear shortcoming of the iCalendar spec, because it @emph{does}
434 | allow tasks to have a standalone deadline without a starting time, but
435 | @emph{doesn't} allow such tasks to repeat.
436 |
437 | By default, @code{ox-icalendar} follows the iCalendar spec, and when
438 | exporting a TODO with a repeating @code{DEADLINE} but no @code{SCHEDULED}
439 | timestamp, will add a start time based on
440 | @code{org-deadline-warning-days}. On future syncs, this start time will be
441 | inserted into Org as a @code{SCHEDULED} timestamp.
442 |
443 | However, in practice, many iCalendar implementations ignore this
444 | limitation, and allow Todos with @code{DEADLINE} (@code{DUE}) times to have
445 | repeaters (@code{RRULE}), even if they are missing @code{SCHEDULED} (@code{DTSTART})
446 | times. If your CalDav server allows this, then you may set the
447 | variable @code{org-icalendar-todo-unscheduled-start} to @code{nil}. This will
448 | prevent @code{ox-icalendar} from adding a start time to such TODOs, thus
449 | preventing the @code{SCHEDULED} timestamp from being inserted on future
450 | syncs.
451 |
452 | See @ref{Repeating events and todos} for more details about how org-caldav
453 | handles repeating events and todos.
454 |
455 | @node Compatible CalDav servers
456 | @chapter Compatible CalDav servers
457 |
458 | @itemize
459 | @item
460 | @strong{Owncloud} and @strong{Nextcloud}: Regularly tested.
461 |
462 | @item
463 | @strong{Radicale} and @strong{Baikal}: Works. If you get problems with 'Digest'
464 | authentication, switch back to 'Basic' (make sure to use https,
465 | though!). If you get asked for password repeatedly, put it in
466 | @code{.authinfo} file (@ref{Storing authentication information in authinfo/netrc, , Storing authentication information}).
467 |
468 | @item
469 | @strong{SOGo} and @strong{Kolab}: Reported to be working
470 | (@uref{https://docs.kolab.org/client-configuration/emacs.html})
471 |
472 | @item
473 | @strong{Google Calendar}: Should work, but you need to register an
474 | application with the Google Developer Console for OAuth2
475 | authentication (see below), because Google explicitly forbids to put
476 | client id/secrets into open source software (see
477 | @uref{https://developers.google.com/terms}, section 4b, paragraph 1). Instead
478 | of doing that though, I'd rather suggest you choose another service
479 | provider.
480 | @end itemize
481 |
482 | @menu
483 | * Syncing to Google Calendar::
484 | @end menu
485 |
486 | @node Syncing to Google Calendar
487 | @section Syncing to Google Calendar
488 |
489 | NOTE: Using org-caldav with Google Calendar may be currently
490 | broken. See @uref{https://github.com/dengste/org-caldav/issues/284}
491 |
492 | The CalDAV endpoint for Google Calendar requires OAuth2
493 | authentication. So first, you need to install the oauth2 library from
494 | GNU ELPA, and afterwards you need to acquire an application ID and
495 | secret from the Google Developer Console. For details on how to do
496 | this, follow the Google documentation at
497 |
498 | @uref{https://developers.google.com/google-apps/calendar/caldav/v2/guide#creating_your_client_id}
499 |
500 | Put the client ID and secret into @code{org-caldav-oauth2-client-id} and
501 | @code{org-caldav-oauth2-client-secret}, respectively. Then set
502 | @code{org-caldav-url} to the symbol @code{'google}, and look up the
503 | @code{org-caldav-calendar-id} as described above.
504 |
505 | On first connection, the oauth2 library should redirect you to the
506 | Google OAuth2 authentication site. This requires a javascript enabled
507 | browser, so make sure that @code{browse-url-browser-function} is set to
508 | something like @code{browse-url-firefox} (the internal eww or w3m browsers
509 | will @strong{not} work). After authentication, you will be given a key that
510 | you have to paste into the Emacs prompt. The oauth2 library will save
511 | this key in Emacs' secure plist store, which is encrypted with
512 | GnuPG@. If you have not yet used a secure plist store, you will be
513 | asked for its encryption passphrase. In the future, you should only
514 | need to enter that passphrase again to connect with Google Calendar.
515 |
516 | By default, plstore will @strong{not} cache your entered password, so it will
517 | possibly ask you @strong{many} times. To activate caching, use
518 |
519 | @lisp
520 | (setq plstore-cache-passphrase-for-symmetric-encryption t)
521 | @end lisp
522 |
523 | @node Implementation details
524 | @chapter Implementation details
525 |
526 | @menu
527 | * Org and the iCalendar format::
528 | * Conflict handling::
529 | * Repeating events and todos::
530 | * How syncing happens (a.k.a. David's little CalDAV rant): How syncing happens (aka David's little CalDAV rant).
531 | @end menu
532 |
533 | @node Org and the iCalendar format
534 | @section Org and the iCalendar format
535 |
536 | An Org entry can store much more information than an iCalendar entry,
537 | so there is no one-to-one correspondence between the two formats which
538 | makes syncing a bit difficult.
539 |
540 | @itemize
541 | @item
542 | Org to iCalendar
543 | @end itemize
544 |
545 | This package uses the org-icalendar package to do the export to the
546 | iCalendar format (.ics files). By default, it uses the title of the
547 | Org entry as SUMMARY and puts the entry's body into DESCRIPTION,
548 | snipping stuff like properties and timestamps (you can override that
549 | with properties of the same name, but IMO it makes stuff just more
550 | complicated). The variable @code{org-icalendar-include-body}
551 | denotes how many characters from the body should be included as
552 | DESCRIPTION (by default all characters are included).
553 |
554 | @itemize
555 | @item
556 | iCalendar to Org
557 | @end itemize
558 |
559 | If you create a new iCalendar entry in your calendar, you'll get an
560 | Org entry with SUMMARY as heading, DESCRIPTION as body and the
561 | timestamp. However, if you @emph{change} an existing entry in the calendar,
562 | things get more complicated and the variable
563 | @code{org-caldav-sync-changes-to-org} comes into play. Its default is the
564 | symbol "title-and-timestamp", which means that only the entry's
565 | heading is synced (with SUMMARY) and the timestamp gets updated, but
566 | @emph{not} the entry's body with DESCRIPTION@. The simple reason is that
567 | you might loose data, since DESCRIPTION is rather limited in what it
568 | can store. Still, you can set the variable to the symbol "all", which
569 | will completely @emph{replace} an existing Org entry with the entry that
570 | gets generated from the calendar's event. You can also limit syncing
571 | to heading and/or timestamp only.
572 |
573 | To be extra safe, org-caldav will by default backup entries it
574 | changes. See the variable @code{org-caldav-backup-file} for details.
575 |
576 | @itemize
577 | @item
578 | Org sexp entries
579 | @end itemize
580 |
581 | A special case are sexp entries like
582 |
583 | @example
584 | %%(diary-anniversary 2 2 1969) Foo's birthday
585 |
586 | * Regular meeting
587 | <%%(diary-float t 4 2)>
588 | @end example
589 |
590 | As you can see, they can appear in two different ways: plain by
591 | themselves, or inside an Org entry. If they are inside an Org entry,
592 | there's a good chance they will be exported (see below) and have an ID
593 | property, so they can be found by org-caldav. We can sync the title,
594 | but syncing the timestamp with the s-expression is just infeasible, so
595 | this will generate a sync error (which are @emph{not} critical; you'll just
596 | see them at the end of the sync, just so that you're aware that some
597 | stuff wasn't synced properly).
598 |
599 | However, sexp-entries are insanely flexible, and there are limits as
600 | to what the icalendar exporter will handle. For example, this here
601 |
602 | @example
603 | ** Regular event
604 | <%%(memq (calendar-day-of-week date) '(1 3 5))>
605 | @end example
606 |
607 | will not be exported at all.
608 |
609 | If the sexp entry is not inside an Org entry but stands by itself,
610 | they still will be exported, but they won't get an ID (since IDs are
611 | properties linked to Org entries). In practice, that means that you
612 | can delete and change them inside Org and this will be synced, but if
613 | you @emph{change} them in the @emph{calendar}, this will @emph{not} get synced
614 | back. Org-caldav just cannot find those entries, so this will generate
615 | a one-time sync error instead (again: those are not critical, just
616 | FYI). If you don't want those entries to be exported at all, just set
617 | @code{org-icalendar-include-sexps} to nil.
618 |
619 | @node Conflict handling
620 | @section Conflict handling
621 |
622 | Now that's an easy one: Org always wins. That means, if you change an
623 | entry in Org @emph{and} in the calendar, the changes in the calendar will
624 | be lost. I might implement proper conflict handling some day, but
625 | don't hold your breath (patches are welcome, of course).
626 |
627 | @node Repeating events and todos
628 | @section Repeating events and todos
629 |
630 | Org-caldav has basic support for repeating events and todos. In
631 | particular, simple Org timestamp repeaters such as @code{+3d} or @code{+1m} can
632 | be succesfully sync'd bidirectionally.
633 |
634 | However, complex iCalender recurrences, such as "repeat on the 2nd
635 | Tuesday of each month until X date", are not supported.
636 |
637 | For Org TODOs with both @code{SCHEDULED} and @code{DEADLINE} timestamps, each of
638 | the timestamps must have the same repeater, otherwise the behavior is
639 | undefined.
640 |
641 | Furthermore, the behavior of Org TODOs with a repeating @code{DEADLINE}
642 | timestamp, but no @code{SCHEDULED} timestamp, has some subtleties; see the
643 | configuration section: @ref{Behavior of recurring TODO deadlines without a start time}.
644 |
645 | @node How syncing happens (aka David's little CalDAV rant)
646 | @section How syncing happens (a.k.a. David's little CalDAV rant)
647 |
648 | (This is probably not interesting, so you can skip this.)
649 |
650 | CalDAV is a mess.
651 |
652 | First off, it is based on WebDAV, which has its own fair share of
653 | problems. The main design flaw of CalDAV however, is that UID and
654 | resource name (the "filename", if you want) are two different
655 | things. I know that there are reasons for that (not everything has a
656 | UID, like timezones, and you can put several events in one resource),
657 | but this is typical over-engineering to allow some marginal use cases
658 | pretty much no one needs. Another problem is that you have to do
659 | additional round-trips to get Etag and sequence number, which makes
660 | CalDAV pretty slow.
661 |
662 | Org-caldav takes the easy route: it assumes that every resource
663 | contains one event, and that UID and resource name are identical. In
664 | fact, Google's CalDAV interface even enforces the latter. And while
665 | Owncloud does not enforce it, at least it just does it if you create
666 | items in its web interface.
667 |
668 | However, the CalDAV standard does not demand this, so I guess there
669 | are servers out there with which org-caldav does not work. Patches
670 | welcome.
671 |
672 | Now, all this would be bad enough if it weren't for the sloppy server
673 | implementations which make implementing a CalDAV client a living hell
674 | and led to several rewrites of the code. Especially Google, the 500
675 | pound gorilla in the room, doesn't really care much for CalDAV@. I
676 | guess they really like their own shiny REST-based calendar API better,
677 | and I can't blame them for that.
678 |
679 | @node Tips Tricks and Troubleshooting
680 | @chapter Tips, Tricks, and Troubleshooting
681 |
682 | @menu
683 | * Standalone import of ICS files::
684 | * Storage of sync information and sync from different computers::
685 | * Starting from scratch::
686 | * Timezone problems::
687 | * Troubleshooting::
688 | @end menu
689 |
690 | @node Standalone import of ICS files
691 | @section Standalone import of ICS files
692 |
693 | org-caldav can also be used for standalone import of ICS files to Org.
694 |
695 | In particular, see @code{org-caldav-import-ics-buffer-to-org} to import
696 | iCalendar entries (e.g. from e-mail attachments) directly to your
697 | @code{org-caldav-inbox}.
698 |
699 | Also, see @code{org-caldav-convert-ics-to-datetree} to convert an iCalendar
700 | file into an Org datetree in a separate buffer (use
701 | @code{org-caldav-datetree-treetype} to control the style of datetree).
702 |
703 | @node Storage of sync information and sync from different computers
704 | @section Storage of sync information and sync from different computers
705 |
706 | The current sync state is stored in a file @code{org-caldav-SOMEID.el} in
707 | the @code{/.emacs.d directory. You can change the location through the
708 | variable ~org-caldav-save-directory}. SOMEID directly depends on the
709 | calendar id (it's a snipped MD5).
710 |
711 | If you sync your Org files across different machines and want to use
712 | org-caldav on all of them, don't forget to sync the org sync state,
713 | too. Probably your best bet is to set @code{org-caldav-save-directory} to the
714 | path you have your Org files in, so that it gets copied alongside with
715 | them.
716 |
717 | @node Starting from scratch
718 | @section Starting from scratch
719 |
720 | If your sync state somehow gets broken, you can make a clean slate by
721 | doing
722 |
723 | @example
724 | C-u M-x org-caldav-delete-everything
725 | @end example
726 |
727 | The function has to be called with a prefix so that you don't call it
728 | by accident. This will delete everything in the calendar along with
729 | the current sync state. You can then call @code{org-caldav-sync} afterwards
730 | and it will completely put all Org events into the now empty
731 | calendar. Needless to say, don't do that if you have new events in
732 | your calendar which are not synced yet@dots{}
733 |
734 | Deleting many events can be slow, though; in that case, just delete
735 | the calendar and re-create it, delete the sync state file in
736 | ~/.emacs.d and restart Emacs.
737 |
738 | @node Timezone problems
739 | @section Timezone problems
740 |
741 | Timezone handling is plain horrible, and it seems every CalDAV server
742 | does it slightly differently, also using non-standard headers like
743 | X-WR-TIMEZONE@. If you see items being shifted by a few hours, make
744 | really really sure you have properly set @code{org-icalendar-timezone}, and
745 | that your calendar is configured to use the same one.
746 |
747 | If it still does not work, you can try setting @code{org-icalendar-timezone}
748 | to the string "UTC". This will put all events using UTC times and the
749 | server should transpose the time to the timezone you have set in your
750 | calendar preferences. For some servers (like SOGo) this might work
751 | better than setting a "real" timezone.
752 |
753 | @node Troubleshooting
754 | @section Troubleshooting
755 |
756 | If org-caldav reports a problem with the given URL, please
757 | triple-check that the URL is correct. It must point to a valid
758 | calendar on your CalDAV server.
759 |
760 | If the error is that the URL does not seem to accept DAV requests, you
761 | can additionally check with 'curl' by doing
762 |
763 | @example
764 | curl -D - -X OPTIONS --basic -u mylogin:mypassword URL
765 | @end example
766 |
767 | The output of this command must contain a 'DAV' header like this:
768 |
769 | @example
770 | DAV: 1, 3, extended-mkcol, access-control, ... etc. ...
771 | @end example
772 |
773 | By default, org-caldav will put all kinds of debug output into the
774 | buffer @code{*org-caldav-debug*}. Look there if you're getting sync errors
775 | or if something plain doesn't work. If you're using an authinfo file
776 | and authentication doesn't work, set auth-info-debug to t and look in
777 | the @code{*Messages*} buffer. When you report a bug, please try to post the
778 | relevant portion of the @code{*org-caldav-debug*} buffer since it might be
779 | helpful to see what's going wrong. If Emacs throws an error, do
780 |
781 | @example
782 | M-x toggle-debug-on-error
783 | @end example
784 |
785 | and try to replicate the error to get a backtrace.
786 |
787 | You can also turn on excessive debugging by setting the variable
788 | @code{org-caldav-debug-level} to 2. This will also output the @emph{contents} of
789 | the events into the debug buffer. If you send such a buffer in a bug
790 | report, please make very sure you have removed personal information
791 | from those events.
792 |
793 | @node Known Bugs
794 | @chapter Known Bugs
795 |
796 | @itemize
797 | @item
798 | Syncing is currently pretty slow since everything is done
799 | synchronously.
800 |
801 | @item
802 | Pretty much everything besides SUMMARY, DESCRIPTION, LOCATION and time
803 | is ignored in iCalendar.
804 | @end itemize
805 |
806 | @bye
807 |
--------------------------------------------------------------------------------
/org-caldav-tests.el:
--------------------------------------------------------------------------------
1 | ;; Test suite for org-caldav.el
2 | ;; Copyright, authorship, license: see org-caldav.el.
3 |
4 | ;; Run it from the org-caldav directory like this:
5 | ;; TZ="Europe/Berlin" emacs -Q -L . --eval '(setq org-caldav-url "CALDAV-URL")' -l org-caldav-testsuite.el -f ert
6 | ;;
7 | ;; On the server, there must already exist two calendars "test1" and "test2".
8 | ;; These will completely wiped by running this test!
9 | ;;
10 | ;; Hint: In case you need a test server, one lightweight option is:
11 | ;; docker run -v /path/to/data:/data tomsquest/docker-radicale
12 | ;; Then, you can create the test1 calendar from Thunderbird like so:
13 | ;; Thunderbird -> New Calendar -> Network -> Location:
14 | ;; http://localhost:5232/test/test1/ (the trailing slash is
15 | ;; important), with username "test" and blank password. Then add an
16 | ;; event from Thunderbird to make sure the calendar exists.
17 |
18 | (require 'ert)
19 | (require 'org)
20 | (require 'org-caldav)
21 | (require 'cl-lib)
22 |
23 | (when (org-caldav-use-oauth2)
24 | (org-caldav-check-oauth2 org-caldav-url)
25 | (org-caldav-retrieve-oauth2-token org-caldav-url))
26 |
27 | (defvar org-caldav-test-calendar-names '("test1" "test2"))
28 |
29 | (setq org-caldav-debug-level (max 2 org-caldav-debug-level))
30 |
31 | (setq org-caldav-delete-calendar-entries 'always)
32 | (setq org-caldav-backup-file nil)
33 | (setq org-caldav-test-preamble
34 | "BEGIN:VCALENDAR
35 | VERSION:2.0
36 | CALSCALE:GREGORIAN
37 | X-WR-TIMEZONE:Europe/Berlin
38 | X-WR-CALDESC:
39 | BEGIN:VTIMEZONE
40 | TZID:Europe/Berlin
41 | X-LIC-LOCATION:Europe/Berlin
42 | BEGIN:DAYLIGHT
43 | TZOFFSETFROM:+0100
44 | TZOFFSETTO:+0200
45 | TZNAME:CEST
46 | DTSTART:19700329T020000
47 | RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
48 | END:DAYLIGHT
49 | BEGIN:STANDARD
50 | TZOFFSETFROM:+0200
51 | TZOFFSETTO:+0100
52 | TZNAME:CET
53 | DTSTART:19701025T030000
54 | RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
55 | END:STANDARD
56 | END:VTIMEZONE
57 | ")
58 |
59 | ;; First test event in calendar
60 | (setq org-caldav-test-ics1
61 | "BEGIN:VEVENT
62 | DTSTART;VALUE=DATE:20121220
63 | DTEND;VALUE=DATE:20121221
64 | DTSTAMP:20121218T212132Z
65 | UID:orgcaldavtest@cal1
66 | CREATED:20121216T205929Z
67 | DESCRIPTION:A first test
68 | LAST-MODIFIED:20121218T212132Z
69 | LOCATION:
70 | SUMMARY:Test appointment Number 1
71 | END:VEVENT
72 | ")
73 |
74 | ;; How it should end up in Org
75 | (setq org-caldav-test-ics1-org
76 | "\\* Test appointment Number 1
77 | \\s-*:PROPERTIES:
78 | \\s-*:ID:\\s-*orgcaldavtest@cal1
79 | \\s-*:END:
80 | \\s-*<2012-12-20 Thu>
81 | \\s-*A first test")
82 |
83 | ;; Dto., second one
84 | (setq org-caldav-test-ics2
85 | "BEGIN:VEVENT
86 | DTSTART;TZID=Europe/Berlin:20121205T190000
87 | DTEND;TZID=Europe/Berlin:20121205T200000
88 | DTSTAMP:20121219T213352Z
89 | UID:orgcaldavtest-cal2
90 | CREATED:20121219T213352Z
91 | DESCRIPTION:A second test
92 | LAST-MODIFIED:20121219T213352Z
93 | LOCATION:
94 | SUMMARY:Test appointment Number 2
95 | END:VEVENT
96 | ")
97 |
98 | (setq org-caldav-test-ics2-org
99 | "\\* Test appointment Number 2
100 | \\s-*:PROPERTIES:
101 | \\s-*:ID:\\s-*orgcaldavtest-cal2
102 | \\s-*:END:
103 | \\s-*<2012-12-05 Wed 19:00-20:00>
104 | \\s-*A second test")
105 |
106 | ;; First test task in calendar
107 | (setq org-caldav-test-ics3
108 | "BEGIN:VTODO
109 | UID:orgcaldavtest@cal3
110 | DTSTAMP:20220828T161432Z
111 | DTSTART;VALUE=DATE:20121223
112 | SUMMARY:A test task from iCal
113 | DESCRIPTION:ical test task 1
114 | PRIORITY:0
115 | STATUS:NEEDS-ACTION
116 | END:VTODO
117 | ")
118 |
119 | (setq org-caldav-test-ics3-org
120 | "\\* TODO A test task from iCal
121 | \\s-*SCHEDULED: <2012-12-23 Sun>
122 | \\s-*:PROPERTIES:
123 | \\s-*:ID: orgcaldavtest@cal3
124 | \\s-*:END:
125 | \\s-*ical test task 1")
126 |
127 | ;; Second test task in calendar
128 | (setq org-caldav-test-ics4
129 | "BEGIN:VTODO
130 | UID:orgcaldavtest-cal4
131 | DTSTAMP:20220828T161432Z
132 | DTSTART;VALUE=DATE:20121219
133 | DUE;VALUE=DATE:20121223
134 | SUMMARY:Another test task from iCal
135 | DESCRIPTION:ical test task 2
136 | PRIORITY:5
137 | STATUS:NEEDS-ACTION
138 | END:VTODO
139 | ")
140 |
141 | (setq org-caldav-test-ics4-org
142 | "\\* TODO \\[#B\\] Another test task from iCal
143 | \\s-*DEADLINE: <2012-12-23 Sun> SCHEDULED: <2012-12-19 Wed>
144 | \\s-*:PROPERTIES:
145 | \\s-*:ID: orgcaldavtest-cal4
146 | \\s-*:END:
147 | \\s-*ical test task 2")
148 |
149 | ;; First test entry in Org which should end up in calendar
150 | (setq org-caldav-test-org1
151 | "* This is a test
152 | :PROPERTIES:
153 | :ID: orgcaldavtest@org1
154 | :END:
155 | <2012-12-23 Sun 20:00-21:00>
156 | Foo Bar Baz
157 | ")
158 |
159 | ;; Dto., second one
160 | (setq org-caldav-test-org2
161 | "* This is another test
162 | :PROPERTIES:
163 | :ID: orgcaldavtest-org2
164 | :END:
165 | <2012-12-19 Wed 19:00-21:00>
166 | Baz Bar Foo
167 | ")
168 |
169 | (setq org-caldav-test-org3
170 | "* This is a test with a tag :sometag:
171 | :PROPERTIES:
172 | :ID: orgcaldavtest-org3
173 | :END:
174 | <2012-12-20 Thu 19:00-21:00>
175 | moose
176 | ")
177 |
178 | ;; First test task in Org which should end up in calendar
179 | (setq org-caldav-test-org4
180 | "* TODO A test task from Org
181 | SCHEDULED: <2012-12-23 Sun>
182 | :PROPERTIES:
183 | :ID: orgcaldavtest@org4
184 | :END:
185 | Org task 1
186 | ")
187 |
188 | ;; Dto., second one
189 | (setq org-caldav-test-org5
190 | "* TODO [#B] Another test task from Org
191 | DEADLINE: <2012-12-23 Sun> SCHEDULED: <2012-12-19 Wed>
192 | :PROPERTIES:
193 | :ID: orgcaldavtest-org5
194 | :END:
195 | Org task 2
196 | ")
197 |
198 | ;; All events after sync.
199 | (setq org-caldav-test-allevents
200 | '("orgcaldavtest@org1" "orgcaldavtest-org2" "orgcaldavtest@cal1" "orgcaldavtest-cal2"))
201 |
202 | (setq org-caldav-test-alltodos
203 | '("orgcaldavtest@org4" "orgcaldavtest-org5" "orgcaldavtest@cal3" "orgcaldavtest-cal4"))
204 |
205 |
206 | (setq org-caldav-test-sync-result
207 | '(("test1" "orgcaldavtest@cal1" new-in-cal cal->org)
208 | ("test1" "orgcaldavtest-cal2" new-in-cal cal->org)))
209 |
210 | ;; Test files.
211 | (defun org-caldav-test-calendar-empty-p ()
212 | "Check if calendar is empty."
213 | (let ((output (org-caldav-url-dav-get-properties
214 | (org-caldav-events-url) "resourcetype")))
215 | (unless (eq (plist-get (cdar output) 'DAV:status) 200)
216 | (error "Could not query CalDAV URL %s: %s." (org-caldav-events-url) (prin1-to-string output)))
217 | (= (length output) 1)))
218 |
219 | (defun org-caldav-test-set-up ()
220 | "Make a clean slate."
221 | (message "SET UP")
222 | (unless (org-caldav-test-calendar-empty-p)
223 | (dolist (cur (org-caldav-get-event-etag-list))
224 | (message "Deleting %s" (car cur))
225 | (org-caldav-delete-event (car cur))))
226 | (should (org-caldav-test-calendar-empty-p)))
227 |
228 | (defun org-caldav-test-put-events ()
229 | "Put initial events to calendar."
230 | (message "PUT")
231 | (let ((org-caldav-calendar-preamble org-caldav-test-preamble)
232 | events)
233 | (with-temp-buffer
234 | (insert org-caldav-test-ics1)
235 | (should (org-caldav-put-event (current-buffer)))
236 | (erase-buffer)
237 | (insert org-caldav-test-ics2)
238 | (should (org-caldav-put-event (current-buffer))))
239 | (should
240 | (setq events
241 | (org-caldav-get-event-etag-list)))
242 | (should (assoc "orgcaldavtest@cal1" events))
243 | (should (assoc "orgcaldavtest-cal2" events))))
244 |
245 | (defun org-caldav-test-setup-temp-files ()
246 | (let ((tmpdir (make-temp-file "org-caldav-test-" t)))
247 | (message "Using tempdir %s" tmpdir)
248 | (setq org-caldav-save-directory (expand-file-name "org-caldav-savedir" tmpdir)
249 | org-caldav-backup-file (expand-file-name "org-caldav-test-backup.org" tmpdir)
250 | org-caldav-test-orgfile (expand-file-name "test.org" tmpdir)
251 | org-caldav-test-second-orgfile (expand-file-name "test-second.org" tmpdir)
252 | org-caldav-test-inbox (expand-file-name "inbox.org" tmpdir)
253 | org-id-locations-file (expand-file-name "org-id-locations" tmpdir)
254 | org-id-locations nil
255 | org-id-files nil
256 | org-caldav-test-current-tempdir tmpdir))
257 | (make-directory org-caldav-save-directory)
258 | (with-current-buffer (find-file-noselect org-caldav-test-inbox)
259 | (save-buffer)))
260 |
261 | (defun org-caldav-test-cleanup ()
262 | (dolist (name '("test.org" "test-second.org" "inbox.org" "org-id-locations"))
263 | (let ((buf (get-buffer name)))
264 | (when buf
265 | (with-current-buffer buf
266 | (set-buffer-modified-p nil)
267 | (kill-buffer)))))
268 | (setq org-id-locations nil)
269 | (setq org-caldav-event-list nil)
270 | (when org-caldav-test-current-tempdir
271 | (delete-directory org-caldav-test-current-tempdir t)
272 | (setq org-caldav-test-current-tempdir nil)))
273 |
274 | ;; This is one, big, big test, since pretty much everything depends on
275 | ;; the current calendar/org state and I cannot easily split it.
276 | (ert-deftest org-caldav-01-sync-test ()
277 | (message "Setting up temporary files")
278 | (org-caldav-test-setup-temp-files)
279 | (setq org-caldav-calendar-id (car org-caldav-test-calendar-names))
280 | ;; Set up orgfile.
281 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
282 | (insert org-caldav-test-org1 "\n")
283 | (insert org-caldav-test-org2 "\n")
284 | (save-buffer))
285 | ;; Set up data for org-caldav.
286 | (setq org-caldav-files (list org-caldav-test-orgfile))
287 | (setq org-caldav-inbox org-caldav-test-inbox)
288 | (message "Cleaning up upstream calendars")
289 | (org-caldav-test-set-up)
290 | (message "Putting events")
291 | (org-caldav-test-put-events)
292 | (message "1st SYNC")
293 | ;; Do the sync.
294 | (org-caldav-sync)
295 | ;; Check result.
296 | (should (member `(,org-caldav-calendar-id "orgcaldavtest@cal1" new-in-cal cal->org)
297 | org-caldav-sync-result))
298 | (should (member `(,org-caldav-calendar-id "orgcaldavtest-cal2" new-in-cal cal->org)
299 | org-caldav-sync-result))
300 | (should (member `(,org-caldav-calendar-id "orgcaldavtest@org1" new-in-org org->cal)
301 | org-caldav-sync-result))
302 | (should (member `(,org-caldav-calendar-id "orgcaldavtest-org2" new-in-org org->cal)
303 | org-caldav-sync-result))
304 | ;; State file should exist now.
305 | (should (file-exists-p
306 | (org-caldav-sync-state-filename org-caldav-calendar-id)))
307 | (let ((calevents (org-caldav-get-event-etag-list)))
308 | (should (= (length calevents) (length org-caldav-test-allevents)))
309 | ;; Org events should be in cal.
310 | (dolist (cur org-caldav-test-allevents)
311 | (should (assoc cur calevents))))
312 | ;; Cal events should be in Org.
313 | (with-current-buffer (find-file-noselect org-caldav-test-inbox)
314 | (goto-char (point-min))
315 | (should (re-search-forward org-caldav-test-ics1-org nil t))
316 | (goto-char (point-min))
317 | (should (re-search-forward org-caldav-test-ics2-org nil t)))
318 |
319 | (message "2nd SYNC")
320 | ;; Sync again.
321 | (org-caldav-sync)
322 | ;; Nothing should have happened.
323 | (should-not org-caldav-sync-result)
324 | (message "Changing org events")
325 | ;; Now change events in Org
326 | (with-current-buffer (find-buffer-visiting org-caldav-test-orgfile)
327 | (goto-char (point-min))
328 | (search-forward "This is another test")
329 | (replace-match "This is a changed test heading")
330 | (search-forward "<2012-12-19 Wed 19:00-21:00>")
331 | (replace-match "<2012-12-19 Thu 20:00-22:00>"))
332 | (with-current-buffer (find-buffer-visiting org-caldav-test-inbox)
333 | (goto-char (point-min))
334 | (search-forward "Test appointment Number 2")
335 | (replace-match "Appointment number 2 was changed!")
336 | (search-forward "<2012-12-05 Wed 19:00-20:00>")
337 | (replace-match "<2012-12-04 Tue 18:00-19:00>"))
338 |
339 | ;; And sync...
340 | (message "3rd SYNC")
341 | (org-caldav-sync)
342 | (should (equal `((,org-caldav-calendar-id "orgcaldavtest-cal2" changed-in-org org->cal)
343 | (,org-caldav-calendar-id "orgcaldavtest-org2" changed-in-org org->cal))
344 | org-caldav-sync-result))
345 |
346 | ;; Check if those events correctly end up in calendar.
347 | (with-current-buffer (org-caldav-get-event "orgcaldavtest-cal2")
348 | (goto-char (point-min))
349 | (save-excursion
350 | (should (search-forward "SUMMARY:Appointment number 2 was changed!")))
351 | (save-excursion
352 | (should (re-search-forward "DTSTART.*:20121204T\\(170000Z\\|180000\\)" nil t)))
353 | (save-excursion
354 | (should (re-search-forward "DTEND.*:20121204T\\(180000Z\\|190000\\)" nil t))))
355 |
356 | (with-current-buffer (org-caldav-get-event "orgcaldavtest-org2")
357 | (goto-char (point-min))
358 | (save-excursion
359 | (should (search-forward "SUMMARY:This is a changed test heading")))
360 | (save-excursion
361 | (should (re-search-forward "DTSTART.*:20121219T\\(190000Z\\|200000\\)" nil t)))
362 | (save-excursion
363 | (should (re-search-forward "DTEND.*:20121219T\\(210000Z\\|220000\\)" nil t))))
364 |
365 | ;; Now change events in Cal
366 | (message "Changing events in calendar")
367 | (with-current-buffer (org-caldav-get-event "orgcaldavtest@cal1")
368 | (goto-char (point-min))
369 | (save-excursion
370 | (search-forward "SUMMARY:Test appointment Number 1")
371 | (replace-match "SUMMARY:Changed test appointment Number 1"))
372 | (save-excursion
373 | (re-search-forward "DTSTART\\(;.*\\)?:\\(20121220\\)")
374 | (replace-match "20121212" nil nil nil 2))
375 | (save-excursion
376 | (re-search-forward "DTEND\\(;.*\\)?:\\(20121221\\)")
377 | (replace-match "20121213" nil nil nil 2))
378 | (save-excursion
379 | (when (re-search-forward "SEQUENCE:\\s-*\\([0-9]+\\)" nil t)
380 | (replace-match (number-to-string
381 | (1+ (string-to-number (match-string 1))))
382 | nil t nil 1)))
383 | (message "PUTting first changed event")
384 | (should (org-caldav-save-resource
385 | (concat (org-caldav-events-url) (url-hexify-string "orgcaldavtest@cal1.ics"))
386 | (encode-coding-string (buffer-string) 'utf-8))))
387 |
388 | (with-current-buffer (org-caldav-get-event "orgcaldavtest@org1")
389 | (goto-char (point-min))
390 | (save-excursion
391 | (search-forward "SUMMARY:This is a test")
392 | (replace-match "SUMMARY:This is a changed test"))
393 | (save-excursion
394 | (if (re-search-forward "DTSTART\\(;.*\\)?:\\(20121223T190000Z\\)" nil t)
395 | (replace-match "20121213T180000Z" nil nil nil 2)
396 | (re-search-forward "DTSTART\\(;.*\\)?:\\(20121223T200000\\)")
397 | (replace-match "20121213T190000" nil nil nil 2)))
398 | (save-excursion
399 | (if (re-search-forward "DTEND\\(;.*\\)?:\\(20121223T200000Z\\)" nil t)
400 | (replace-match "20121213T190000Z" nil nil nil 2)
401 | (re-search-forward "DTEND\\(;.*\\)?:\\(20121223T210000\\)")
402 | (replace-match "20121213T200000" nil nil nil 2)))
403 | (save-excursion
404 | (when (re-search-forward "SEQUENCE:\\s-*\\([0-9]+\\)" nil t)
405 | (replace-match (number-to-string
406 | (1+ (string-to-number (match-string 1))))
407 | nil t nil 1)))
408 | (message "PUTting second changed event")
409 | (should (org-caldav-save-resource
410 | (concat (org-caldav-events-url) "orgcaldavtest@org1.ics")
411 | (encode-coding-string (buffer-string) 'utf-8))))
412 |
413 | ;; Aaaand sync!
414 | (message "4th SYNC")
415 | (org-caldav-sync)
416 |
417 | (should (equal `((,org-caldav-calendar-id "orgcaldavtest@cal1" changed-in-cal cal->org)
418 | (,org-caldav-calendar-id "orgcaldavtest@org1" changed-in-cal cal->org))
419 | org-caldav-sync-result))
420 |
421 | (with-current-buffer (find-file-noselect org-caldav-test-inbox)
422 | (goto-char (point-min))
423 | (should (re-search-forward
424 | "* Changed test appointment Number 1
425 | \\s-*:PROPERTIES:
426 | \\s-*:ID:\\s-*orgcaldavtest@cal1
427 | \\s-*:END:
428 | \\s-*<2012-12-12 Wed>
429 | \\s-*A first test")))
430 |
431 | (message "Deleting event in Org")
432 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
433 | (goto-char (point-min))
434 | (should (re-search-forward
435 | "* This is a changed test
436 | \\s-*:PROPERTIES:
437 | \\s-*:ID:\\s-*orgcaldavtest@org1
438 | \\s-*:END:
439 | \\s-*<2012-12-13 Thu 19:00-20:00>
440 | \\s-*Foo Bar Baz"))
441 | ;; Delete this event in Org
442 | (replace-match ""))
443 |
444 | ;; Sync
445 | (message "6th SYNC")
446 | (org-caldav-sync)
447 |
448 | ;; Event should be deleted in calendar
449 | (let ((calevents (org-caldav-get-event-etag-list)))
450 | (should (= (length calevents) 3))
451 | (should-not (assoc '"orgcaldavtest@org1" calevents)))
452 | (should
453 | (equal org-caldav-sync-result
454 | `((,org-caldav-calendar-id "orgcaldavtest@org1" deleted-in-org removed-from-cal))))
455 | (should-not
456 | (assoc '"orgcaldavtest@org1" org-caldav-event-list))
457 |
458 | ;; Delete event in calendar
459 | (message "Delete event in calendar")
460 | (should (org-caldav-delete-event "orgcaldavtest-org2"))
461 | ;; Sync one last time
462 | (message "7th SYNC")
463 | (let ((org-caldav-delete-org-entries 'always))
464 | (org-caldav-sync))
465 |
466 | (should
467 | (equal org-caldav-sync-result
468 | `((,org-caldav-calendar-id "orgcaldavtest-org2" deleted-in-cal removed-from-org))))
469 | ;; There shouldn't be anything left in that buffer
470 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
471 | (goto-char (point-min))
472 | (should-not (re-search-forward "[:alnum:]" nil t)))
473 | (should-not
474 | (assoc '"orgcaldavtest-org2" org-caldav-event-list))
475 |
476 | (org-caldav-test-cleanup)
477 |
478 | )
479 |
480 | (ert-deftest org-caldav-02-change-heading-test ()
481 | (with-current-buffer (get-buffer-create "headingtest")
482 | (erase-buffer)
483 | (insert "* This is a test without timestamp in headline\n"
484 | " <2009-08-08 Sat 10:00>\n whatever\n foo\n bar\n")
485 | (insert "* This is a test <2009-08-08 Sat> \n"
486 | " whatever\n foo\n bar\n")
487 | (insert "* <2009-08-08 Sat 14:00> This is another test\n"
488 | " whatever\n foo\n bar\n")
489 | (org-mode)
490 | (goto-char (point-min))
491 | (save-excursion
492 | (org-caldav-change-heading "first changed heading"))
493 | (should (looking-at "^\\* first changed heading$"))
494 | (search-forward "*" nil t 2)
495 | (beginning-of-line)
496 | (save-excursion
497 | (org-caldav-change-heading "second changed heading"))
498 | (should (looking-at "^\\* second changed heading <2009-08-08 Sat> $"))
499 | (search-forward "*" nil t 2)
500 | (beginning-of-line)
501 | (save-excursion
502 | (org-caldav-change-heading "third changed heading"))
503 | (should (looking-at "^\\* <2009-08-08 Sat 14:00> third changed heading\n"))
504 | ))
505 |
506 | (ert-deftest org-caldav-03-insert-org-entry ()
507 | "Make sure that `org-caldav-insert-org-event-or-todo' works fine."
508 | (let ((entry '((start-d . "01 01 2015")
509 | (start-t . "19:00")
510 | (end-d ."01 01 2015")
511 | (end-t . "20:00")
512 | (summary . "The summary")
513 | (description . "The description")
514 | (location . "location")))
515 | (org-caldav-select-tags ""))
516 | (cl-flet ((write-entry (uid level)
517 | (with-temp-buffer
518 | (org-mode) ; useful to set org-mode's
519 | ; internal variables
520 | (org-caldav-insert-org-event-or-todo
521 | (append entry `((uid . ,uid)
522 | (level . ,level))))
523 | (buffer-string))))
524 | (should (string-match (concat
525 | "\\*\\s-+The summary\n"
526 | "\\s-*:PROPERTIES:\n"
527 | "\\s-*:LOCATION: location\n"
528 | "\\s-*:END:\n"
529 | "\\s-*<2015-01-01 Thu 19:00-20:00>\n"
530 | "\\s-*The description\n")
531 | (write-entry nil nil)))
532 | (should (string-match (concat
533 | "\\*\\*\\s-+The summary\n"
534 | "\\s-*:PROPERTIES:\n"
535 | "\\s-*:LOCATION: location\n"
536 | "\\s-*:END:\n"
537 | "\\s-*<2015-01-01 Thu 19:00-20:00>\n"
538 | "\\s-*The description\n")
539 | (write-entry nil 2)))
540 | (should (string-match (concat "\\*\\s-+The summary\n"
541 | "\\s-*:PROPERTIES:\n"
542 | "\\s-*:ID:\\s-*1\n"
543 | "\\s-*:LOCATION: location\n"
544 | "\\s-*:END:\n"
545 | "\\s-*<2015-01-01 Thu 19:00-20:00>\n"
546 | "\\s-*The description\n")
547 | (write-entry "1" nil))))))
548 |
549 | (ert-deftest org-caldav-04-multiple-calendars ()
550 | (org-caldav-test-setup-temp-files)
551 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
552 | (insert org-caldav-test-org1)
553 | (save-buffer))
554 |
555 | (with-current-buffer (find-file-noselect org-caldav-test-second-orgfile)
556 | (insert org-caldav-test-org2)
557 | (save-buffer))
558 |
559 | ;; Delete calendar contents
560 | (let ((org-caldav-calendar-id (car org-caldav-test-calendar-names)))
561 | (org-caldav-test-set-up))
562 | (let ((org-caldav-calendar-id (nth 1 org-caldav-test-calendar-names)))
563 | (org-caldav-test-set-up))
564 |
565 | (let
566 | ((org-caldav-calendars
567 | `((:calendar-id ,(car org-caldav-test-calendar-names)
568 | :url ,org-caldav-url
569 | :files (,org-caldav-test-orgfile)
570 | :inbox ,org-caldav-test-orgfile)
571 | (:calendar-id ,(nth 1 org-caldav-test-calendar-names)
572 | :url ,org-caldav-url
573 | :files (,org-caldav-test-second-orgfile)
574 | :inbox ,org-caldav-test-second-orgfile))))
575 | (org-caldav-sync))
576 |
577 | ;; Check that each calendar has one event
578 | (let
579 | ((org-caldav-calendar-id (car org-caldav-test-calendar-names)))
580 | (should (org-caldav-get-event "orgcaldavtest@org1"))
581 | (should-error (org-caldav-get-event "orgcaldavtest-org2")))
582 |
583 | (let
584 | ((org-caldav-calendar-id (nth 1 org-caldav-test-calendar-names)))
585 | (should-error (org-caldav-get-event "orgcaldavtest@org1"))
586 | (should (org-caldav-get-event "orgcaldavtest-org2")))
587 |
588 | (org-caldav-test-cleanup)
589 | )
590 |
591 | (ert-deftest org-caldav-05-multiple-calendars-agenda-skip-function ()
592 | (org-caldav-test-setup-temp-files)
593 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
594 | (insert org-caldav-test-org1)
595 | (insert org-caldav-test-org2)
596 | (insert org-caldav-test-org3)
597 | (save-buffer))
598 |
599 | ;; Delete calendar contents
600 | (let ((org-caldav-calendar-id (car org-caldav-test-calendar-names)))
601 | (org-caldav-test-set-up))
602 | (let ((org-caldav-calendar-id (nth 1 org-caldav-test-calendar-names)))
603 | (org-caldav-test-set-up))
604 | (message "Starting sync")
605 | (let
606 | ((org-caldav-calendars
607 | `((:calendar-id ,(car org-caldav-test-calendar-names)
608 | :url ,org-caldav-url
609 | :skip-conditions (regexp ":sometag:")
610 | :files (,org-caldav-test-orgfile)
611 | :inbox ,org-caldav-test-orgfile)
612 | (:calendar-id ,(nth 1 org-caldav-test-calendar-names)
613 | :url ,org-caldav-url
614 | :skip-conditions (notregexp ":sometag:")
615 | :files (,org-caldav-test-orgfile)
616 | :inbox ,org-caldav-test-orgfile))))
617 | (org-caldav-sync))
618 |
619 | ;; Check that each calendar has one event
620 | (let
621 | ((org-caldav-calendar-id (car org-caldav-test-calendar-names)))
622 | (should (org-caldav-get-event "orgcaldavtest@org1"))
623 | (should (org-caldav-get-event "orgcaldavtest-org2"))
624 | (should-error (org-caldav-get-event "orgcaldavtest-org3")))
625 |
626 | (let
627 | ((org-caldav-calendar-id (nth 1 org-caldav-test-calendar-names)))
628 | (should (org-caldav-get-event "orgcaldavtest-org3"))
629 | (should-error (org-caldav-get-event "orgcaldavtest@org1"))
630 | (should-error (org-caldav-get-event "orgcaldavtest-org2")))
631 |
632 | ;; Make sure org-agenda-skip-function-global is not set permanently
633 | (should-not org-agenda-skip-function-global)
634 |
635 | (org-caldav-test-cleanup)
636 | )
637 |
638 | ;; Make sure setting org-caldav-files to 'nil' does not
639 | ;; do anything weird.
640 | (ert-deftest org-caldav-06-org-caldav-files-nil ()
641 | (message "Setting up temporary files")
642 | (org-caldav-test-setup-temp-files)
643 | (setq org-caldav-calendar-id (car org-caldav-test-calendar-names))
644 | ;; Set org-caldav-files to nil
645 | (setq org-caldav-files nil)
646 | (setq org-caldav-inbox org-caldav-test-inbox)
647 | (message "Setting up upstream calendar")
648 | (org-caldav-test-set-up)
649 | (message "Putting events")
650 | (org-caldav-test-put-events)
651 | (org-caldav-sync)
652 | ;; Events must still be in calendar
653 | (should (org-caldav-get-event "orgcaldavtest@cal1"))
654 | (should (org-caldav-get-event "orgcaldavtest-cal2"))
655 | ;; Sync result
656 | (should (or (equal org-caldav-test-sync-result
657 | org-caldav-sync-result)
658 | (equal (reverse org-caldav-test-sync-result)
659 | org-caldav-sync-result)))
660 | (org-caldav-test-cleanup))
661 |
662 | ;; Check that we are able to detect when an Org file was removed from
663 | ;; org-caldav-files between syncs.
664 | (ert-deftest org-caldav-07-detect-removed-file ()
665 | (message "Setting up temporary files")
666 | (org-caldav-test-setup-temp-files)
667 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
668 | (insert org-caldav-test-org1)
669 | (save-buffer))
670 | (with-current-buffer (find-file-noselect org-caldav-test-second-orgfile)
671 | (insert org-caldav-test-org2)
672 | (save-buffer))
673 | (setq org-caldav-calendar-id (car org-caldav-test-calendar-names))
674 | ;; Set org-caldav-files to nil
675 | (setq org-caldav-files (list org-caldav-test-orgfile org-caldav-test-second-orgfile))
676 | (setq org-caldav-inbox org-caldav-test-inbox)
677 | (message "Setting up upstream calendar")
678 | (org-caldav-test-set-up)
679 | (message "Putting events")
680 | (org-caldav-test-put-events)
681 | (message "1st sync")
682 | (org-caldav-sync)
683 | ;; Remove one of the files
684 | (setq org-caldav-files (list org-caldav-test-second-orgfile))
685 | ;; Sync again, binding yes-or-no-p to our test
686 | (setq org-caldav-test-seen-prompt nil)
687 | (let (octest-seen-prompt)
688 | (cl-letf (((symbol-function 'yes-or-no-p)
689 | (lambda (prompt)
690 | (setq octest-seen-prompt prompt) nil)))
691 | (message "2nd sync")
692 | (should-error (org-caldav-sync))
693 | (should (string-match "WARNING: Previously synced"
694 | octest-seen-prompt))))
695 | (org-caldav-test-cleanup))
696 |
697 | (ert-deftest org-caldav-test-multiline-location ()
698 | (with-temp-buffer
699 | (org-mode)
700 | (insert org-caldav-test-org1)
701 | (goto-char (point-min))
702 | (let ((orig-id (alist-get "ID" (org-entry-properties) nil nil #'string=)))
703 | (org-caldav-change-location "multi\nline")
704 | (let ((props (org-entry-properties)))
705 | (should (string= (alist-get "ID" props nil nil #'string=) orig-id))
706 | (should (string-match-p "multi" (alist-get "LOCATION" props nil nil #'string=)))
707 | (should (string-match-p "line" (alist-get "LOCATION" props nil nil #'string=)))))))
708 |
709 | (ert-deftest org-caldav-08-test-setting-sync-direction ()
710 | (org-caldav-test-setup-temp-files)
711 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
712 | (insert org-caldav-test-org1)
713 | (insert org-caldav-test-org2)
714 | (insert org-caldav-test-org3)
715 | (save-buffer))
716 |
717 | ;; Delete calendar contents
718 | (let ((org-caldav-calendar-id (car org-caldav-test-calendar-names)))
719 | (org-caldav-test-set-up))
720 | (let ((org-caldav-calendar-id (nth 1 org-caldav-test-calendar-names)))
721 | (org-caldav-test-set-up))
722 | (message "Starting sync")
723 | (let
724 | ((org-caldav-calendars
725 | ;; First only syncs Org to calendar
726 | `((:calendar-id ,(car org-caldav-test-calendar-names)
727 | :url ,org-caldav-url
728 | :files (,org-caldav-test-orgfile)
729 | :inbox nil ;; No inbox needed
730 | :sync-direction org->cal)
731 | ;; Second only syncs Calendar to Org inbox
732 | (:calendar-id ,(nth 1 org-caldav-test-calendar-names)
733 | :url ,org-caldav-url
734 | :files nil ;; No files needed
735 | :inbox ,org-caldav-test-inbox
736 | :sync-direction cal->org))))
737 | (org-caldav-sync)
738 |
739 | ;; First calendar should sync everything
740 | (let
741 | ((org-caldav-calendar-id (car org-caldav-test-calendar-names)))
742 | (should (org-caldav-get-event "orgcaldavtest@org1"))
743 | (should (org-caldav-get-event "orgcaldavtest-org2"))
744 | (should (org-caldav-get-event "orgcaldavtest-org3")))
745 |
746 | ;; Second calendar syncs nothing from org to cal
747 | (let
748 | ((org-caldav-calendar-id (nth 1 org-caldav-test-calendar-names)))
749 | (should-error (org-caldav-get-event "orgcaldavtest-org3"))
750 | (should-error (org-caldav-get-event "orgcaldavtest@org1"))
751 | (should-error (org-caldav-get-event "orgcaldavtest-org2")))
752 |
753 | ;; Put calendar events in both calendars
754 | (let ((org-caldav-calendar-id (car org-caldav-test-calendar-names)))
755 | (org-caldav-test-put-events))
756 | (let ((org-caldav-calendar-id (nth 1 org-caldav-test-calendar-names)))
757 | (org-caldav-test-put-events))
758 | ;; Sync again
759 | (org-caldav-sync)
760 |
761 | ;; Events are still there in first
762 | (let
763 | ((org-caldav-calendar-id (car org-caldav-test-calendar-names)))
764 | (should (org-caldav-get-event "orgcaldavtest@org1"))
765 | (should (org-caldav-get-event "orgcaldavtest-org2"))
766 | (should (org-caldav-get-event "orgcaldavtest-org3")))
767 | ;; Events still not there in second
768 | (let
769 | ((org-caldav-calendar-id (nth 1 org-caldav-test-calendar-names)))
770 | (should-error (org-caldav-get-event "orgcaldavtest-org3"))
771 | (should-error (org-caldav-get-event "orgcaldavtest@org1"))
772 | (should-error (org-caldav-get-event "orgcaldavtest-org2")))
773 | ;; But second should have new events in inbox
774 | (with-current-buffer (find-file-noselect org-caldav-test-inbox)
775 | (goto-char (point-min))
776 | (should (re-search-forward org-caldav-test-ics1-org nil t))
777 | (goto-char (point-min))
778 | (should (re-search-forward org-caldav-test-ics2-org nil t))))
779 |
780 | (org-caldav-test-cleanup)
781 | )
782 |
783 | ;; Based on org-caldav-01-sync-test, but modified for todos
784 | (ert-deftest org-caldav-09-sync-test-todo ()
785 | (let ((org-caldav-sync-todo t)
786 | (org-icalendar-include-todo 'all))
787 | (message "Setting up temporary files")
788 | (org-caldav-test-setup-temp-files)
789 | (setq org-caldav-calendar-id (car org-caldav-test-calendar-names))
790 | ;; Set up data for org-caldav.
791 | (setq org-caldav-files (list org-caldav-test-orgfile))
792 | (setq org-caldav-inbox org-caldav-test-inbox)
793 |
794 | (message "Cleaning up upstream calendars")
795 | (org-caldav-test-set-up)
796 |
797 | ;; Set up orgfile.
798 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
799 | (insert org-caldav-test-org4 "\n")
800 | (insert org-caldav-test-org5 "\n")
801 | (save-buffer))
802 |
803 | (message "Putting events")
804 | (let ((org-caldav-calendar-preamble org-caldav-test-preamble)
805 | events)
806 | (with-temp-buffer
807 | (insert org-caldav-test-ics3)
808 | (should (org-caldav-put-event (current-buffer)))
809 | (erase-buffer)
810 | (insert org-caldav-test-ics4)
811 | (should (org-caldav-put-event (current-buffer))))
812 | (let ((events (org-caldav-get-event-etag-list)))
813 | (should (assoc "orgcaldavtest@cal3" events))
814 | (should (assoc "orgcaldavtest-cal4" events))))
815 |
816 | (message "1st SYNC")
817 | ;; Do the sync.
818 | (org-caldav-sync)
819 | ;;;; Check result.
820 | (should (= (length (org-caldav-get-event-etag-list)) 4))
821 | (should (member `(,org-caldav-calendar-id "orgcaldavtest@cal3" new-in-cal cal->org)
822 | org-caldav-sync-result))
823 | (should (member `(,org-caldav-calendar-id "orgcaldavtest-cal4" new-in-cal cal->org)
824 | org-caldav-sync-result))
825 | (should (member `(,org-caldav-calendar-id "orgcaldavtest@org4" new-in-org org->cal)
826 | org-caldav-sync-result))
827 | (should (member `(,org-caldav-calendar-id "orgcaldavtest-org5" new-in-org org->cal)
828 | org-caldav-sync-result))
829 | ;; State file should exist now.
830 | (should (file-exists-p
831 | (org-caldav-sync-state-filename org-caldav-calendar-id)))
832 | (let ((calevents (org-caldav-get-event-etag-list)))
833 | (should (= (length calevents) (length org-caldav-test-alltodos)))
834 | ;; Org events should be in cal.
835 | (dolist (cur org-caldav-test-alltodos)
836 | (should (assoc cur calevents))))
837 | ;; Cal events should be in Org.
838 | (with-current-buffer (find-file-noselect org-caldav-test-inbox)
839 | (goto-char (point-min))
840 | (should (re-search-forward org-caldav-test-ics3-org nil t))
841 | (goto-char (point-min))
842 | (should (re-search-forward org-caldav-test-ics4-org nil t)))
843 |
844 | (message "2nd SYNC")
845 | ;; Sync again.
846 | (org-caldav-sync)
847 | ;; Nothing should have happened.
848 | (should-not org-caldav-sync-result)
849 |
850 | (message "Changing org events")
851 | ;; Now change events in Org
852 | (with-current-buffer (find-buffer-visiting org-caldav-test-orgfile)
853 | (goto-char (point-min))
854 | (search-forward "TODO A test task from Org")
855 | (replace-match "DONE Finished test task from Org")
856 | (search-forward "SCHEDULED:")
857 | (replace-match "CLOSED: [2012-12-24 Mon 00:00] SCHEDULED:" t))
858 | (with-current-buffer (find-buffer-visiting org-caldav-test-inbox)
859 | (goto-char (point-min))
860 | (search-forward "TODO [#B] Another test task from iCal")
861 | (replace-match "DONE [#C] Another test task from iCal was finished!")
862 | (search-forward "DEADLINE:")
863 | (replace-match "CLOSED: [2012-12-20 Thu 00:00] DEADLINE:" t))
864 |
865 | ;; And sync...
866 | (message "3rd SYNC")
867 | (org-caldav-sync)
868 | (should (equal `((,org-caldav-calendar-id "orgcaldavtest-cal4" changed-in-org org->cal)
869 | (,org-caldav-calendar-id "orgcaldavtest@org4" changed-in-org org->cal))
870 | org-caldav-sync-result))
871 |
872 | ;; Check if those events correctly end up in calendar.
873 | (with-current-buffer (org-caldav-get-event "orgcaldavtest-cal4")
874 | (goto-char (point-min))
875 | (save-excursion
876 | (should (search-forward "SUMMARY:Another test task from iCal was finished!")))
877 | (save-excursion
878 | (should (search-forward "PRIORITY:9")))
879 | (save-excursion
880 | (should (search-forward "STATUS:COMPLETED")))
881 | (save-excursion
882 | (should (re-search-forward "COMPLETED.*:20121220T000000"))))
883 |
884 | (with-current-buffer (org-caldav-get-event "orgcaldavtest@org4")
885 | (goto-char (point-min))
886 | (save-excursion
887 | (should (search-forward "SUMMARY:Finished test task from Org")))
888 | (save-excursion
889 | (should (search-forward "PRIORITY:0")))
890 | (save-excursion
891 | (should (search-forward "STATUS:COMPLETED")))
892 | (save-excursion
893 | (should (re-search-forward "COMPLETED.*:20121224T000000"))))
894 |
895 | ;; Now change events in Cal
896 | (message "Changing events in calendar")
897 | (with-current-buffer (org-caldav-get-event "orgcaldavtest@cal3")
898 | (goto-char (point-min))
899 | (save-excursion
900 | (search-forward "SUMMARY:A test task from iCal")
901 | (replace-match "SUMMARY:Changed A test task from iCal"))
902 | (save-excursion
903 | (re-search-forward "DTSTART\\(;.*\\)?:\\(20121223\\)")
904 | (replace-match "20121224" nil nil nil 2))
905 | (save-excursion
906 | (when (re-search-forward "SEQUENCE:\\s-*\\([0-9]+\\)" nil t)
907 | (replace-match (number-to-string
908 | (1+ (string-to-number (match-string 1))))
909 | nil t nil 1)))
910 | (message "PUTting first changed event")
911 | (should (org-caldav-save-resource
912 | (concat (org-caldav-events-url) (url-hexify-string "orgcaldavtest@cal3.ics"))
913 | (encode-coding-string (buffer-string) 'utf-8))))
914 |
915 | (with-current-buffer (org-caldav-get-event "orgcaldavtest-org5")
916 | (goto-char (point-min))
917 | (save-excursion
918 | (search-forward "SUMMARY:Another test task from Org")
919 | (replace-match "SUMMARY:Changed Another test task from Org"))
920 | (save-excursion
921 | (search-forward "STATUS:NEEDS-ACTION")
922 | (replace-match "STATUS:COMPLETED\nCOMPLETED:20121224T000000"))
923 | (save-excursion
924 | (search-forward "PERCENT-COMPLETE:0")
925 | (replace-match "PERCENT-COMPLETE:100"))
926 | (save-excursion
927 | (when (re-search-forward "SEQUENCE:\\s-*\\([0-9]+\\)" nil t)
928 | (replace-match (number-to-string
929 | (1+ (string-to-number (match-string 1))))
930 | nil t nil 1)))
931 | (message "PUTting second changed event")
932 | (should (org-caldav-save-resource
933 | (concat (org-caldav-events-url) "orgcaldavtest-org5.ics")
934 | (encode-coding-string (buffer-string) 'utf-8))))
935 |
936 | ;; Aaaand sync!
937 | (message "4th SYNC")
938 | (org-caldav-sync)
939 |
940 | (should (equal `((,org-caldav-calendar-id "orgcaldavtest@cal3" changed-in-cal cal->org)
941 | (,org-caldav-calendar-id "orgcaldavtest-org5" changed-in-cal cal->org))
942 | org-caldav-sync-result))
943 |
944 | (with-current-buffer (find-file-noselect org-caldav-test-inbox)
945 | (goto-char (point-min))
946 | (should (re-search-forward
947 | "* TODO Changed A test task from iCal
948 | \\s-*SCHEDULED: <2012-12-24 Mon>
949 | \\s-*:PROPERTIES:
950 | \\s-*:ID:\\s-*orgcaldavtest@cal3
951 | \\s-*:END:
952 | \\s-*ical test task 1")))
953 |
954 | (message "Changing description in icalendar")
955 | (with-current-buffer (org-caldav-get-event "orgcaldavtest@cal3")
956 | (goto-char (point-min))
957 | (save-excursion
958 | (search-forward "DESCRIPTION:ical test task 1")
959 | (replace-match "DESCRIPTION:ical test task 1 modified"))
960 |
961 | (message "PUTting changed description")
962 | (should (org-caldav-save-resource
963 | (concat (org-caldav-events-url) (url-hexify-string "orgcaldavtest@cal3.ics"))
964 | (encode-coding-string (buffer-string) 'utf-8))))
965 |
966 | (message "5th SYNC")
967 | (let ((org-caldav-sync-changes-to-org 'all))
968 | (org-caldav-sync))
969 |
970 | (should (equal `((,org-caldav-calendar-id "orgcaldavtest@cal3"
971 | changed-in-cal cal->org))
972 | org-caldav-sync-result))
973 |
974 | (with-current-buffer (find-file-noselect org-caldav-test-inbox)
975 | (goto-char (point-min))
976 | (should (re-search-forward
977 | "* TODO Changed A test task from iCal
978 | \\s-*SCHEDULED: <2012-12-24 Mon>
979 | \\s-*:PROPERTIES:
980 | \\s-*:ID:\\s-*orgcaldavtest@cal3
981 | \\s-*:END:
982 | \\s-*ical test task 1 modified")))
983 |
984 | (message "Deleting event in Org")
985 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
986 | (goto-char (point-min))
987 | (message (buffer-string))
988 | (should (search-forward
989 | "* DONE [#B] Changed Another test task from Org :test:
990 | CLOSED: [2012-12-24 Mon 00:00] DEADLINE: <2012-12-23 Sun> SCHEDULED: <2012-12-19 Wed>
991 | :PROPERTIES:
992 | :ID: orgcaldavtest-org5
993 | :END:
994 | Org task 2
995 |
996 | "))
997 | ;; Delete this event in Org
998 | (replace-match ""))
999 |
1000 | ;; Sync
1001 | (message "6th SYNC")
1002 | (org-caldav-sync)
1003 |
1004 | ;; Event should be deleted in calendar
1005 | (let ((calevents (org-caldav-get-event-etag-list)))
1006 | (should (= (length calevents) 3))
1007 | (should-not (assoc '"orgcaldavtest-org5" calevents)))
1008 | (should
1009 | (equal org-caldav-sync-result
1010 | `((,org-caldav-calendar-id "orgcaldavtest-org5" deleted-in-org removed-from-cal))))
1011 | (should-not
1012 | (assoc '"orgcaldavtest-org5" org-caldav-event-list))
1013 |
1014 | ;; Delete event in calendar
1015 | (message "Delete event in calendar")
1016 | (should (org-caldav-delete-event "orgcaldavtest@org4"))
1017 | ;; Sync one last time
1018 | (message "7th SYNC")
1019 | (let ((org-caldav-delete-org-entries 'always))
1020 | (org-caldav-sync))
1021 |
1022 | (should
1023 | (equal org-caldav-sync-result
1024 | `((,org-caldav-calendar-id "orgcaldavtest@org4" deleted-in-cal removed-from-org))))
1025 | ;; There shouldn't be anything left in that buffer
1026 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
1027 | (goto-char (point-min))
1028 | (should-not (re-search-forward "[:alnum:]" nil t)))
1029 | (should-not
1030 | (assoc '"orgcaldavtest@org4" org-caldav-event-list))
1031 |
1032 | (org-caldav-test-cleanup)))
1033 |
1034 | (ert-deftest org-caldav-10-test-description-cleanup ()
1035 | (let ((org-caldav-sync-todo t)
1036 | (org-icalendar-include-todo 'all))
1037 | (message "Setting up temporary files")
1038 | (org-caldav-test-setup-temp-files)
1039 | (setq org-caldav-calendar-id (car org-caldav-test-calendar-names))
1040 | ;; Set up data for org-caldav.
1041 | (setq org-caldav-files (list org-caldav-test-orgfile))
1042 | (setq org-caldav-inbox org-caldav-test-inbox)
1043 |
1044 | (message "Cleaning up upstream calendars")
1045 | (org-caldav-test-set-up)
1046 |
1047 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
1048 | (insert "* TODO Test links preserved in description
1049 | :PROPERTIES:
1050 | :ID: org-caldav-10-test-links-preserved
1051 | :END:
1052 |
1053 | https://orgmode.org
1054 |
1055 | * Test timestamp is filtered from description
1056 | :PROPERTIES:
1057 | :ID: org-caldav-10-test-timestamp-filtered
1058 | :END:
1059 | <2023-01-06 Fri>
1060 |
1061 | * 2nd test for timestamp filtering
1062 | :PROPERTIES:
1063 | :ID: org-caldav-10-test-timestamp-filtered2
1064 | :END:
1065 | <2023-01-06 Fri 14:00>--<2023-01-06 Fri 15:00>")
1066 | (save-buffer))
1067 |
1068 | (org-caldav-sync)
1069 |
1070 | (with-current-buffer (org-caldav-get-event
1071 | "org-caldav-10-test-links-preserved")
1072 | (goto-char (point-min))
1073 | (save-excursion
1074 | (should (search-forward "DESCRIPTION:"))))
1075 |
1076 | (with-current-buffer (org-caldav-get-event
1077 | "org-caldav-10-test-timestamp-filtered")
1078 | (goto-char (point-min))
1079 | (save-excursion
1080 | (should (not (re-search-forward
1081 | "DESCRIPTION:.*2023-01-06" nil t)))))
1082 |
1083 | (with-current-buffer (org-caldav-get-event
1084 | "org-caldav-10-test-timestamp-filtered2")
1085 | (goto-char (point-min))
1086 | (save-excursion
1087 | (should (not (re-search-forward
1088 | "DESCRIPTION:.*2023-01-06" nil t))))))
1089 |
1090 | (org-caldav-test-cleanup))
1091 |
1092 | (ert-deftest org-caldav-11-test-sync-unsaved ()
1093 | (org-caldav-test-setup-temp-files)
1094 | (setq org-caldav-files (list org-caldav-test-orgfile))
1095 | (setq org-caldav-inbox org-caldav-test-inbox)
1096 | (setq org-caldav-calendar-id (car org-caldav-test-calendar-names))
1097 | (org-caldav-test-set-up)
1098 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
1099 | ;; Make sure the file exists
1100 | (save-buffer)
1101 | ;; Add an event, but don't save it
1102 | (insert org-caldav-test-org2))
1103 | (org-caldav-sync))
1104 |
1105 | (ert-deftest org-caldav-12-test-doublesync-created-id ()
1106 | (org-caldav-test-setup-temp-files)
1107 | (setq org-caldav-files (list org-caldav-test-orgfile))
1108 | (setq org-caldav-inbox org-caldav-test-inbox)
1109 | (setq org-caldav-calendar-id (car org-caldav-test-calendar-names))
1110 | (org-caldav-test-set-up)
1111 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
1112 | (insert "* This is a test without ID\n"
1113 | " <2009-08-08 Sat 10:00>\n whatever\n foo\n bar\n")
1114 | (save-buffer))
1115 | ;; First sync creates an ID for the event
1116 | (org-caldav-sync)
1117 | ;; Test if second sync can find the ID we created. If not, the test
1118 | ;; will exit with org-caldav error "Could not find UID"
1119 | (org-caldav-sync))
1120 |
1121 | (defun org-caldav-test-input-output-entry (input output)
1122 | "Helper function to test Org->Cal->Org preserves an entry.
1123 | Clear the Org files and iCalendar. Then add INPUT to
1124 | `org-caldav-test-inbox'. Then sync it to iCalendar. Then reset
1125 | the Org files and database, pull from iCalendar, and check that
1126 | the result matches the regular expression OUTPUT."
1127 | (message "Setting up temporary files")
1128 | (org-caldav-test-setup-temp-files)
1129 | (setq org-caldav-calendar-id (car org-caldav-test-calendar-names))
1130 | ;; Set up data for org-caldav.
1131 | (setq org-caldav-files (list org-caldav-test-orgfile))
1132 | (setq org-caldav-inbox org-caldav-test-inbox)
1133 |
1134 | (message "Cleaning up upstream calendars")
1135 | (org-caldav-test-set-up)
1136 |
1137 | ;; Set up orgfile.
1138 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
1139 | (insert input)
1140 | (save-buffer))
1141 |
1142 | (message "Sync")
1143 | ;; Sync event to iCal
1144 | (org-caldav-sync)
1145 |
1146 | ;; Reset org-caldav sync state
1147 | (delete-file (org-caldav-sync-state-filename org-caldav-calendar-id))
1148 | (setq org-caldav-event-list nil)
1149 | (setq org-caldav-sync-result nil)
1150 | ;; Also delete the event in org
1151 | ;;(delete-file org-caldav-test-orgfile)
1152 | (with-current-buffer (find-file-noselect org-caldav-test-orgfile)
1153 | (erase-buffer)
1154 | (save-buffer))
1155 |
1156 | ;; Sync event back to inbox
1157 | (org-caldav-sync)
1158 | (with-current-buffer (find-file-noselect org-caldav-test-inbox)
1159 | (goto-char (point-min))
1160 | (should (re-search-forward
1161 | output))))
1162 |
1163 | (ert-deftest org-caldav-13a-test-simple-repeating-event ()
1164 | (org-caldav-test-input-output-entry
1165 | "* Simple repeating event
1166 | :PROPERTIES:
1167 | :ID: test-repeating-event
1168 | :END:
1169 | <2024-05-25 Sat +7d>"
1170 | "* Simple repeating event
1171 | :PROPERTIES:
1172 | :ID:\\s-+test-repeating-event
1173 | :END:
1174 | <2024-05-25 Sat \\+7d>"))
1175 |
1176 | (ert-deftest org-caldav-13b-test-simple-repeating-todo-dtstart ()
1177 | (let ((org-caldav-sync-todo t)
1178 | (org-icalendar-include-todo 'all))
1179 | (org-caldav-test-input-output-entry
1180 | "* TODO Simple repeating scheduled todo
1181 | SCHEDULED: <2024-06-08 Sat +3d>
1182 | :PROPERTIES:
1183 | :ID: test-simple-repeating-todo-dtstart
1184 | :END:"
1185 | "* TODO Simple repeating scheduled todo
1186 | SCHEDULED: <2024-06-08 Sat \\+3d>
1187 | :PROPERTIES:
1188 | :ID:\\s-+test-simple-repeating-todo-dtstart
1189 | :END:")))
1190 |
1191 | (ert-deftest org-caldav-13c-test-simple-repeating-todo-dtstart-due ()
1192 | (let ((org-caldav-sync-todo t)
1193 | (org-icalendar-include-todo 'all))
1194 | (org-caldav-test-input-output-entry
1195 | "* TODO Simple repeating scheduled todo with deadline
1196 | SCHEDULED: <2024-06-08 Sat +3d> DEADLINE: <2024-06-10 Mon +3d>
1197 | :PROPERTIES:
1198 | :ID: test-simple-repeating-todo-dtstart-due
1199 | :END:"
1200 | "* TODO Simple repeating scheduled todo with deadline
1201 | \\(SCHEDULED: <2024-06-08 Sat \\+3d> DEADLINE: <2024-06-10 Mon \\+3d>\\|DEADLINE: <2024-06-10 Mon \\+3d> SCHEDULED: <2024-06-08 Sat \\+3d>\\)
1202 | :PROPERTIES:
1203 | :ID:\\s-+test-simple-repeating-todo-dtstart-due
1204 | :END:")))
1205 |
--------------------------------------------------------------------------------