From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp11.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms9.migadu.com with LMTPS id OJQhMOp/JmTYywAASxT56A (envelope-from ) for ; Fri, 31 Mar 2023 08:38:34 +0200 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp11.migadu.com with LMTPS id oC8oMOp/JmTW3AAA9RJhRA (envelope-from ) for ; Fri, 31 Mar 2023 08:38:34 +0200 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 521D72A103 for ; Fri, 31 Mar 2023 08:38:34 +0200 (CEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pi7jt-0003Vv-1W; Fri, 31 Mar 2023 01:55:53 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pi7jo-0003VX-87 for emacs-orgmode@gnu.org; Fri, 31 Mar 2023 01:55:50 -0400 Received: from mail-pj1-x1035.google.com ([2607:f8b0:4864:20::1035]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1pi7jl-0005SW-Pl for emacs-orgmode@gnu.org; Fri, 31 Mar 2023 01:55:47 -0400 Received: by mail-pj1-x1035.google.com with SMTP id l7so19440218pjg.5 for ; Thu, 30 Mar 2023 22:55:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; t=1680242144; h=mime-version:message-id:date:references:in-reply-to:subject:cc:to :from:from:to:cc:subject:date:message-id:reply-to; bh=jDDlaxN/il1ZNkt8QF2UBJRtba7oXbwEfeJhjhekexU=; b=E8j8pVGqbRQR10Ocl21Oda1MFPJaiKSorTvq2blBMJwm9Uamdbs6Nbt80NirHcJBLf mEHlFt7VDQKfgDBEAuu4D1vb3gBRilb5eQjUxRbdgIMZ/aSIua06xo45tlNhgbY79+uW LxGt8924NpJ8JzeE7UTR1F4PRQcccJNWw34fi/4s0H4ZAD48Q1Ih8IoqskH+xkVdVWnB XiROdMuXeaShUJUwkHq7cAU9oB73FMva+LsP5Kand900FC7sZCl+PG/BbdLalWW11CNG jdsJEQhmCH7AVOw4UHhKE5du22nVXae/Z5jQZkuCStLP1ZvjH8r35xzjvJ3MmlrCgFel 4EOQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; t=1680242144; h=mime-version:message-id:date:references:in-reply-to:subject:cc:to :from:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=jDDlaxN/il1ZNkt8QF2UBJRtba7oXbwEfeJhjhekexU=; b=yqN4lmhLfGLLBhOAImi3G1ZW0S+ZJ1z5FL+VkDcBvpgz9LeC6cJ6ayhPSHKPrEuOHt d424kCB4zIcSWDMwfLpiQgxpI3pYPLhwDisk27qca77FkINv+TkjNQu/GxeLRLUjlTuZ dOEBO+6u4RcYiovSWhhGxpomyJQUWCwXemHwk3vo5T5yBzQDFuXc4Z3yhoIwWqrfe9dC cGyPWb5Ehc0ODkExplkm5cOblFP+tiu2z302bty+/tzJNxW/D3Fk7c0oTZ145GugHw8k 5jGoXXb8J4P0KYRrWFqvEECDXZKmVMyshrNYuShIkbTrpOSBz081FGc+3qIY30LRmaGS du4w== X-Gm-Message-State: AAQBX9deG7p3NGHSkOu+nMWFknCJd/5TkIWZiwI+labHVm/UbwNKwrad nwQ8wp+qBM3kzqVF0u/85ys= X-Google-Smtp-Source: AKy350Z0QTQ7fudJrvfIJuZq7nmhDFLGUTvf/VhJFiIwaq7MN5LiJEhR3bhiEMCICAdfjMa3EiUO6g== X-Received: by 2002:a17:90b:3e81:b0:23a:ad68:25a7 with SMTP id rj1-20020a17090b3e8100b0023aad6825a7mr29334833pjb.2.1680242143618; Thu, 30 Mar 2023 22:55:43 -0700 (PDT) Received: from localhost (157-131-78-143.fiber.dynamic.sonic.net. [157.131.78.143]) by smtp.gmail.com with ESMTPSA id x14-20020a170902820e00b001a1ccb37847sm678983pln.146.2023.03.30.22.55.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 30 Mar 2023 22:55:42 -0700 (PDT) From: Jack Kamm To: Ihor Radchenko Cc: emacs-orgmode@gnu.org, mail@nicolasgoaziou.fr Subject: Re: [RFC] ox-icalendar: Unscheduled tasks & repeating tasks In-Reply-To: <87o7oetneo.fsf@localhost> References: <874jq75okg.fsf@gmail.com> <87o7oetneo.fsf@localhost> Date: Thu, 30 Mar 2023 22:55:41 -0700 Message-ID: <87r0t5jwgy.fsf@gmail.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=2607:f8b0:4864:20::1035; envelope-from=jackkamm@gmail.com; helo=mail-pj1-x1035.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: emacs-orgmode-bounces+larch=yhetil.org@gnu.org X-Migadu-Country: US X-Migadu-Flow: FLOW_IN ARC-Seal: i=1; s=key1; d=yhetil.org; t=1680244714; a=rsa-sha256; cv=none; b=KFCBxhF33RVrc7wqQKegdmCrg5iYz655dtuXfqrq83XrxWKFeR5/r6LDtegqZfGI+99mVZ ODDoaS6iMiO8RUHruFqecBnudu5AUwEWAQEI/FyctZj3xj1VkxsL0usDLKwROsv2RJhmsj hea5QO5FwbW54CIS2D+xt4HWhN1T0hqmJKx3UZUdI1y4c+eu4KskKbP2ikGBi6zFs0abOJ a80YG3eg1yuamBLOrULWzkH+gYPy8jnV9gnWFv5PFgCwHu61DJ5w4haI1+XUaMqcog0JlO 6FJJwGaw8iW0rGPt/xzB3C6/ui7OGD8X/PbyY70HDLvFKp99shCXrhDYp+4fcg== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20210112 header.b=E8j8pVGq; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none); spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1680244714; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:in-reply-to:in-reply-to: references:references:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=JD0dhkQmKR1A/ry+wG4T5p3M0f4E99a6iD1RVFC8ZMg=; b=DANItjSYnb7bd5dcA9oBiOcZDB5X2EATt4qO3xiiQOR81deE3uSm/0Q0uvSAUDhmj3Yomi jKP6+3A1WuwZ0tCK5gyHl2h6j6Yjpz51z8mvn/ZE3JWTk8KpkB+1U5Hw4gViPH0RrvLv5X iqcgkYREU7Ys9YfVUwlWAxxqbMOcQNnIFKFhBZ+1ZGF8X4+naXO9ogrqEqAO5lNHUZQj47 gomyYXkLTEAd3brlblgh2nbb8Pzv6VDFnsx84oJjN1jrAGL3TWvcJBwC/s9d0sbUcxOkox iLZLvw0AvHDc0lGwPVj7FkypuWN/4sRPHw/8w7//KqG0ICm7fuzYfZMbpKTWIA== X-Migadu-Spam-Score: 5.98 X-Migadu-Scanner: scn1.migadu.com Authentication-Results: aspmx1.migadu.com; dkim=fail ("body hash did not verify") header.d=gmail.com header.s=20210112 header.b=E8j8pVGq; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none); spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" X-Spam-Score: 5.98 X-Migadu-Queue-Id: 521D72A103 X-TUID: O00nB7OLuyh8 --=-=-= Content-Type: text/plain Ihor Radchenko writes: > Side note: here, and in other places, we use "\n" as end of line. Yet, > for example > https://icalendar.org/iCalendar-RFC-5545/3-8-2-4-date-time-start.html > prescribes CRLF (\r\n). Also, see > https://orgmode.org/list/87ilgljv6i.fsf@localhost > If you are familiar with iCalendar spec, may you look through the > ox-icalendar code and check other places where we do not conform to the > newline spec? org-icalendar--vtodo is wrapped in org-icalendar-fold-string, so this "\n" gets converted to CRLF later on. However you are right that other parts of the iCalendar export have inconsistent line endings. Currently, VEVENT and VTODO components have the correct CRLF endings, but the other parts of the VCALENDAR do not (such as the preamble). I like your suggestion in the above thread to just wrap the whole export in `org-icalendar-fold-string'. Though I think it's slightly nicer to do it in `org-icalendar--vcalendar' instead of `org-icalendar-template'. So, I've attached a standalone patch to do this. It also fixes an issue with `org-icalendar-fold-string' where the last newline was missing "\r", and adds a unit test. Note that fixing the line endings causes a surprising compatibility issue with org-caldav. I fixed this problem on the org-caldav side, and made a note in ORG-NEWS. --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0001-ox-icalendar-Use-consistent-CRLF-line-endings.patch >From 712a4ef09b63b2f6bdec2a3967712be912dce0d2 Mon Sep 17 00:00:00 2001 From: Jack Kamm Date: Thu, 30 Mar 2023 22:19:09 -0700 Subject: [PATCH] ox-icalendar: Use consistent CRLF line endings Fixes issue where the ox-icalendar export uses an inconsistent mix of dos and unix style line endings. * lisp/ox-icalendar.el (org-icalendar-fold-string): Don't use "\r" during the string construction, instead replace "\n" with "\r\n" after string has been created. This fixes an issue where the final "\n" added by `org-element-normalize-string' was missing "\r". (org-icalendar--vevent): Remove call to `org-icalendar-fold-string'. (org-icalendar--vtodo): Remove call to `org-icalendar-fold-string'. (org-icalendar--vcalendar): Wrap in `org-icalendar-fold-string'. * testing/lisp/test-ox-icalendar.el: New file for unit tests of ox-icalendar. Add an initial test for CRLF line endings. See also: https://list.orgmode.org/87o7oetneo.fsf@localhost/T/#m3e3eb80f9fc51ba75854b33ebfe9ecdefa2ded24 https://list.orgmode.org/orgmode/87ilgljv6i.fsf@localhost/ --- etc/ORG-NEWS | 12 +++ lisp/ox-icalendar.el | 159 +++++++++++++++--------------- testing/lisp/test-ox-icalendar.el | 46 +++++++++ 3 files changed, 138 insertions(+), 79 deletions(-) create mode 100644 testing/lisp/test-ox-icalendar.el diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index ac233a986..9f7d01707 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -23,6 +23,18 @@ If you still want to use python-mode with ob-python, you might consider [[https://gitlab.com/jackkamm/ob-python-mode-mode][ob-python-mode-mode]], where the code to support python-mode has been ported to. +*** =ox-icalendar.el= line ending fix may affect downstream packages + +iCalendar export now uses dos-style CRLF ("\r\n") line endings +throughout, as required by the iCalendar specification (RFC 5545). +Previously, the export used an inconsistent mix of dos and unix line +endings. + +This might cause errors in external packages that parse output from +ox-icalendar. In particular, older versions of org-caldav may +encounter issues, and users are advised to update to the most recent +version of org-caldav. See [[https://github.com/dengste/org-caldav/commit/618bf4cdc9be140ca1993901d017b7f18297f1b8][this org-caldav commit]] for more information. + ** New and changed options *** New ~org-cite-natbib-export-bibliography~ option defining fallback bibliography style diff --git a/lisp/ox-icalendar.el b/lisp/ox-icalendar.el index 81a77a770..06e90d032 100644 --- a/lisp/ox-icalendar.el +++ b/lisp/ox-icalendar.el @@ -526,25 +526,27 @@ (defun org-icalendar-cleanup-string (s) (defun org-icalendar-fold-string (s) "Fold string S according to RFC 5545." - (org-element-normalize-string - (mapconcat - (lambda (line) - ;; Limit each line to a maximum of 75 characters. If it is - ;; longer, fold it by using "\r\n " as a continuation marker. - (let ((len (length line))) - (if (<= len 75) line - (let ((folded-line (substring line 0 75)) - (chunk-start 75) - chunk-end) - ;; Since continuation marker takes up one character on the - ;; line, real contents must be split at 74 chars. - (while (< (setq chunk-end (+ chunk-start 74)) len) - (setq folded-line - (concat folded-line "\r\n " - (substring line chunk-start chunk-end)) - chunk-start chunk-end)) - (concat folded-line "\r\n " (substring line chunk-start)))))) - (org-split-string s "\n") "\r\n"))) + (replace-regexp-in-string + "\n" "\r\n" + (org-element-normalize-string + (mapconcat + (lambda (line) + ;; Limit each line to a maximum of 75 characters. If it is + ;; longer, fold it by using "\r\n " as a continuation marker. + (let ((len (length line))) + (if (<= len 75) line + (let ((folded-line (substring line 0 75)) + (chunk-start 75) + chunk-end) + ;; Since continuation marker takes up one character on the + ;; line, real contents must be split at 74 chars. + (while (< (setq chunk-end (+ chunk-start 74)) len) + (setq folded-line + (concat folded-line "\n " + (substring line chunk-start chunk-end)) + chunk-start chunk-end)) + (concat folded-line "\n " (substring line chunk-start)))))) + (org-split-string s "\n") "\n")))) @@ -736,31 +738,30 @@ (\"PUBLIC\", \"CONFIDENTIAL\", and \"PRIVATE\") are predefined, others should be treated as \"PRIVATE\" if they are unknown to the iCalendar server. Return VEVENT component as a string." - (org-icalendar-fold-string - (if (eq (org-element-property :type timestamp) 'diary) - (org-icalendar-transcode-diary-sexp - (org-element-property :raw-value timestamp) uid summary) - (concat "BEGIN:VEVENT\n" - (org-icalendar-dtstamp) "\n" - "UID:" uid "\n" - (org-icalendar-convert-timestamp timestamp "DTSTART" nil timezone) "\n" - (org-icalendar-convert-timestamp timestamp "DTEND" t timezone) "\n" - ;; RRULE. - (when (org-element-property :repeater-type timestamp) - (format "RRULE:FREQ=%s;INTERVAL=%d\n" - (cl-case (org-element-property :repeater-unit timestamp) - (hour "HOURLY") (day "DAILY") (week "WEEKLY") - (month "MONTHLY") (year "YEARLY")) - (org-element-property :repeater-value timestamp))) - "SUMMARY:" summary "\n" - (and (org-string-nw-p location) (format "LOCATION:%s\n" location)) - (and (org-string-nw-p class) (format "CLASS:%s\n" class)) - (and (org-string-nw-p description) - (format "DESCRIPTION:%s\n" description)) - "CATEGORIES:" categories "\n" - ;; VALARM. - (org-icalendar--valarm entry timestamp summary) - "END:VEVENT")))) + (if (eq (org-element-property :type timestamp) 'diary) + (org-icalendar-transcode-diary-sexp + (org-element-property :raw-value timestamp) uid summary) + (concat "BEGIN:VEVENT\n" + (org-icalendar-dtstamp) "\n" + "UID:" uid "\n" + (org-icalendar-convert-timestamp timestamp "DTSTART" nil timezone) "\n" + (org-icalendar-convert-timestamp timestamp "DTEND" t timezone) "\n" + ;; RRULE. + (when (org-element-property :repeater-type timestamp) + (format "RRULE:FREQ=%s;INTERVAL=%d\n" + (cl-case (org-element-property :repeater-unit timestamp) + (hour "HOURLY") (day "DAILY") (week "WEEKLY") + (month "MONTHLY") (year "YEARLY")) + (org-element-property :repeater-value timestamp))) + "SUMMARY:" summary "\n" + (and (org-string-nw-p location) (format "LOCATION:%s\n" location)) + (and (org-string-nw-p class) (format "CLASS:%s\n" class)) + (and (org-string-nw-p description) + (format "DESCRIPTION:%s\n" description)) + "CATEGORIES:" categories "\n" + ;; VALARM. + (org-icalendar--valarm entry timestamp summary) + "END:VEVENT"))) (defun org-icalendar--vtodo (entry uid summary location description categories timezone class) @@ -786,34 +787,33 @@ (defun org-icalendar--vtodo :day-start (nth 3 now) :month-start (nth 4 now) :year-start (nth 5 now))))))) - (org-icalendar-fold-string - (concat "BEGIN:VTODO\n" - "UID:TODO-" uid "\n" - (org-icalendar-dtstamp) "\n" - (org-icalendar-convert-timestamp start "DTSTART" nil timezone) "\n" - (and (memq 'todo-due org-icalendar-use-deadline) - (org-element-property :deadline entry) - (concat (org-icalendar-convert-timestamp - (org-element-property :deadline entry) "DUE" nil timezone) - "\n")) - "SUMMARY:" summary "\n" - (and (org-string-nw-p location) (format "LOCATION:%s\n" location)) - (and (org-string-nw-p class) (format "CLASS:%s\n" class)) - (and (org-string-nw-p description) - (format "DESCRIPTION:%s\n" description)) - "CATEGORIES:" categories "\n" - "SEQUENCE:1\n" - (format "PRIORITY:%d\n" - (let ((pri (or (org-element-property :priority entry) - org-priority-default))) - (floor (- 9 (* 8. (/ (float (- org-priority-lowest pri)) - (- org-priority-lowest - org-priority-highest))))))) - (format "STATUS:%s\n" - (if (eq (org-element-property :todo-type entry) 'todo) - "NEEDS-ACTION" - "COMPLETED")) - "END:VTODO")))) + (concat "BEGIN:VTODO\n" + "UID:TODO-" uid "\n" + (org-icalendar-dtstamp) "\n" + (org-icalendar-convert-timestamp start "DTSTART" nil timezone) "\n" + (and (memq 'todo-due org-icalendar-use-deadline) + (org-element-property :deadline entry) + (concat (org-icalendar-convert-timestamp + (org-element-property :deadline entry) "DUE" nil timezone) + "\n")) + "SUMMARY:" summary "\n" + (and (org-string-nw-p location) (format "LOCATION:%s\n" location)) + (and (org-string-nw-p class) (format "CLASS:%s\n" class)) + (and (org-string-nw-p description) + (format "DESCRIPTION:%s\n" description)) + "CATEGORIES:" categories "\n" + "SEQUENCE:1\n" + (format "PRIORITY:%d\n" + (let ((pri (or (org-element-property :priority entry) + org-priority-default))) + (floor (- 9 (* 8. (/ (float (- org-priority-lowest pri)) + (- org-priority-lowest + org-priority-highest))))))) + (format "STATUS:%s\n" + (if (eq (org-element-property :todo-type entry) 'todo) + "NEEDS-ACTION" + "COMPLETED")) + "END:VTODO"))) (defun org-icalendar--valarm (entry timestamp summary) "Create a VALARM component. @@ -879,19 +879,20 @@ (defun org-icalendar--vcalendar (name owner tz description contents) NAME, OWNER, TZ, DESCRIPTION and CONTENTS are all strings giving, respectively, the name of the calendar, its owner, the timezone used, a short description and the other components included." - (concat (format "BEGIN:VCALENDAR + (org-icalendar-fold-string + (concat (format "BEGIN:VCALENDAR VERSION:2.0 X-WR-CALNAME:%s PRODID:-//%s//Emacs with Org mode//EN X-WR-TIMEZONE:%s X-WR-CALDESC:%s CALSCALE:GREGORIAN\n" - (org-icalendar-cleanup-string name) - (org-icalendar-cleanup-string owner) - (org-icalendar-cleanup-string tz) - (org-icalendar-cleanup-string description)) - contents - "END:VCALENDAR\n")) + (org-icalendar-cleanup-string name) + (org-icalendar-cleanup-string owner) + (org-icalendar-cleanup-string tz) + (org-icalendar-cleanup-string description)) + contents + "END:VCALENDAR\n"))) diff --git a/testing/lisp/test-ox-icalendar.el b/testing/lisp/test-ox-icalendar.el new file mode 100644 index 000000000..539d2a0e0 --- /dev/null +++ b/testing/lisp/test-ox-icalendar.el @@ -0,0 +1,46 @@ +;;; test-ox-icalendar.el --- tests for ox-icalendar.el -*- lexical-binding: t; -*- + +;; Copyright (C) 2023 Jack Kamm + +;; Author: Jack Kamm + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Tests checking validity of Org iCalendar export output. + +;;; Code: + +(require 'ox-icalendar) + +(ert-deftest test-ox-icalendar/crfl-endings () + "Test every line of iCalendar export has CRFL ending." + (should + (seq-every-p + (lambda (x) (equal (substring x -1) "\r")) + (org-split-string + (org-test-with-temp-text + "* Test event +:PROPERTIES: +:ID: b17d8f92-1beb-442e-be4d-d2060fa3c7ff +:END: +<2023-03-30 Thu>" + (with-current-buffer + (org-export-to-buffer 'icalendar "*Test iCalendar Export*") + (buffer-string))) + "\n")))) + +(provide 'test-ox-icalendar) +;;; test-ox-icalendar.el ends here -- 2.39.2 --=-=-=--