mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-02-16 09:14:18 +00:00
Add full support for iCalendar (RFC5545) data
This is a fix for Bug#74994 that replaces the existing support in icalendar.el. It implements a full parser, recurrence rule and time zone calculations, diary import and export, and a major mode with syntax highlighting for iCalendar data. It obsoletes most of the code in icalendar.el. In addition to Bug#74994, the proposal to update Emacs' iCalendar support was discussed on emacs-devel in this thread: https://lists.gnu.org/archive/html/emacs-devel/2024-10/msg00425.html icalendar.el pre-dates the current standard (RFC5545), contains numerous bugs, is not well documented, and could not easily be updated or extended; starting fresh was the simplest path to creating an iCalendar library that other Emacs applications and packages can rely on. It was decided to leave icalendar.el's code in place for posterity, but declare it obsolete. Most of the changes in icalendar.el simply consist of such declarations. The old To Do list has also been deleted. A few changes in icalendar.el, however, consist of new code for library-wide functions and options, especially error handling. In particular: * lisp/calendar/icalendar.el: Log iCalendar library errors in a single buffer. (icalendar-errors-mode): New mode for it. (icalendar-uid-format): Change the default value to "%h", a hash value (for privacy). (icalendar-make-uid): New function, to replace 'icalendar--create-uid'. (icalendar-debug-level, icalendar-vcalendar-prodid): New option. (icalendar-vcalendar-version): New constant. * lisp/calendar/icalendar.el (icalendar-import-format) (icalendar-import-format-summary, icalendar-import-format-description) (icalendar-import-format-location, icalendar-import-format-organizer) (icalendar-import-format-url, icalendar-import-format-uid) (icalendar-import-format-status, icalendar-import-format-class) (icalendar-recurring-start-year, icalendar-export-hidden-diary-entries) (icalendar-export-sexp-enumerate-all, icalendar-export-alarms, icalendar-debug, icalendar--weekday-array, icalendar--dmsg) (icalendar--get-unfolded-buffer icalendar--clean-up-line-endings) (icalendar--rris, icalendar--read-element) (icalendar--get-event-property, icalendar--get-event-properties) (icalendar--get-event-property-attributes) (icalendar--get-children, icalendar--all-events, icalendar--split-value) (icalendar--convert-tz-offset, icalendar--parse-vtimezone) (icalendar--get-most-recent-observance) (icalendar--convert-all-timezones, icalendar--find-time-zone) (icalendar--decode-isodatetime) (icalendar--decode-isoduration, icalendar--add-decoded-times) (icalendar--datetime-to-american-date) (icalendar--datetime-to-european-date, icalendar--datetime-to-iso-date) (icalendar--datetime-to-diary-date, icalendar--datetime-to-colontime) (icalendar--get-month-number, icalendar--get-weekday-number) (icalendar--get-weekday-numbers, icalendar--get-weekday-abbrev) (icalendar--date-to-isodate, icalendar--datestring-to-isodate) (icalendar--diarytime-to-isotime, icalendar--convert-string-for-export) (icalendar--convert-string-for-import, icalendar-export-file) (icalendar-export-region, icalendar--create-uid) (icalendar--parse-summary-and-rest, icalendar--create-ical-alarm) (icalendar--do-create-ical-alarm, icalendar--convert-ordinary-to-ical) (icalendar-first-weekday-of-year, icalendar--convert-weekly-to-ical) (icalendar--convert-yearly-to-ical, icalendar--convert-sexp-to-ical) (icalendar--convert-block-to-ical, icalendar--convert-float-to-ical) (icalendar--convert-date-to-ical, icalendar--convert-cyclic-to-ical) (icalendar--convert-anniversary-to-ical, icalendar-import-file) (icalendar-import-buffer, icalendar--format-ical-event) (icalendar--convert-to-ical, icalendar--convert-ical-to-diary) (icalendar--convert-recurring-to-diary) (icalendar--convert-non-recurring-all-day-to-diary) (icalendar--convert-non-recurring-not-all-day-to-diary) (icalendar--add-diary-entry, icalendar-import-format-sample): Mark them as obsolete. In addition to the changes above, the new iCalendar library consists of the following: * lisp/calendar/diary-icalendar.el: * lisp/calendar/icalendar-ast.el: * lisp/calendar/icalendar-macs.el: * lisp/calendar/icalendar-mode.el: * lisp/calendar/icalendar-parser.el: * lisp/calendar/icalendar-recur.el: * lisp/calendar/icalendar-utils.el: New files A few changes were made to existing files dealing with the calendar and diary: * lisp/calendar/calendar.el (calendar-date-from-day-of-year): New function, extracted from calendar-goto-day-of-year. * lisp/calendar/cal-move.el (calendar-goto-day-of-year): Use it. * lisp/calendar/cal-dst.el (calendar-dst-find-data): Improve docstring. * lisp/calendar/calendar.el (diary-date-insertion-form): New option. (diary-american-date-insertion-form, diary-european-date-insertion-form) (diary-iso-date-insertion-form): New constants. * lisp/calendar/diary-lib.el (diary-insert-entry): Use the new 'diary-date-insertion-form' option. (diary-time-regexp): Add FIXME to an existing comment. The user-facing aspects of the above changes are documented in the Emacs manual and the NEWS file: * doc/emacs/calendar.texi (Diary Conversion): Update manual section to describe the new importer and exporter. * doc/emacs/emacs.texi (Detailed node listing): Update to include the new nodes in docs/emacs/calendar.texi. * etc/NEWS: Briefly describe the new library, major mode, and options. The remainder of the changes apply to test files. The following changes introduce new test files related to the new diary importer and exporter: * test/lisp/calendar/diary-icalendar-tests.el (Diary import and export): Tests for diary-icalendar. In addition to new tests for the exporter, the existing import tests for icalendar.el have been ported here; these use the existing iCalendar files in test/lisp/calendar/icalendar-resources. (A few new input .ics files have also been added to this directory; see below.) * test/lisp/calendar/diary-icalendar-resources: New directory containing expected outputs for the import tests in diary-icalendar-tests.el. (These have the same or similar names to the output files for the old importer, in test/lisp/calendar/icalendar-resources, but different contents. Thus they live in a new directory.) * test/lisp/calendar/icalendar-resources/import-legacy-function.ics: New input file to test backward compatibility of the new importer with a function as the value of 'icalendar-import-format', now obsolete. * test/lisp/calendar/icalendar-resources/import-legacy-vars.ics: New input file to test backward compatibility of the new importer with values for options provided by icalendar.el which are now obsolete. * test/lisp/calendar/icalendar-resources/import-with-attachment.ics: New input file to test import of base64-encoded attachments. * icalendar-resources/import-time-format-12hr-blank.ics: New input file to test import with a custom value of 'diary-icalendar-time-format'. Two other new test files provide unit tests for the main functions of the library: * test/lisp/calendar/icalendar-parser-tests.el (Parser): Tests for icalendar-parser. Most of these are derived from examples in RFC5545, to ensure the parser implements the standard. * test/lisp/calendar/icalendar-recur-tests.el (Recurrence rules): Tests for icalendar-recur. Most of these are derived from examples in RFC5545, to ensure the recurrence rule interpreter implements the standard. A few of the existing test files for icalendar.el have also been modified. Besides the specific changes mentioned below, the modified .ics files also now use CR-LF line endings, as required by RFC5545: * test/lisp/calendar/icalendar-tests.el (icalendar-deftest-obsolete): New macro. * test/lisp/calendar/icalendar-resources/import-non-recurring-all-day.ics: Correct a malformed VALUE parameter. * test/lisp/calendar/icalendar-resources/import-rrule-anniversary.ics: Correct representation of a recurring event. * test/lisp/calendar/icalendar-resources/import-rrule-daily-with-exceptions.ics: Add a required VALUE parameter. * test/lisp/calendar/icalendar-resources/import-rrule-daily.ics: * test/lisp/calendar/icalendar-resources/import-rrule-monthly-no-end.ics: * test/lisp/calendar/icalendar-resources/import-rrule-monthly-with-end.ics: * test/lisp/calendar/icalendar-resources/import-rrule-weekly.ics: Correct a malformed RRULE property.
This commit is contained in:
parent
0aabe62b64
commit
c685cf336a
106 changed files with 21901 additions and 303 deletions
|
|
@ -1005,7 +1005,7 @@ entries.
|
|||
* Adding to Diary:: Commands to create diary entries.
|
||||
* Special Diary Entries:: Anniversaries, blocks of dates, cyclic entries, etc.
|
||||
* Appointments:: Reminders when it's time to do something.
|
||||
* Importing Diary:: Converting diary events to/from other formats.
|
||||
* Diary Conversion:: Converting diary events to/from other formats.
|
||||
@end menu
|
||||
|
||||
@node Format of Diary File
|
||||
|
|
@ -1549,71 +1549,286 @@ clock. The command @kbd{M-x appt-add} adds entries to the appointment
|
|||
list without affecting your diary file. You delete entries from the
|
||||
appointment list with @kbd{M-x appt-delete}.
|
||||
|
||||
@node Importing Diary
|
||||
@node Diary Conversion
|
||||
@subsection Importing and Exporting Diary Entries
|
||||
@cindex importing diary entries
|
||||
@cindex diary import
|
||||
@cindex diary export
|
||||
|
||||
You can transfer diary entries between Emacs diary files and a
|
||||
variety of other formats.
|
||||
You can transfer diary entries between Emacs diary files and other
|
||||
formats.
|
||||
|
||||
@menu
|
||||
* Diary iCalendar Import:: Importing iCalendar data to the Diary.
|
||||
* Diary iCalendar Display:: Displaying iCalendar data without importing.
|
||||
* Diary iCalendar Export:: Exporting Diary entries to iCalendar.
|
||||
* Diary Outlook Import:: Importing Outlook appointments to the Diary.
|
||||
@end menu
|
||||
|
||||
@node Diary iCalendar Import
|
||||
@subsubsection Importing iCalendar data as Diary Entries
|
||||
@cindex import iCalendar to diary
|
||||
|
||||
@cindex iCalendar support in diary
|
||||
@dfn{iCalendar} is an Internet standard format for exchanging calendar
|
||||
data. Many calendar applications can export and import data in
|
||||
iCalendar format. iCalendar data is also often sent as email
|
||||
attachments. iCalendar data usually uses the @file{.ics} file
|
||||
extension, and is sent with the `text/calendar' @acronym{MIME} type in
|
||||
email. (@xref{Mail Misc}, for more information on @acronym{MIME} and
|
||||
email attachments.)
|
||||
|
||||
The @code{diary-icalendar} package allows you to make use of iCalendar
|
||||
data with the Emacs diary. You can import and export data between
|
||||
iCalendar format and your Emacs diary file, and also display iCalendar
|
||||
data directly in the diary.
|
||||
|
||||
The following commands will import iCalendar data to your diary file:
|
||||
|
||||
@ftable @code
|
||||
@item diary-icalendar-import-file
|
||||
Imports an iCalendar file to an Emacs diary file.
|
||||
|
||||
@item diary-icalendar-import-buffer
|
||||
Imports iCalendar data from the current buffer to an Emacs diary file.
|
||||
@end ftable
|
||||
|
||||
@code{diary-icalendar-import-buffer} is also suitable for importing
|
||||
iCalendar data from email attachments. For example, with the Rmail mail
|
||||
client, you could use:
|
||||
|
||||
@example
|
||||
(add-hook 'rmail-show-message-hook #'diary-icalendar-import-buffer)
|
||||
@end example
|
||||
|
||||
Diary import depends on a number of user-customizable variables, which
|
||||
are in the @code{diary-icalendar-import} customization group. You can
|
||||
review and customize these variables with @kbd{M-x customize-group}.
|
||||
@xref{Customization Groups}.
|
||||
|
||||
iCalendar data is grouped into @dfn{components} which represent calendar
|
||||
events (the VEVENT component), tasks (VTODO), and other text data
|
||||
(VJOURNAL). Because these components contain different types of data,
|
||||
they are imported by different functions, determined by the following
|
||||
variables:
|
||||
|
||||
@vtable @code
|
||||
@item diary-icalendar-vevent-skeleton-command
|
||||
Function to format VEVENT components for the diary.
|
||||
|
||||
@item diary-icalendar-vtodo-skeleton-command
|
||||
Function to format VTODO components for the diary.
|
||||
|
||||
@item diary-icalendar-vjournal-skeleton-command
|
||||
Function to format VJOURNAL components for the diary.
|
||||
@end vtable
|
||||
|
||||
You can customize the format of the imported diary entries by writing
|
||||
your own formatting functions. It is convenient (but not required) to
|
||||
express such functions as templates called @dfn{skeletons}.
|
||||
@ifinfo
|
||||
@xref{Top, Autotyping, The Autotype Manual, autotype}, for more about
|
||||
skeletons.
|
||||
@end ifinfo
|
||||
|
||||
For example, suppose you only want to import the date, time, summary,
|
||||
and location of each calendar event, and to write them on a single line
|
||||
like:
|
||||
|
||||
@example
|
||||
2025/11/11 Summary @@ Some Location
|
||||
@end example
|
||||
|
||||
@noindent
|
||||
Then you could write the import formatting function as a skeleton and
|
||||
set it to the value of @code{diary-icalendar-vevent-skeleton-command} as
|
||||
follows:
|
||||
|
||||
@lisp
|
||||
@group
|
||||
(require 'skeleton)
|
||||
|
||||
(define-skeleton simple-vevent
|
||||
"Format a VEVENT summary and location on a single line"
|
||||
nil
|
||||
start-to-end & " " & summary & " "
|
||||
(when location "@@ ") & location "\n")
|
||||
|
||||
(setopt diary-icalendar-vevent-skeleton-command #'simple-vevent)
|
||||
@end group
|
||||
@end lisp
|
||||
|
||||
The variables @code{start-to-end}, @code{summary} and @code{location} in
|
||||
this example are dynamically bound to appropriate values when the
|
||||
skeleton is called. See the docstring of
|
||||
@code{diary-icalendar-vevent-skeleton-command} for more information.
|
||||
|
||||
Any errors encountered during import will be reported in a buffer named
|
||||
@file{*icalendar-errors*}. You can review these errors with the
|
||||
@code{next-error} command. @xref{Compilation Mode}. If you regularly
|
||||
need to import malformed iCalendar data, there are several hooks
|
||||
available for this purpose; see the @code{icalendar-parser}
|
||||
customization group.
|
||||
|
||||
@node Diary iCalendar Display
|
||||
@subsubsection Displaying iCalendar entries in the Diary
|
||||
@cindex display iCalendar in diary
|
||||
|
||||
If you primarily store your calendar data outside of Emacs, but still
|
||||
want to see it in the Emacs calendar and diary, you can do so by
|
||||
including an iCalendar file from your diary file.
|
||||
|
||||
Suppose, for example, that you download your calendar from an
|
||||
external server to a file called @file{Appointments.ics}. Then you can
|
||||
include this file in your diary by writing a line like
|
||||
|
||||
@example
|
||||
#include "path/to/Appointments.ics"
|
||||
@end example
|
||||
|
||||
@noindent
|
||||
in your diary file. You must also set up some hooks to display the
|
||||
data in that file as diary entries and mark them in the calendar:
|
||||
|
||||
@lisp
|
||||
@group
|
||||
(add-hook 'diary-mark-entries-hook
|
||||
#'diary-mark-included-diary-files)
|
||||
(add-hook 'diary-nongregorian-marking-hook
|
||||
#'diary-icalendar-mark-entries)
|
||||
(add-hook 'diary-list-entries-hook
|
||||
#'diary-include-other-diary-files)
|
||||
(add-hook 'diary-nongregorian-listing-hook
|
||||
#'diary-icalendar-display-entries)
|
||||
@end group
|
||||
@end lisp
|
||||
|
||||
@noindent
|
||||
Events, tasks, and journal entries in @file{Appointments.ics} will then show
|
||||
up on the appropriate days when you display the diary from the calendar.
|
||||
@xref{Displaying the Diary}.
|
||||
|
||||
The advantage of doing this is that you don't need to synchronize the
|
||||
data between the calendar server and your diary file. This is simpler
|
||||
and more reliable than regularly importing and exporting between diary
|
||||
and iCalendar format.
|
||||
|
||||
@findex diary-icalendar-mailcap-viewer
|
||||
You can also display iCalendar attachments in email messages
|
||||
without importing them to your diary file using the function
|
||||
@code{diary-icalendar-mailcap-viewer}. You can add this function, for
|
||||
example, to the variable @code{mailcap-user-mime-data}; see its docstring
|
||||
for more information.
|
||||
|
||||
Displaying iCalendar entries uses the same infrastructure as importing
|
||||
them, so customizing the import format will also change the format of
|
||||
the displayed entries. @xref{Diary iCalendar Import}.
|
||||
|
||||
@node Diary iCalendar Export
|
||||
@subsubsection Exporting Diary Entries to iCalendar
|
||||
@cindex export diary to iCalendar
|
||||
|
||||
The following commands will export diary entries in iCalendar format:
|
||||
|
||||
@ftable @code
|
||||
@item diary-icalendar-export-file
|
||||
Exports a diary file to iCalendar format.
|
||||
|
||||
@item diary-icalendar-export-region
|
||||
Exports a region of diary text to iCalendar format.
|
||||
@end ftable
|
||||
|
||||
iCalendar export depends on a number of user-customizable variables, which
|
||||
are in the @code{diary-icalendar-export} customization group. You can
|
||||
review and customize these variables with @kbd{M-x customize-group}.
|
||||
@xref{Customization Groups}.
|
||||
|
||||
Exporting diary entries to iCalendar requires you to respect certain
|
||||
conventions in your diary, so that iCalendar properties can be parsed
|
||||
from your diary entries.
|
||||
|
||||
By default, the exporter will use the first line of the entry (after the
|
||||
date and time) as the iCalendar summary and the rest of the entry as its
|
||||
iCalendar description. Other iCalendar properties can also be encoded in
|
||||
the entry on separate lines, like this:
|
||||
|
||||
@example
|
||||
@group
|
||||
2025/11/11 Bender's birthday bash
|
||||
Location: Robot House
|
||||
Attendees:
|
||||
Fry <philip.fry@@mars.edu>
|
||||
Günter <guenter@@mars.edu>
|
||||
@end group
|
||||
@end example
|
||||
|
||||
@noindent
|
||||
This format matches the format produced by the default import
|
||||
functions.
|
||||
|
||||
@vindex diary-icalendar-address-regexp
|
||||
@vindex diary-icalendar-class-regexp
|
||||
@vindex diary-icalendar-description-regexp
|
||||
@vindex diary-icalendar-location-regexp
|
||||
@vindex diary-icalendar-organizer-regexp
|
||||
@vindex diary-icalendar-status-regexp
|
||||
@vindex diary-icalendar-summary-regexp
|
||||
@vindex diary-icalendar-todo-regexp
|
||||
@vindex diary-icalendar-uid-regexp
|
||||
@vindex diary-icalendar-url-regexp
|
||||
If you customize the import format, or you want to export diary entries
|
||||
in a different format, you will need to customize the export variables
|
||||
to detect the format of your diary entries. The most common iCalendar
|
||||
properties are parsed from diary entries using regular expressions. See
|
||||
the variables named @code{diary-icalendar-*-regexp} in the
|
||||
@code{diary-icalendar-export} customization group to modify how these
|
||||
properties are parsed.
|
||||
|
||||
@vindex diary-icalendar-other-properties-parser
|
||||
If you need to export other iCalendar properties, or do more
|
||||
complicated parsing, you can define a function to do so and set it as
|
||||
the value of the variable @code{diary-icalendar-other-properties-parser};
|
||||
see its docstring for details.
|
||||
|
||||
@vindex diary-icalendar-export-linewise
|
||||
By default, the exporter assumes that each diary entry represents a
|
||||
single iCalendar event. If you like to keep your diary in a
|
||||
one-entry-per-day format, with different events on continuation
|
||||
lines within the same entry, you can still export such entries as
|
||||
distinct iCalendar events. To do this, set the variable
|
||||
@code{diary-icalendar-export-linewise} to a non-nil value.
|
||||
|
||||
For example, after setting this variable, an entry like:
|
||||
|
||||
@example
|
||||
@group
|
||||
2025-05-03
|
||||
9AM Lab meeting
|
||||
Günter to present on new assay
|
||||
Start experiment A
|
||||
12:30-1:30PM Lunch with Phil
|
||||
16:00 Experiment A finishes; move to freezer
|
||||
@end group
|
||||
@end example
|
||||
|
||||
@noindent
|
||||
will be exported as four events, each on the same day, but with
|
||||
different start times (except for the second event, ``Start experiment
|
||||
A'', which has no start time). See the docstring of
|
||||
@code{diary-icalendar-export-linewise} for more information.
|
||||
|
||||
@node Diary Outlook Import
|
||||
@subsubsection Importing Outlook appointments as Diary Entries
|
||||
@cindex diary outlook import
|
||||
|
||||
@vindex diary-outlook-formats
|
||||
You can import diary entries from Outlook-generated appointment
|
||||
@vindex diary-from-outlook-function
|
||||
You can also import diary entries from Outlook-generated appointment
|
||||
messages. While viewing such a message in Rmail or Gnus, do @kbd{M-x
|
||||
diary-from-outlook} to import the entry. You can make this command
|
||||
recognize additional appointment message formats by customizing the
|
||||
variable @code{diary-outlook-formats}. Other mail clients can set
|
||||
@code{diary-from-outlook-function} to an appropriate value.
|
||||
|
||||
@c FIXME the name of the RFC is hardly very relevant.
|
||||
@cindex iCalendar support
|
||||
The icalendar package allows you to transfer data between your Emacs
|
||||
diary file and iCalendar files, which are defined in @cite{RFC
|
||||
2445---Internet Calendaring and Scheduling Core Object Specification
|
||||
(iCalendar)} (as well as the earlier vCalendar format).
|
||||
|
||||
@c Importing works for ordinary (i.e., non-recurring) events, but
|
||||
@c (at present) may not work correctly (if at all) for recurring events.
|
||||
@c Exporting of diary files into iCalendar files should work correctly
|
||||
@c for most diary entries. This feature is a work in progress, so the
|
||||
@c commands may evolve in future.
|
||||
|
||||
@findex icalendar-import-buffer
|
||||
The command @code{icalendar-import-buffer} extracts
|
||||
iCalendar data from the current buffer and adds it to your
|
||||
diary file. This function is also suitable for automatic extraction of
|
||||
iCalendar data; for example with the Rmail mail client one could use:
|
||||
|
||||
@example
|
||||
(add-hook 'rmail-show-message-hook 'icalendar-import-buffer)
|
||||
@end example
|
||||
|
||||
@findex icalendar-import-file
|
||||
The command @code{icalendar-import-file} imports an iCalendar file
|
||||
and adds the results to an Emacs diary file. For example:
|
||||
|
||||
@example
|
||||
(icalendar-import-file "/here/is/calendar.ics"
|
||||
"/there/goes/ical-diary")
|
||||
@end example
|
||||
|
||||
@noindent
|
||||
You can use an @code{#include} directive to add the import file contents
|
||||
to the main diary file, if these are different files.
|
||||
@iftex
|
||||
@xref{Fancy Diary Display,,, emacs-xtra, Specialized Emacs Features}.
|
||||
@end iftex
|
||||
@ifnottex
|
||||
@xref{Fancy Diary Display}.
|
||||
@end ifnottex
|
||||
|
||||
|
||||
@findex icalendar-export-file
|
||||
@findex icalendar-export-region
|
||||
@cindex export diary
|
||||
Use @code{icalendar-export-file} to interactively export an entire
|
||||
Emacs diary file to iCalendar format. To export only a part of a diary
|
||||
file, mark the relevant area, and call @code{icalendar-export-region}.
|
||||
In both cases, Emacs appends the result to the target file.
|
||||
|
||||
@node Daylight Saving
|
||||
@section Daylight Saving Time
|
||||
|
|
|
|||
|
|
@ -1020,7 +1020,14 @@ The Diary
|
|||
* Adding to Diary:: Commands to create diary entries.
|
||||
* Special Diary Entries:: Anniversaries, blocks of dates, cyclic entries, etc.
|
||||
* Appointments:: Reminders when it's time to do something.
|
||||
* Importing Diary:: Converting diary events to/from other formats.
|
||||
* Diary Conversion:: Converting diary events to/from other formats.
|
||||
|
||||
Diary Conversion
|
||||
|
||||
* Diary iCalendar Import:: Importing iCalendar data to the Diary.
|
||||
* Diary iCalendar Display:: Displaying iCalendar data without importing.
|
||||
* Diary iCalendar Export:: Exporting Diary entries to iCalendar.
|
||||
* Diary Outlook Import:: Importing Outlook appointments to the Diary.
|
||||
|
||||
@ifnottex
|
||||
More advanced features of the Calendar and Diary
|
||||
|
|
|
|||
33
etc/NEWS
33
etc/NEWS
|
|
@ -3084,6 +3084,34 @@ The user options 'calendar-mark-holidays-flag' and
|
|||
'calendar-mark-diary-entries-flag' are not modified anymore when
|
||||
changing the marking state in the calendar buffer.
|
||||
|
||||
*** New library for iCalendar data.
|
||||
A new library has been added to the calendar for handling iCalendar
|
||||
(RFC5545) data. The library is designed for reuse in other parts of
|
||||
Emacs and in third-party packages. Package authors can find the new
|
||||
library in the Emacs distribution under lisp/calendar/icalendar-*.el.
|
||||
|
||||
Most of the functions and variables in the older icalendar.el have been
|
||||
marked obsolete and now suggest appropriate replacements from the new
|
||||
library. diary-icalendar.el provides replacements for the diary-related
|
||||
features from icalendar.el; see below.
|
||||
|
||||
** Diary
|
||||
|
||||
*** New user option 'diary-date-insertion-form'.
|
||||
This user option determines how dates are inserted into the diary by
|
||||
Lisp functions. Its value is a pseudo-pattern of the same type as in
|
||||
'diary-date-forms'. It is used by 'diary-insert-entry' when inserting
|
||||
entries from the calendar, or when importing them from other formats.
|
||||
|
||||
+++
|
||||
*** New library 'diary-icalendar'.
|
||||
This library reimplements features previously provided by icalendar.el:
|
||||
import from iCalendar format to the diary, and export from the diary to
|
||||
iCalendar. It also adds the ability to include iCalendar files in the
|
||||
diary and display and mark their contents in the calendar without
|
||||
importing them to the diary file. The library uses the new iCalendar
|
||||
library (see above) and makes diary import and export more customizable.
|
||||
|
||||
** Calc
|
||||
|
||||
*** New user option 'calc-string-maximum-character'.
|
||||
|
|
@ -3247,6 +3275,11 @@ value. Previously, only 'hi-lock-face-buffer' supported this.
|
|||
|
||||
* New Modes and Packages in Emacs 31.1
|
||||
|
||||
** New major mode 'icalendar-mode'.
|
||||
A major mode for displaying and editing iCalendar (RFC5545) data. This
|
||||
mode handles line unfolding and fontification, including highlighting
|
||||
syntax errors in invalid data.
|
||||
|
||||
** New minor mode 'delete-trailing-whitespace-mode'.
|
||||
A simple buffer-local mode that runs 'delete-trailing-whitespace'
|
||||
before saving the buffer.
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ The result has the proper form for `calendar-daylight-savings-starts'."
|
|||
(car candidate-rules)))
|
||||
|
||||
;; TODO it might be better to extract this information directly from
|
||||
;; the system timezone database. But cross-platform...?
|
||||
;; the system timezone database. But cross-platform...?
|
||||
;; See thread
|
||||
;; https://lists.gnu.org/r/emacs-pretest-bug/2006-11/msg00060.html
|
||||
(defun calendar-dst-find-data (&optional time)
|
||||
|
|
@ -309,7 +309,9 @@ system knows:
|
|||
UTC-DIFF is an integer specifying the number of minutes difference between
|
||||
standard time in the current time zone and Coordinated Universal Time
|
||||
(Greenwich Mean Time). A negative value means west of Greenwich.
|
||||
DST-OFFSET is an integer giving the daylight saving time offset in minutes.
|
||||
DST-OFFSET is an integer giving the daylight saving time offset in minutes
|
||||
relative to UTC-DIFF. (That is, the total UTC offset during daylight saving
|
||||
time is UTC-DIFF + DST-OFFSET minutes.)
|
||||
STD-ZONE is a string giving the name of the time zone when no seasonal time
|
||||
adjustment is in effect.
|
||||
DST-ZONE is a string giving the name of the time zone when there is a seasonal
|
||||
|
|
|
|||
|
|
@ -431,11 +431,7 @@ Interactively, prompt for YEAR and DAY number."
|
|||
(calendar-day-number (calendar-current-date))
|
||||
last)))
|
||||
(list year day)))
|
||||
(calendar-goto-date
|
||||
(calendar-gregorian-from-absolute
|
||||
(if (< 0 day)
|
||||
(+ -1 day (calendar-absolute-from-gregorian (list 1 1 year)))
|
||||
(+ 1 day (calendar-absolute-from-gregorian (list 12 31 year))))))
|
||||
(calendar-goto-date (calendar-date-from-day-of-year year day))
|
||||
(or noecho (calendar-print-day-of-year)))
|
||||
|
||||
(provide 'cal-move)
|
||||
|
|
|
|||
|
|
@ -871,7 +871,15 @@ current word of the diary entry, so in no case can the pattern match more than
|
|||
a portion of the first word of the diary entry.
|
||||
|
||||
For examples of three common styles, see `diary-american-date-forms',
|
||||
`diary-european-date-forms', and `diary-iso-date-forms'."
|
||||
`diary-european-date-forms', and `diary-iso-date-forms'.
|
||||
|
||||
If you customize this variable, you should also customize the variable
|
||||
`diary-date-insertion-form' to contain a pseudo-pattern which produces
|
||||
dates that match one of the forms in this variable. (If
|
||||
`diary-date-insertion-form' does not correspond to one of the patterns
|
||||
in this variable, then the diary will not recognize such dates,
|
||||
including those inserted into the diary from the calendar with
|
||||
`diary-insert-entry'.)"
|
||||
:type '(repeat (choice (cons :tag "Backup"
|
||||
:value (backup . nil)
|
||||
(const backup)
|
||||
|
|
@ -895,6 +903,52 @@ For examples of three common styles, see `diary-american-date-forms',
|
|||
(diary))))
|
||||
:group 'diary)
|
||||
|
||||
(defconst diary-american-date-insertion-form '(month "/" day "/" year)
|
||||
"Pseudo-pattern for American dates in `diary-date-insertion-form'")
|
||||
|
||||
(defconst diary-european-date-insertion-form '(day "/" month "/" year)
|
||||
"Pseudo-pattern for European dates in `diary-date-insertion-form'")
|
||||
|
||||
(defconst diary-iso-date-insertion-form '(year "/" month "/" day)
|
||||
"Pseudo-pattern for ISO dates in `diary-date-insertion-form'")
|
||||
|
||||
(defcustom diary-date-insertion-form
|
||||
(cond ((eq calendar-date-style 'iso) diary-iso-date-insertion-form)
|
||||
((eq calendar-date-style 'european) diary-european-date-insertion-form)
|
||||
(t diary-american-date-insertion-form))
|
||||
"Pseudo-pattern describing how to format a date for a new diary entry.
|
||||
|
||||
A pseudo-pattern is a list of expressions that can include the symbols
|
||||
`month', `day', and `year' (all numbers in string form), and `monthname'
|
||||
and `dayname' (both alphabetic strings). For example, a typical American
|
||||
form would be
|
||||
|
||||
(month \"/\" day \"/\" (substring year -2))
|
||||
|
||||
whereas
|
||||
|
||||
((format \"%9s, %9s %2s, %4s\" dayname monthname day year))
|
||||
|
||||
would give the usual American style in fixed-length fields.
|
||||
|
||||
This pattern will be used by `calendar-date-string' (which see) to
|
||||
format dates when inserting them with `diary-insert-entry', or when
|
||||
importing them from other formats into the diary.
|
||||
|
||||
If you customize this variable, you should also customize the variable
|
||||
`diary-date-forms' to include a pseudo-pattern which matches dates
|
||||
produced by this pattern. (If there is no corresponding pattern in
|
||||
`diary-date-forms', then the diary will not recognize such dates,
|
||||
including those inserted into the diary from the calendar with
|
||||
`diary-insert-entry'.)"
|
||||
:version "31.1"
|
||||
:type 'sexp
|
||||
:risky t
|
||||
:set-after '(calendar-date-style diary-american-date-insertion-form
|
||||
diary-european-date-insertion-form
|
||||
diary-iso-date-insertion-form)
|
||||
:group 'diary)
|
||||
|
||||
;; Next three are provided to aid in setting calendar-date-display-form.
|
||||
(defcustom calendar-iso-date-display-form '((format "%s-%.2d-%.2d" year
|
||||
(string-to-number month)
|
||||
|
|
@ -1028,7 +1082,9 @@ The valid styles are described in the documentation of `calendar-date-style'."
|
|||
calendar-month-header
|
||||
(symbol-value (intern-soft (format "calendar-%s-month-header" style)))
|
||||
diary-date-forms
|
||||
(symbol-value (intern-soft (format "diary-%s-date-forms" style))))
|
||||
(symbol-value (intern-soft (format "diary-%s-date-forms" style)))
|
||||
diary-date-insertion-form
|
||||
(symbol-value (intern-soft (format "diary-%s-date-insertion-form" style))))
|
||||
(calendar-redraw)
|
||||
(calendar-update-mode-line))
|
||||
|
||||
|
|
@ -1298,6 +1354,16 @@ return negative results."
|
|||
(/ offset-years 400)
|
||||
(calendar-day-number '(12 31 -1))))))) ; days in year 1 BC
|
||||
|
||||
;; This function is the inverse of `calendar-day-number':
|
||||
(defun calendar-date-from-day-of-year (year dayno)
|
||||
"Return the date of the DAYNO-th day in YEAR.
|
||||
DAYNO must be an integer between -366 and 366."
|
||||
(calendar-gregorian-from-absolute
|
||||
(+ (if (< dayno 0)
|
||||
(+ 1 dayno (if (calendar-leap-year-p year) 366 365))
|
||||
dayno)
|
||||
(calendar-absolute-from-gregorian (list 12 31 (1- year))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun calendar (&optional arg)
|
||||
"Display a three-month Gregorian calendar.
|
||||
|
|
|
|||
3970
lisp/calendar/diary-icalendar.el
Normal file
3970
lisp/calendar/diary-icalendar.el
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2120,8 +2120,9 @@ show the diary buffer."
|
|||
Prefix argument ARG makes the entry nonmarking."
|
||||
(interactive
|
||||
(list current-prefix-arg last-nonmenu-event))
|
||||
(diary-make-entry (calendar-date-string (calendar-cursor-to-date t event) t t)
|
||||
arg))
|
||||
(calendar-dlet ((calendar-date-display-form diary-date-insertion-form))
|
||||
(diary-make-entry (calendar-date-string (calendar-cursor-to-date t event) t t)
|
||||
arg)))
|
||||
|
||||
;;;###cal-autoload
|
||||
(defun diary-insert-weekly-entry (arg)
|
||||
|
|
@ -2318,6 +2319,7 @@ return a font-lock pattern matching array of MONTHS and marking SYMBOL."
|
|||
;; Accepted formats: 10:00 10.00 10h00 10h 10am 10:00am 10.00am
|
||||
;; Use of "." as a separator annoyingly matches numbers, eg "123.45".
|
||||
;; Hence often prefix this with "\\(^\\|\\s-\\)."
|
||||
;; FIXME.
|
||||
(concat "[0-9]?[0-9]\\([AaPp][mM]\\|\\("
|
||||
"[Hh]\\([0-9][0-9]\\)?\\|[:.][0-9][0-9]"
|
||||
"\\)\\([AaPp][Mm]\\)?\\)")
|
||||
|
|
|
|||
927
lisp/calendar/icalendar-ast.el
Normal file
927
lisp/calendar/icalendar-ast.el
Normal file
|
|
@ -0,0 +1,927 @@
|
|||
;;; icalendar-ast.el --- Syntax trees for iCalendar -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2024 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Richard Lawrence <rwl@recursewithless.net>
|
||||
;; Created: October 2024
|
||||
;; Keywords: calendar
|
||||
;; Human-Keywords: calendar, iCalendar
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This file defines the abstract syntax tree representation for
|
||||
;; iCalendar data. The AST is based on `org-element-ast' (which see;
|
||||
;; that feature will eventually be renamed and moved out of the Org tree
|
||||
;; into the main tree).
|
||||
|
||||
;; This file contains low-level functions for constructing and
|
||||
;; manipulating the AST, most of which are minimal wrappers around the
|
||||
;; functions provided by `org-element-ast'. This low-level API is
|
||||
;; primarily used by `icalendar-parser'. It also contains a higher-level
|
||||
;; API for constructing AST nodes in Lisp code. Finally, it defines
|
||||
;; functions for validating AST nodes.
|
||||
|
||||
;; There are three main pieces of data in an AST node: its type, its
|
||||
;; value, and its child nodes. Nodes which represent iCalendar
|
||||
;; components have no values; they are simply containers for their
|
||||
;; children. Nodes which represent data of the base iCalendar data
|
||||
;; types have no children; they are the leaf nodes in the syntax tree.
|
||||
;; The main low-level accessors for these data in AST nodes are:
|
||||
;;
|
||||
;; `icalendar-ast-node-type'
|
||||
;; `icalendar-ast-node-value'
|
||||
;; `icalendar-ast-node-children'
|
||||
;; `icalendar-ast-node-children-of'
|
||||
;; `icalendar-ast-node-first-child-of'
|
||||
|
||||
;; To construct AST nodes in Lisp code, see especially the high-level macros:
|
||||
;;
|
||||
;; `icalendar-make-vcalendar'
|
||||
;; `icalendar-make-vtimezone'
|
||||
;; `icalendar-make-vevent'
|
||||
;; `icalendar-make-vtodo'
|
||||
;; `icalendar-make-vjournal'
|
||||
;; `icalendar-make-property'
|
||||
;; `icalendar-make-param'
|
||||
;;
|
||||
;; These macros wrap the macro `icalendar-make-node-from-templates',
|
||||
;; which allows writing iCalendar syntax tree nodes as Lisp templates.
|
||||
|
||||
;; Constructing nodes with these macros automatically validates them
|
||||
;; with the function `icalendar-ast-node-valid-p', which signals an
|
||||
;; `icalendar-validation-error' if the node is not valid acccording to
|
||||
;; RFC5545.
|
||||
|
||||
|
||||
;;; Code:
|
||||
(eval-when-compile (require 'icalendar-macs))
|
||||
(require 'icalendar)
|
||||
(require 'org-element-ast)
|
||||
(require 'cl-lib)
|
||||
|
||||
;;; Type symbols and metadata
|
||||
|
||||
;; All nodes in the syntax tree have a type symbol as their first element.
|
||||
;; We use the following symbol properties (all prefixed with 'icalendar-')
|
||||
;; to associate type symbols with various important data about the type:
|
||||
;;
|
||||
;; is-type - t (marks this symbol as an icalendar type)
|
||||
;; is-value, is-param, is-property, or is-component - t
|
||||
;; (specifies what sort of value this type represents)
|
||||
;; list-sep - for property and parameters types, a string (typically
|
||||
;; "," or ";") which separates individual printed values, if the
|
||||
;; type allows lists of values. If this is non-nil, syntax nodes of
|
||||
;; this type should always have a list of values in their VALUE
|
||||
;; field (even if there is only one value)
|
||||
;; matcher - a function to match this type. This function matches the
|
||||
;; regular expression defined under the type's name; it is used to provide
|
||||
;; syntax highlighting in `icalendar-mode'
|
||||
;; begin-rx, end-rx - for component-types, an `rx' regular expression which
|
||||
;; matches the BEGIN and END lines that form its boundaries
|
||||
;; value-rx - an `rx' regular expression which matches individual values
|
||||
;; of this type, with no consideration for quoting or lists of values.
|
||||
;; (For value types, this is just a synonym for the rx definition
|
||||
;; under the type's symbol)
|
||||
;; values-rx - for types that accept lists of values, an `rx' regular
|
||||
;; expression which matches the whole list (including quotes, if required)
|
||||
;; full-value-rx - for property and parameter types, an `rx' regular
|
||||
;; expression which matches a valid value expression in group 2, or
|
||||
;; an invalid value in group 3
|
||||
;; value-reader - for value types, a function which creates syntax
|
||||
;; nodes of this type given a string representing their value
|
||||
;; value-printer - for value types, a function to print individual
|
||||
;; values of this type. It accepts a value and returns its string
|
||||
;; representation.
|
||||
;; default-value - for property and parameter types, a string
|
||||
;; representing a default value for nodes of this type. This is the
|
||||
;; value assumed when no node of this type is present in the
|
||||
;; relevant part of the syntax tree.
|
||||
;; substitute-value - for parameter types, a string representing a value
|
||||
;; which will be substituted at parse times for unrecognized values.
|
||||
;; (This is normally the same as default-value, but differs from it
|
||||
;; in at least one case in RFC5545, thus it is stored separately.)
|
||||
;; default-type - for property types which can accept values of multiple
|
||||
;; types, this is the default type when no type for the value is
|
||||
;; specified in the parameters. Any type of value other than this
|
||||
;; one requires a VALUE=... parameter when the property is read or printed.
|
||||
;; other-types - for property types which can accept values of multiple types,
|
||||
;; this is a list of other types that the property can accept.
|
||||
;; value-type - for param types, this is the value type which the parameter
|
||||
;; can accept.
|
||||
;; child-spec - for property and component types, a plist describing the
|
||||
;; required and optional child nodes. See `icalendar-define-property' and
|
||||
;; `icalendar-define-component' for details.
|
||||
;; other-validator - a function to perform type-specific validation
|
||||
;; for nodes of this type. If present, this function will be called
|
||||
;; by `icalendar-ast-node-valid-p' during validation.
|
||||
;; type-documentation - a string documenting the type. This documentation is
|
||||
;; printed in the help buffer when `describe-symbol' is called on TYPE.
|
||||
;; link - a hyperlink to the documentation of the type in the relevant standard
|
||||
|
||||
(defun ical:type-symbol-p (symbol)
|
||||
"Return non-nil if SYMBOL is an iCalendar type symbol.
|
||||
|
||||
This function only checks that SYMBOL has been marked as a type;
|
||||
it returns t for value types defined by `icalendar-define-type',
|
||||
but also e.g. for types defined by `icalendar-define-param' and
|
||||
`icalendar-define-property'. To check that SYMBOL names a value
|
||||
type for property or parameter values, see
|
||||
`icalendar-value-type-symbol-p' and
|
||||
`icalendar-printable-value-type-symbol-p'."
|
||||
(and (symbolp symbol)
|
||||
(get symbol 'ical:is-type)))
|
||||
|
||||
(defun ical:value-type-symbol-p (symbol)
|
||||
"Return non-nil if SYMBOL is a type symbol for a value type.
|
||||
|
||||
This means that SYMBOL must both satisfy `icalendar-type-symbol-p' and
|
||||
have the property `icalendar-is-value'. It does not require the type to
|
||||
be associated with a print name in `icalendar-value-types'; for that see
|
||||
`icalendar-printable-value-type-symbol-p'."
|
||||
(and (ical:type-symbol-p symbol)
|
||||
(get symbol 'ical:is-value)))
|
||||
|
||||
(defun ical:expects-list-of-values-p (type)
|
||||
"Return non-nil if TYPE expects a list of values.
|
||||
|
||||
This is never t for value types or component types. For property and
|
||||
parameter types defined with `icalendar-define-param' and
|
||||
`icalendar-define-property', it is true if the :list-sep argument was
|
||||
specified in the definition."
|
||||
(and (ical:type-symbol-p type)
|
||||
(get type 'ical:list-sep)))
|
||||
|
||||
(defun ical:param-type-symbol-p (type)
|
||||
"Return non-nil if TYPE is a type symbol for an iCalendar parameter."
|
||||
(and (ical:type-symbol-p type)
|
||||
(get type 'ical:is-param)))
|
||||
|
||||
(defun ical:property-type-symbol-p (type)
|
||||
"Return non-nil if TYPE is a type symbol for an iCalendar property."
|
||||
(and (ical:type-symbol-p type)
|
||||
(get type 'ical:is-property)))
|
||||
|
||||
(defun ical:component-type-symbol-p (type)
|
||||
"Return non-nil if TYPE is a type symbol for an iCalendar component."
|
||||
(and (ical:type-symbol-p type)
|
||||
(get type 'ical:is-component)))
|
||||
|
||||
;; TODO: we could define other accessors here for the other metadata
|
||||
;; properties, but at the moment I see no advantage to this; they would
|
||||
;; all just be long-winded wrappers around `get'.
|
||||
|
||||
|
||||
;; The basic, low-level API for the AST, mostly intended for use by
|
||||
;; `icalendar-parser'. These functions are mostly aliases and simple
|
||||
;; wrappers around functions provided by `org-element-ast', which does
|
||||
;; the heavy lifting.
|
||||
(defalias 'ical:ast-node-type #'org-element-type)
|
||||
|
||||
(defsubst ical:ast-node-value (node)
|
||||
"Return the value of iCalendar syntax node NODE.
|
||||
In component nodes, this is nil. Otherwise, it is a syntax node
|
||||
representing an iCalendar (property or parameter) value."
|
||||
(org-element-property :value node))
|
||||
|
||||
(defalias 'ical:ast-node-children #'org-element-contents)
|
||||
|
||||
;; TODO: probably don't want &rest form for this
|
||||
(defalias 'ical:ast-node-set-children #'org-element-set-contents)
|
||||
|
||||
(defalias 'ical:ast-node-adopt-children #'org-element-adopt-elements)
|
||||
|
||||
(defalias 'ical:ast-node-meta-get #'org-element-property)
|
||||
|
||||
(defalias 'ical:ast-node-meta-set #'org-element-put-property)
|
||||
|
||||
(defun ical:ast-node-set-type (node type)
|
||||
"Set the type of iCalendar syntax node NODE to TYPE.
|
||||
|
||||
This function is probably not what you want! It directly modifies the
|
||||
type of NODE in-place, which could make the node invalid if its value or
|
||||
children do not match the new TYPE. If you do not know in advance that
|
||||
the data in NODE is compatible with the new TYPE, it is better to
|
||||
construct a new syntax node."
|
||||
(setcar node type))
|
||||
|
||||
(defun ical:ast-node-set-value (node value)
|
||||
"Set the value of iCalendar syntax node NODE to VALUE."
|
||||
(ical:ast-node-meta-set node :value value))
|
||||
|
||||
(defun ical:make-ast-node (type props &optional children)
|
||||
"Construct a syntax node of TYPE with meta-properties PROPS and CHILDREN.
|
||||
|
||||
This is a low-level constructor. If you are constructing iCalendar
|
||||
syntax nodes directly in Lisp code, consider using one of the
|
||||
higher-level macros based on `icalendar-make-node-from-templates'
|
||||
instead, which expand to calls to this function but also perform type
|
||||
checking and validation.
|
||||
|
||||
TYPE should be an iCalendar type symbol. CHILDREN, if given, should be
|
||||
a list of syntax nodes. In property nodes, these should be the
|
||||
parameters of the property. In component nodes, these should be the
|
||||
properties or subcomponents of the component. CHILDREN should otherwise
|
||||
be nil.
|
||||
|
||||
PROPS should be a plist with any of the following keywords:
|
||||
|
||||
:value - in value nodes, this should be the Elisp value parsed from a
|
||||
property or parameter's value string. In parameter and property nodes,
|
||||
this should be a value node or list of value nodes. In component
|
||||
nodes, it should not be present.
|
||||
:buffer - buffer from which VALUE was parsed
|
||||
:begin - position at which this node begins in BUFFER
|
||||
:end - position at which this node ends in BUFFER
|
||||
:value-begin - position at which VALUE begins in BUFFER
|
||||
:value-end - position at which VALUE ends in BUFFER
|
||||
:original-value - a string containing the original, uninterpreted value
|
||||
of the node. This can differ from (a string represented by) VALUE
|
||||
if e.g. a default VALUE was substituted for an unrecognized but
|
||||
syntactically correct value.
|
||||
:original-name - a string containing the original, uninterpreted name
|
||||
of the parameter, property or component this node represents.
|
||||
This can differ from (a string representing) TYPE
|
||||
if e.g. a default TYPE was substituted for an unrecognized but
|
||||
syntactically correct one."
|
||||
;; automatically mark :value as a "secondary property" for org-element-ast
|
||||
(let ((full-props (if (plist-member props :value)
|
||||
(plist-put props :secondary (list :value))
|
||||
props)))
|
||||
(apply #'org-element-create type full-props children)))
|
||||
|
||||
(defun ical:ast-node-p (val)
|
||||
"Return non-nil if VAL is an iCalendar syntax node."
|
||||
(and (listp val)
|
||||
(length> val 1)
|
||||
(ical:type-symbol-p (ical:ast-node-type val))
|
||||
(plistp (cadr val))
|
||||
(listp (ical:ast-node-children val))))
|
||||
|
||||
(defun ical:param-node-p (node)
|
||||
"Return non-nil if NODE is a syntax node whose type is a parameter type."
|
||||
(and (ical:ast-node-p node)
|
||||
(ical:param-type-symbol-p (ical:ast-node-type node))))
|
||||
|
||||
(defun ical:property-node-p (node)
|
||||
"Return non-nil if NODE is a syntax node whose type is a property type."
|
||||
(and (ical:ast-node-p node)
|
||||
(ical:property-type-symbol-p (ical:ast-node-type node))))
|
||||
|
||||
(defun ical:component-node-p (node)
|
||||
"Return non-nil if NODE is a syntax node whose type is a component type."
|
||||
(and (ical:ast-node-p node)
|
||||
(ical:component-type-symbol-p (ical:ast-node-type node))))
|
||||
|
||||
(defun ical:ast-node-first-child-of (type node)
|
||||
"Return the first child of NODE of type TYPE, or nil."
|
||||
(assq type (ical:ast-node-children node)))
|
||||
|
||||
(defun ical:ast-node-children-of (type node)
|
||||
"Return a list of all the children of NODE of type TYPE."
|
||||
(seq-filter (lambda (c) (eq type (ical:ast-node-type c)))
|
||||
(ical:ast-node-children node)))
|
||||
|
||||
|
||||
;; A high-level API for constructing iCalendar syntax nodes in Lisp code:
|
||||
|
||||
(declare-function ical:list-of-p "icalendar-parser")
|
||||
|
||||
(defun ical:type-of (value &optional types)
|
||||
"Find the iCalendar type symbol for the type to which VALUE belongs.
|
||||
|
||||
TYPES, if specified, should be a list of type symbols to check.
|
||||
TYPES defaults to all type symbols listed in `icalendar-value-types'."
|
||||
(require 'icalendar-parser) ; for ical:value-types, ical:list-of-p
|
||||
(catch 'found
|
||||
(when (ical:ast-node-p value)
|
||||
(throw 'found (ical:ast-node-type value)))
|
||||
;; FIXME: the warning here is spurious, given that icalendar-parser
|
||||
;; is require'd above:
|
||||
(with-suppressed-warnings ((free-vars ical:value-types))
|
||||
(dolist (type (or types (mapcar #'cdr ical:value-types)))
|
||||
(if (ical:expects-list-of-values-p type)
|
||||
(when (ical:list-of-p value type)
|
||||
(throw 'found type))
|
||||
(when (cl-typep value type)
|
||||
(throw 'found type)))))))
|
||||
|
||||
;; A more flexible constructor for value nodes which can choose the
|
||||
;; correct type from a list. This helps keep templates succinct and easy
|
||||
;; to use in `icalendar-make-node-from-templates', and related macros
|
||||
;; below.
|
||||
(defun ical:make-value-node-of (type value)
|
||||
"Make an iCalendar syntax node of type TYPE containing VALUE as its value.
|
||||
|
||||
TYPE should be a symbol for an iCalendar value type, and VALUE should be
|
||||
a value of that type. If TYPE is the symbol \\='plain-text, VALUE should
|
||||
be a string, and in that case VALUE is returned as-is.
|
||||
|
||||
TYPE may also be a list of type symbols; in that case, the first type in
|
||||
the list which VALUE satisfies is used as the returned node's type. If
|
||||
the list is nil, VALUE will be checked against all types in
|
||||
`icalendar-value-types'.
|
||||
|
||||
If VALUE is nil, and `icalendar-boolean' is not (in) TYPE, nil is
|
||||
returned. Otherwise, a \\='wrong-type-argument error is signaled if
|
||||
VALUE does not satisfy (any type in) TYPE."
|
||||
(require 'icalendar-parser)
|
||||
(cond
|
||||
((and (null value)
|
||||
(not (if (listp type) (memq 'ical:boolean type)
|
||||
(eq 'ical:boolean type))))
|
||||
;; Instead of signaling an error, we just return nil in this case.
|
||||
;; This allows the `ical:make-*' macros higher up the stack to
|
||||
;; filter out templates that evaluate to nil at run time:
|
||||
nil)
|
||||
((eq type 'plain-text)
|
||||
(unless (stringp value)
|
||||
(signal 'wrong-type-argument (list 'stringp value)))
|
||||
value)
|
||||
((symbolp type)
|
||||
(unless (ical:value-type-symbol-p type)
|
||||
(signal 'wrong-type-argument (list 'icalendar-value-type-symbol-p type)))
|
||||
(if (ical:expects-list-of-values-p type)
|
||||
(unless (ical:list-of-p value type)
|
||||
(signal 'wrong-type-argument (list `(list-of ,type) value)))
|
||||
(unless (cl-typep value type)
|
||||
(signal 'wrong-type-argument (list type value)))
|
||||
(ical:make-ast-node type (list :value value))))
|
||||
((listp type)
|
||||
;; N.B. nil is allowed; in that case, `ical:type-of' will check all
|
||||
;; types in `ical:value-types':
|
||||
(let ((the-type (ical:type-of value type)))
|
||||
(if the-type
|
||||
(ical:make-ast-node the-type (list :value value))
|
||||
(signal 'wrong-type-argument
|
||||
(list (if (length> type 1) (cons 'or type) (car type))
|
||||
value)))))
|
||||
(t (signal 'wrong-type-argument (list '(or symbolp listp) type)))))
|
||||
|
||||
(defmacro ical:make-param (type value)
|
||||
"Construct an iCalendar parameter node of TYPE with value VALUE.
|
||||
|
||||
TYPE should be an iCalendar type symbol satisfying
|
||||
`icalendar-param-type-symbol-p'; it should not be quoted.
|
||||
|
||||
VALUE should evaluate to a value appropriate for TYPE. In particular, if
|
||||
TYPE expects a list of values (see `icalendar-expects-list-p'), VALUE
|
||||
should be such a list. If necessary, the value(s) in VALUE will be
|
||||
wrapped in syntax nodes indicating their type.
|
||||
|
||||
For example,
|
||||
|
||||
(icalendar-make-param icalendar-deltoparam
|
||||
(list \"mailto:minionA@example.com\" \"mailto:minionB@example.com\"))
|
||||
|
||||
will return an `icalendar-deltoparam' node whose value is a list of
|
||||
`icalendar-cal-address' nodes containing the two addresses.
|
||||
|
||||
The resulting syntax node is checked for validity by
|
||||
`icalendar-ast-node-valid-p' before it is returned."
|
||||
;; TODO: support `ical:otherparam'
|
||||
(unless (ical:param-type-symbol-p type)
|
||||
(error "Not an iCalendar param type: %s" type))
|
||||
(let ((value-type (or (get type 'ical:value-type) 'plain-text))
|
||||
(needs-list (ical:expects-list-of-values-p type)))
|
||||
`(let* ((raw-value ,value)
|
||||
(value-type (quote ,value-type))
|
||||
(value
|
||||
,(if needs-list
|
||||
'(if (seq-every-p #'ical:ast-node-p raw-value)
|
||||
raw-value
|
||||
(mapcar
|
||||
(lambda (c) (ical:make-value-node-of value-type c))
|
||||
raw-value))
|
||||
'(if (ical:ast-node-p raw-value)
|
||||
raw-value
|
||||
(ical:make-value-node-of value-type raw-value)))))
|
||||
(when value
|
||||
(ical:ast-node-valid-p
|
||||
(ical:make-ast-node
|
||||
(quote ,type)
|
||||
(list :value value)))))))
|
||||
|
||||
(defmacro ical:make-property (type value &rest param-templates)
|
||||
"Construct an iCalendar property node of TYPE with value VALUE.
|
||||
|
||||
TYPE should be an iCalendar type symbol satisfying
|
||||
`icalendar-property-type-symbol-p'; it should not be quoted.
|
||||
|
||||
VALUE should evaluate to a value appropriate for TYPE. In particular, if
|
||||
TYPE expects a list of values (see
|
||||
`icalendar-expects-list-of-values-p'), VALUE should be such a list. If
|
||||
necessary, the value(s) in VALUE will be wrapped in syntax nodes
|
||||
indicating their type. If VALUE is not of the default value type for
|
||||
TYPE, an `icalendar-valuetypeparam' will automatically be added to TEMPLATES.
|
||||
|
||||
Each element of PARAM-TEMPLATES should represent a parameter node; see
|
||||
`icalendar-make-node-from-templates' for the format of such TEMPLATES.
|
||||
A template can also have the form (@ L), where L evaluates to a list of
|
||||
parameter nodes to be added to the component.
|
||||
|
||||
PARAM-TEMPLATES which evaluate to nil are removed when the property node
|
||||
is constructed.
|
||||
|
||||
For example,
|
||||
|
||||
(icalendar-make-property icalendar-rdate (list \\='(2 1 2025) \\='(3 1 2025)))
|
||||
|
||||
will return an `icalendar-rdate' node whose value is a list of
|
||||
`icalendar-date' nodes containing the dates above as their values.
|
||||
|
||||
The resulting syntax node is checked for validity by
|
||||
`icalendar-ast-node-valid-p' before it is returned."
|
||||
;; TODO: support `ical:other-property', maybe like
|
||||
;; (ical:other-property "X-NAME" value ...)
|
||||
(unless (ical:property-type-symbol-p type)
|
||||
(error "Not an iCalendar property type: %s" type))
|
||||
(let ((value-types (cons (get type 'ical:default-type)
|
||||
(get type 'ical:other-types)))
|
||||
(needs-list (ical:expects-list-of-values-p type))
|
||||
params-expr children lists-of-children)
|
||||
(dolist (c param-templates)
|
||||
(cond ((and (listp c) (ical:type-symbol-p (car c)))
|
||||
;; c is a template for a child node, so it should be
|
||||
;; recursively expanded:
|
||||
(push (cons 'ical:make-node-from-templates c)
|
||||
children))
|
||||
((and (listp c) (eq '@ (car c)))
|
||||
;; c is a template (@ L) where L evaluates to a list of children:
|
||||
(push (cadr c) lists-of-children))
|
||||
(t
|
||||
;; otherwise, just pass c through as is; this allows
|
||||
;; interleaving templates with other expressions that
|
||||
;; evaluate to syntax nodes:
|
||||
(push c children))))
|
||||
(when (or children lists-of-children)
|
||||
(setq params-expr
|
||||
`(seq-filter #'identity
|
||||
(append (list ,@children) ,@lists-of-children))))
|
||||
|
||||
`(let* ((raw-value ,value)
|
||||
(value-types (quote ,value-types))
|
||||
(value
|
||||
,(if needs-list
|
||||
'(if (seq-every-p #'ical:ast-node-p raw-value)
|
||||
raw-value
|
||||
(mapcar
|
||||
(lambda (c) (ical:make-value-node-of value-types c))
|
||||
raw-value))
|
||||
'(if (ical:ast-node-p raw-value)
|
||||
raw-value
|
||||
(ical:make-value-node-of value-types raw-value)))))
|
||||
(when value
|
||||
(ical:ast-node-valid-p
|
||||
(ical:maybe-add-value-param
|
||||
(ical:make-ast-node
|
||||
(quote ,type)
|
||||
(list :value value)
|
||||
,params-expr)))))))
|
||||
|
||||
(defmacro ical:make-component (type &rest templates)
|
||||
"Construct an iCalendar component node of TYPE from TEMPLATES.
|
||||
|
||||
TYPE should be an iCalendar type symbol satisfying
|
||||
`icalendar-component-type-symbol-p'; it should not be quoted.
|
||||
|
||||
Each expression in TEMPLATES should represent a child node of the
|
||||
component; see `icalendar-make-node-from-templates' for the format of
|
||||
such TEMPLATES. A template can also have the form (@ L), where L
|
||||
evaluates to a list of child nodes to be added to the component.
|
||||
|
||||
Any value in TEMPLATES that evaluates to nil will be removed before the
|
||||
component node is constructed.
|
||||
|
||||
If TYPE is `icalendar-vevent', `icalendar-vtodo', `icalendar-vjournal',
|
||||
or `icalendar-vfreebusy', the properties `icalendar-dtstamp' and
|
||||
`icalendar-uid' will be automatically provided, if they are absent in
|
||||
TEMPLATES. Likewise, if TYPE is `icalendar-vcalendar', the properties
|
||||
`icalendar-prodid', `icalendar-version', and `icalendar-calscale' will
|
||||
be automatically provided if absent.
|
||||
|
||||
For example,
|
||||
|
||||
(icalendar-make-component icalendar-vevent
|
||||
(icalendar-summary \"Party\")
|
||||
(icalendar-location \"Robot House\")
|
||||
(@ list-of-other-properties))
|
||||
|
||||
will return an `icalendar-vevent' node containing the provided
|
||||
properties as well as `icalendar-dtstamp' and `icalendar-uid'
|
||||
properties.
|
||||
|
||||
The resulting syntax node is checked for validity by
|
||||
`icalendar-ast-node-valid-p' before it is returned."
|
||||
;; TODO: support `ical:other-component', maybe like
|
||||
;; (ical:other-component (:x-name "X-NAME") templates ...)
|
||||
(unless (ical:component-type-symbol-p type)
|
||||
(error "Not an iCalendar component type: %s" type))
|
||||
;; Add templates for required properties automatically if we can:
|
||||
(when (memq type '(ical:vevent ical:vtodo ical:vjournal ical:vfreebusy))
|
||||
(unless (assq 'ical:dtstamp templates)
|
||||
(push '(ical:dtstamp (decode-time nil t))
|
||||
templates))
|
||||
(unless (assq 'ical:uid templates)
|
||||
(push `(ical:uid ,(ical:make-uid templates))
|
||||
templates)))
|
||||
(when (eq type 'ical:vcalendar)
|
||||
(unless (assq 'ical:prodid templates)
|
||||
(push `(ical:prodid ,ical:vcalendar-prodid)
|
||||
templates))
|
||||
(unless (assq 'ical:version templates)
|
||||
(push `(ical:version ,ical:vcalendar-version)
|
||||
templates))
|
||||
(unless (assq 'ical:calscale templates)
|
||||
(push '(ical:calscale "GREGORIAN")
|
||||
templates)))
|
||||
(when (null templates)
|
||||
(error "At least one template is required"))
|
||||
|
||||
(let (children lists-of-children)
|
||||
(dolist (c templates)
|
||||
(cond ((and (listp c) (ical:type-symbol-p (car c)))
|
||||
;; c is a template for a child node, so it should be
|
||||
;; recursively expanded:
|
||||
(push (cons 'ical:make-node-from-templates c)
|
||||
children))
|
||||
((and (listp c) (eq '@ (car c)))
|
||||
;; c is a template (@ L) where L evaluates to a list of children:
|
||||
(push (cadr c) lists-of-children))
|
||||
(t
|
||||
;; otherwise, just pass c through as is; this allows
|
||||
;; interleaving templates with other expressions that
|
||||
;; evaluate to syntax nodes:
|
||||
(push c children))))
|
||||
(setq children (nreverse children)
|
||||
lists-of-children (nreverse lists-of-children))
|
||||
(when (or children lists-of-children)
|
||||
`(ical:ast-node-valid-p
|
||||
(ical:make-ast-node
|
||||
(quote ,type)
|
||||
nil
|
||||
(seq-filter #'identity
|
||||
(append (list ,@children) ,@lists-of-children)))))))
|
||||
|
||||
;; TODO: allow disabling the validity check??
|
||||
(defmacro ical:make-node-from-templates (type &rest templates)
|
||||
"Construct an iCalendar syntax node of TYPE from TEMPLATES.
|
||||
|
||||
TYPE should be an iCalendar type symbol; it should not be quoted. This
|
||||
macro (and the derived macros `icalendar-make-vcalendar',
|
||||
`icalendar-make-vevent', `icalendar-make-vtodo',
|
||||
`icalendar-make-vjournal', `icalendar-make-vfreebusy',
|
||||
`icalendar-make-valarm', `icalendar-make-vtimezone',
|
||||
`icalendar-make-standard', and `icalendar-make-daylight') makes it easy
|
||||
to write iCalendar syntax nodes of TYPE as Lisp code.
|
||||
|
||||
Each expression in TEMPLATES represents a child node of the constructed
|
||||
node. It must either evaluate to such a node, or it must have one of
|
||||
the following forms:
|
||||
|
||||
\(VALUE-TYPE VALUE) - constructs a node of VALUE-TYPE containing the
|
||||
value VALUE.
|
||||
|
||||
\(PARAM-TYPE VALUE) - constructs a parameter node of PARAM-TYPE
|
||||
containing the VALUE.
|
||||
|
||||
\(PROPERTY-TYPE VALUE [PARAM ...]) - constructs a property node of
|
||||
PROPERTY-TYPE containing the value VALUE and PARAMs as child
|
||||
nodes. Each PARAM should be a template (PARAM-TYPE VALUE), as above,
|
||||
or any other expression that evaluates to a parameter node.
|
||||
|
||||
\(COMPONENT-TYPE CHILD [CHILD ...]) - constructs a component node of
|
||||
COMPONENT-TYPE with CHILDs as child nodes. Each CHILD should either be
|
||||
a template for a property (as above), a template for a
|
||||
sub-component (of the same form), or any other expression that
|
||||
evaluates to an iCalendar syntax node.
|
||||
|
||||
If TYPE is an iCalendar component or property type, a TEMPLATE can also
|
||||
have the form (@ L), where L evaluates to a list of child nodes to be
|
||||
added to the component or property node.
|
||||
|
||||
For example, an iCalendar VEVENT could be written like this:
|
||||
|
||||
(icalendar-make-node-from-templates icalendar-vevent
|
||||
(icalendar-dtstamp (decode-time (current-time) 0))
|
||||
(icalendar-uid \"some-unique-id\")
|
||||
(icalendar-summary \"Party\")
|
||||
(icalendar-location \"Robot House\")
|
||||
(icalendar-organizer \"mailto:bender@mars.edu\")
|
||||
(icalendar-attendee \"mailto:philip.j.fry@mars.edu\"
|
||||
(icalendar-partstatparam \"ACCEPTED\"))
|
||||
(icalendar-attendee \"mailto:gunther@mars.edu\"
|
||||
(icalendar-partstatparam \"DECLINED\"))
|
||||
(icalendar-categories (list \"MISCHIEF\" \"DOUBLE SECRET PROBATION\"))
|
||||
(icalendar-dtstart (icalendar-make-date-time :year 3003 :month 3 :day 13
|
||||
:hour 22 :minute 0 :second 0)
|
||||
(icalendar-tzidparam \"Mars/University_Time\")))
|
||||
|
||||
Before the constructed node is returned, it is validated by
|
||||
`icalendar-ast-node-valid-p'."
|
||||
(cond
|
||||
((not (ical:type-symbol-p type))
|
||||
(error "Not an iCalendar type symbol: %s" type))
|
||||
((ical:value-type-symbol-p type)
|
||||
`(ical:ast-node-valid-p
|
||||
(ical:make-value-node-of (quote ,type) ,(car templates))))
|
||||
((ical:param-type-symbol-p type)
|
||||
`(ical:make-param ,type ,(car templates)))
|
||||
((ical:property-type-symbol-p type)
|
||||
`(ical:make-property ,type ,(car templates) ,@(cdr templates)))
|
||||
((ical:component-type-symbol-p type)
|
||||
`(ical:make-component ,type ,@templates))))
|
||||
|
||||
(defmacro ical:make-vcalendar (&rest templates)
|
||||
"Construct an iCalendar VCALENDAR object from TEMPLATES.
|
||||
See `icalendar-make-node-from-templates' for the format of TEMPLATES.
|
||||
See `icalendar-vcalendar' for the permissible child types.
|
||||
|
||||
If TEMPLATES does not contain templates for the `icalendar-prodid' and
|
||||
`icalendar-version' properties, they will be automatically added; see
|
||||
the variables `icalendar-vcalendar-prodid' and
|
||||
`icalendar-vcalendar-version'."
|
||||
`(ical:make-node-from-templates ical:vcalendar ,@templates))
|
||||
|
||||
(defmacro ical:make-vevent (&rest templates)
|
||||
"Construct an iCalendar VEVENT node from TEMPLATES.
|
||||
See `icalendar-make-node-from-templates' for the format of TEMPLATES.
|
||||
See `icalendar-vevent' for the permissible child types.
|
||||
|
||||
If TEMPLATES does not contain templates for the `icalendar-dtstamp' and
|
||||
`icalendar-uid' properties (both required), they will be automatically
|
||||
provided."
|
||||
`(ical:make-node-from-templates ical:vevent ,@templates))
|
||||
|
||||
(defmacro ical:make-vtodo (&rest templates)
|
||||
"Construct an iCalendar VTODO node from TEMPLATES.
|
||||
See `icalendar-make-node-from-templates' for the format of TEMPLATES.
|
||||
See `icalendar-vtodo' for the permissible child types.
|
||||
|
||||
If TEMPLATES does not contain templates for the `icalendar-dtstamp' and
|
||||
`icalendar-uid' properties (both required), they will be automatically
|
||||
provided."
|
||||
`(ical:make-node-from-templates ical:vtodo ,@templates))
|
||||
|
||||
(defmacro ical:make-vjournal (&rest templates)
|
||||
"Construct an iCalendar VJOURNAL node from TEMPLATES.
|
||||
See `icalendar-make-node-from-templates' for the format of TEMPLATES.
|
||||
See `icalendar-vjournal' for the permissible child types.
|
||||
|
||||
If TEMPLATES does not contain templates for the `icalendar-dtstamp' and
|
||||
`icalendar-uid' properties (both required), they will be automatically
|
||||
provided."
|
||||
`(ical:make-node-from-templates ical:vjournal ,@templates))
|
||||
|
||||
(defmacro ical:make-vfreebusy (&rest templates)
|
||||
"Construct an iCalendar VFREEBUSY node from TEMPLATES.
|
||||
See `icalendar-make-node-from-templates' for the format of TEMPLATES.
|
||||
See `icalendar-vfreebusy' for the permissible child types.
|
||||
|
||||
If TEMPLATES does not contain templates for the `icalendar-dtstamp' and
|
||||
`icalendar-uid' properties (both required), they will be automatically
|
||||
provided."
|
||||
`(ical:make-node-from-templates ical:vfreebusy ,@templates))
|
||||
|
||||
(defmacro ical:make-valarm (&rest templates)
|
||||
"Construct an iCalendar VALARM node from TEMPLATES.
|
||||
See `icalendar-make-node-from-templates' for the format of TEMPLATES.
|
||||
See `icalendar-valarm' for the permissible child types."
|
||||
`(ical:make-node-from-templates ical:valarm ,@templates))
|
||||
|
||||
(defmacro ical:make-vtimezone (&rest templates)
|
||||
"Construct an iCalendar VTIMEZONE node from TEMPLATES.
|
||||
See `icalendar-make-node-from-templates' for the format of TEMPLATES.
|
||||
See `icalendar-vtimezone' for the permissible child types."
|
||||
`(ical:make-node-from-templates ical:vtimezone ,@templates))
|
||||
|
||||
(defmacro ical:make-standard (&rest templates)
|
||||
"Construct an iCalendar STANDARD node from TEMPLATES.
|
||||
See `icalendar-make-node-from-templates' for the format of TEMPLATES.
|
||||
See `icalendar-standard' for the permissible child types."
|
||||
`(ical:make-node-from-templates ical:standard ,@templates))
|
||||
|
||||
(defmacro ical:make-daylight (&rest templates)
|
||||
"Construct an iCalendar DAYLIGHT node from TEMPLATES.
|
||||
See `icalendar-make-node-from-templates' for the format of TEMPLATES.
|
||||
See `icalendar-daylight' for the permissible child types."
|
||||
`(ical:make-node-from-templates ical:daylight ,@templates))
|
||||
|
||||
|
||||
;;; Validation:
|
||||
|
||||
;; Errors at the validation stage:
|
||||
;; e.g. property/param values did not match, or are of the wrong type,
|
||||
;; or required properties not present in a component
|
||||
(define-error 'ical:validation-error "Invalid iCalendar data" 'ical:error)
|
||||
|
||||
(cl-defun ical:signal-validation-error (msg &key node (severity 2))
|
||||
(signal 'ical:validation-error
|
||||
(list :message msg
|
||||
:buffer (ical:ast-node-meta-get :buffer node)
|
||||
:position (ical:ast-node-meta-get :begin node)
|
||||
:severity severity
|
||||
:node node)))
|
||||
|
||||
(defun ical:ast-node-required-child-p (child parent)
|
||||
"Return non-nil if CHILD is required by PARENT's node type."
|
||||
(let* ((type (ical:ast-node-type parent))
|
||||
(child-spec (get type 'ical:child-spec))
|
||||
(child-type (ical:ast-node-type child)))
|
||||
(or (memq child-type (plist-get child-spec :one))
|
||||
(memq child-type (plist-get child-spec :one-or-more)))))
|
||||
|
||||
(declare-function ical:printable-value-type-symbol-p "icalendar-parser")
|
||||
|
||||
(defun ical:ast-node-valid-value-p (node)
|
||||
"Validate that NODE's value satisfies the requirements of its type.
|
||||
Signals an `icalendar-validation-error' if NODE's value is
|
||||
invalid, or returns NODE."
|
||||
(require 'icalendar-parser) ; for ical:printable-value-type-symbol-p
|
||||
(let* ((type (ical:ast-node-type node))
|
||||
(value (ical:ast-node-value node))
|
||||
(valtype-param (when (ical:property-type-symbol-p type)
|
||||
(ical:with-param-of node 'ical:valuetypeparam)))
|
||||
(allowed-types
|
||||
(cond ((ical:printable-value-type-symbol-p valtype-param)
|
||||
;; with an explicit `VALUE=sometype' param, this is the
|
||||
;; only allowed type:
|
||||
(list valtype-param))
|
||||
((and (ical:param-type-symbol-p type)
|
||||
(get type 'ical:value-type))
|
||||
(list (get type 'ical:value-type)))
|
||||
((ical:property-type-symbol-p type)
|
||||
(cons (get type 'ical:default-type)
|
||||
(get type 'ical:other-types)))
|
||||
(t nil))))
|
||||
(cond ((ical:value-type-symbol-p type)
|
||||
(unless (cl-typep value type) ; see `ical:define-type'
|
||||
(ical:signal-validation-error
|
||||
(format "Invalid value for `%s' node: %s" type value)
|
||||
:node node))
|
||||
node)
|
||||
((ical:component-node-p node)
|
||||
;; component types have no value, so no need to check anything
|
||||
node)
|
||||
((and (or (ical:param-type-symbol-p type)
|
||||
(ical:property-type-symbol-p type))
|
||||
(null (get type 'ical:value-type))
|
||||
(stringp value))
|
||||
;; property and param nodes with no value type are assumed to contain
|
||||
;; strings which match a value regex:
|
||||
(unless (string-match (rx-to-string (get type 'ical:value-rx)) value)
|
||||
(ical:signal-validation-error
|
||||
(format "Invalid string value for `%s' node: %s" type value)
|
||||
:node node))
|
||||
node)
|
||||
;; otherwise this is a param or property node which itself
|
||||
;; should have one or more syntax nodes as a value, so
|
||||
;; recurse on value(s):
|
||||
((ical:expects-list-of-values-p type)
|
||||
(unless (listp value)
|
||||
(ical:signal-validation-error
|
||||
(format "Expected list of values for `%s' node" type)
|
||||
:node node))
|
||||
(when allowed-types
|
||||
(dolist (v value)
|
||||
(unless (memq (ical:ast-node-type v) allowed-types)
|
||||
(ical:signal-validation-error
|
||||
(format "Value of unexpected type `%s' in `%s' node"
|
||||
(ical:ast-node-type v) type)
|
||||
:node node))))
|
||||
(mapc #'ical:ast-node-valid-value-p value)
|
||||
node)
|
||||
(t
|
||||
(unless (ical:ast-node-p value)
|
||||
(ical:signal-validation-error
|
||||
(format "Invalid value for `%s' node: %s" type value)
|
||||
:node node))
|
||||
(when allowed-types
|
||||
(unless (memq (ical:ast-node-type value) allowed-types)
|
||||
(ical:signal-validation-error
|
||||
(format "Value of unexpected type `%s' in `%s' node"
|
||||
(ical:ast-node-type value) type)
|
||||
:node node)))
|
||||
(ical:ast-node-valid-value-p value)))))
|
||||
|
||||
(defun ical:count-children-by-type (node)
|
||||
"Count NODE's children by type.
|
||||
Returns an alist mapping type symbols to the number of NODE's children
|
||||
of that type."
|
||||
(let ((children (ical:ast-node-children node))
|
||||
(map nil))
|
||||
(dolist (child children map)
|
||||
(let* ((type (ical:ast-node-type child))
|
||||
(n (alist-get type map)))
|
||||
(setf (alist-get type map) (1+ (or n 0)))))))
|
||||
|
||||
(defun ical:ast-node-valid-children-p (node)
|
||||
"Validate that NODE's children satisfy its type's :child-spec.
|
||||
|
||||
The :child-spec is associated with NODE's type by
|
||||
`icalendar-define-component', `icalendar-define-property',
|
||||
`icalendar-define-param', or `icalendar-define-type', which see.
|
||||
Signals an `icalendar-validation-error' if NODE is invalid, or returns
|
||||
NODE.
|
||||
|
||||
Note that this function does not check that the children of NODE
|
||||
are themselves valid; for that, see `ical:ast-node-valid-p'."
|
||||
(let* ((type (ical:ast-node-type node))
|
||||
(child-spec (get type 'ical:child-spec))
|
||||
(child-counts (ical:count-children-by-type node)))
|
||||
|
||||
(when child-spec
|
||||
|
||||
(dolist (child-type (plist-get child-spec :one))
|
||||
(unless (= 1 (alist-get child-type child-counts 0))
|
||||
(ical:signal-validation-error
|
||||
(format "iCalendar `%s' node must contain exactly one `%s'"
|
||||
type child-type)
|
||||
:node node)))
|
||||
|
||||
(dolist (child-type (plist-get child-spec :one-or-more))
|
||||
(unless (<= 1 (alist-get child-type child-counts 0))
|
||||
(ical:signal-validation-error
|
||||
(format "iCalendar `%s' node must contain one or more `%s'"
|
||||
type child-type)
|
||||
:node node)))
|
||||
|
||||
(dolist (child-type (plist-get child-spec :zero-or-one))
|
||||
(unless (<= (alist-get child-type child-counts 0)
|
||||
1)
|
||||
(ical:signal-validation-error
|
||||
(format "iCalendar `%s' node may contain at most one `%s'"
|
||||
type child-type)
|
||||
:node node)))
|
||||
|
||||
;; check that all child nodes are allowed:
|
||||
(unless (plist-get child-spec :allow-others)
|
||||
(let ((allowed-types (append (plist-get child-spec :one)
|
||||
(plist-get child-spec :one-or-more)
|
||||
(plist-get child-spec :zero-or-one)
|
||||
(plist-get child-spec :zero-or-more)))
|
||||
(appearing-types (mapcar #'car child-counts)))
|
||||
|
||||
(dolist (child-type appearing-types)
|
||||
(unless (member child-type allowed-types)
|
||||
(ical:signal-validation-error
|
||||
(format "`%s' may not contain `%s'" type child-type)
|
||||
:node node))))))
|
||||
;; success:
|
||||
node))
|
||||
|
||||
(defun ical:ast-node-valid-p (node &optional recursively)
|
||||
"Check that NODE is a valid iCalendar syntax node.
|
||||
By default, the check will only validate NODE itself, but if
|
||||
RECURSIVELY is non-nil, it will recursively check all its
|
||||
descendants as well. Signals an `icalendar-validation-error' if
|
||||
NODE is invalid, or returns NODE."
|
||||
(unless (ical:ast-node-p node)
|
||||
(ical:signal-validation-error
|
||||
"Not an iCalendar syntax node"
|
||||
:node node))
|
||||
|
||||
(ical:ast-node-valid-value-p node)
|
||||
(ical:ast-node-valid-children-p node)
|
||||
|
||||
(let* ((type (ical:ast-node-type node))
|
||||
(other-validator (get type 'ical:other-validator)))
|
||||
|
||||
(unless (ical:type-symbol-p type)
|
||||
(ical:signal-validation-error
|
||||
(format "Node's type `%s' is not an iCalendar type symbol" type)
|
||||
:node node))
|
||||
|
||||
(when (and other-validator (not (functionp other-validator)))
|
||||
(ical:signal-validation-error
|
||||
(format "Bad validator function `%s' for type `%s'" other-validator type)))
|
||||
|
||||
(when other-validator
|
||||
(funcall other-validator node)))
|
||||
|
||||
(when recursively
|
||||
(dolist (c (ical:ast-node-children node))
|
||||
(ical:ast-node-valid-p c recursively)))
|
||||
|
||||
;; success:
|
||||
node)
|
||||
|
||||
(provide 'icalendar-ast)
|
||||
;; Local Variables:
|
||||
;; read-symbol-shorthands: (("ical:" . "icalendar-"))
|
||||
;; End:
|
||||
;;; icalendar-ast.el ends here
|
||||
1125
lisp/calendar/icalendar-macs.el
Normal file
1125
lisp/calendar/icalendar-macs.el
Normal file
File diff suppressed because it is too large
Load diff
610
lisp/calendar/icalendar-mode.el
Normal file
610
lisp/calendar/icalendar-mode.el
Normal file
|
|
@ -0,0 +1,610 @@
|
|||
;;; icalendar-mode.el --- Major mode for iCalendar format -*- lexical-binding: t; -*-
|
||||
;;;
|
||||
|
||||
;; Copyright (C) 2024 Richard Lawrence
|
||||
|
||||
;; Author: Richard Lawrence <rwl@recursewithless.net>
|
||||
;; Keywords: calendar
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This file defines icalendar-mode, a major mode for iCalendar data.
|
||||
;; Its main job is to provide syntax highlighting using the matching
|
||||
;; functions created for iCalendar syntax in icalendar-parser.el, and to
|
||||
;; perform line unfolding and folding via format conversion.
|
||||
|
||||
;; When activated, icalendar-mode unfolds content lines if necessary.
|
||||
;; This is because the parsing functions, and thus syntax highlighting,
|
||||
;; assume that content lines have already been unfolded. When a buffer
|
||||
;; is saved, icalendar-mode also automatically folds long content if
|
||||
;; necessary, as required by RFC5545.
|
||||
|
||||
|
||||
;;; Code:
|
||||
(require 'icalendar-parser)
|
||||
(require 'format)
|
||||
|
||||
;; Faces and font lock:
|
||||
(defgroup ical:faces
|
||||
'((ical:property-name custom-face)
|
||||
(ical:property-value custom-face)
|
||||
(ical:parameter-name custom-face)
|
||||
(ical:parameter-value custom-face)
|
||||
(ical:component-name custom-face)
|
||||
(ical:keyword custom-face)
|
||||
(ical:binary-data custom-face)
|
||||
(ical:date-time-types custom-face)
|
||||
(ical:numeric-types custom-face)
|
||||
(ical:recurrence-rule custom-face)
|
||||
(ical:warning custom-face)
|
||||
(ical:ignored custom-face))
|
||||
"Faces for `icalendar-mode'."
|
||||
:version "31.1"
|
||||
:group 'icalendar
|
||||
:prefix 'icalendar)
|
||||
|
||||
(defface ical:property-name
|
||||
'((default . (:inherit font-lock-keyword-face)))
|
||||
"Face for iCalendar property names.")
|
||||
|
||||
(defface ical:property-value
|
||||
'((default . (:inherit default)))
|
||||
"Face for iCalendar property values.")
|
||||
|
||||
(defface ical:parameter-name
|
||||
'((default . (:inherit font-lock-property-name-face)))
|
||||
"Face for iCalendar parameter names.")
|
||||
|
||||
(defface ical:parameter-value
|
||||
'((default . (:inherit font-lock-property-use-face)))
|
||||
"Face for iCalendar parameter values.")
|
||||
|
||||
(defface ical:component-name
|
||||
'((default . (:inherit font-lock-constant-face)))
|
||||
"Face for iCalendar component names.")
|
||||
|
||||
(defface ical:keyword
|
||||
'((default . (:inherit font-lock-keyword-face)))
|
||||
"Face for other iCalendar keywords.")
|
||||
|
||||
(defface ical:binary-data
|
||||
'((default . (:inherit font-lock-comment-face)))
|
||||
"Face for iCalendar values that represent binary data.")
|
||||
|
||||
(defface ical:date-time-types
|
||||
'((default . (:inherit font-lock-type-face)))
|
||||
"Face for iCalendar values that represent time.
|
||||
These include dates, date-times, durations, periods, and UTC offsets.")
|
||||
|
||||
(defface ical:numeric-types
|
||||
'((default . (:inherit ical:property-value-face)))
|
||||
"Face for iCalendar values that represent integers, floats, and geolocations.")
|
||||
|
||||
(defface ical:recurrence-rule
|
||||
'((default . (:inherit font-lock-type-face)))
|
||||
"Face for iCalendar recurrence rule values.")
|
||||
|
||||
(defface ical:uri
|
||||
'((default . (:inherit ical:property-value-face :underline t)))
|
||||
"Face for iCalendar values that are URIs (including URLs and mail addresses).")
|
||||
|
||||
(defface ical:warning
|
||||
'((default . (:inherit font-lock-warning-face)))
|
||||
"Face for iCalendar syntax errors.")
|
||||
|
||||
(defface ical:ignored
|
||||
'((default . (:inherit font-lock-comment-face)))
|
||||
"Face for iCalendar syntax which is parsed but ignored.")
|
||||
|
||||
;;; Font lock:
|
||||
(defconst ical:params-font-lock-keywords
|
||||
'((ical:match-other-param
|
||||
(1 'font-lock-comment-face t t)
|
||||
(2 'font-lock-comment-face t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-value-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-tzid-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:parameter-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-sent-by-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:uri t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-rsvp-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-role-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-reltype-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-related-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-range-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-partstat-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-member-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:uri t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-language-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:parameter-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-fbtype-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-fmttype-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:parameter-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-encoding-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-dir-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:uri t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-delegated-to-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:uri t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-delegated-from-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:uri t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-cutype-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-cn-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:parameter-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-altrep-param
|
||||
(1 'ical:parameter-name t t)
|
||||
(2 'ical:uri t t)
|
||||
(3 'ical:warning t t)))
|
||||
"Entries for iCalendar property parameters in `font-lock-keywords'.")
|
||||
|
||||
(defconst ical:properties-font-lock-keywords
|
||||
'((ical:match-request-status-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-other-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-sequence-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:numeric-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-last-modified-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-dtstamp-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-created-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-trigger-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-repeat-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:numeric-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-action-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-rrule-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:recurrence-rule t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-rdate-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-exdate-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-uid-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-url-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:uri t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-related-to-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-recurrence-id-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-organizer-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:uri t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-contact-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-attendee-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:uri t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-tzurl-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:uri t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-tzoffsetto-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-tzoffsetfrom-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-tzname-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-tzid-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-transp-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-freebusy-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-duration-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-dtstart-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-due-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-dtend-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-completed-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:date-time-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-summary-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-status-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-resources-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-priority-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:numeric-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-percent-complete-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:numeric-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-location-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-geo-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:numeric-types t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-description-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-comment-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-class-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-categories-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-attach-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t)
|
||||
(13 'ical:uri t t)
|
||||
(14 'ical:binary-data t t))
|
||||
(ical:match-version-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-prodid-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-method-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:property-value t t)
|
||||
(3 'ical:warning t t))
|
||||
(ical:match-calscale-property
|
||||
(1 'ical:property-name t t)
|
||||
(2 'ical:keyword t t)
|
||||
(3 'ical:warning t t)))
|
||||
"Entries for iCalendar properties in `font-lock-keywords'.")
|
||||
|
||||
(defconst ical:ignored-properties-font-lock-keywords
|
||||
`((,(rx ical:other-property) (1 'ical:ignored keep t)
|
||||
(2 'ical:ignored keep t)))
|
||||
"Entries for iCalendar ignored properties in `font-lock-keywords'.")
|
||||
|
||||
(defconst ical:components-font-lock-keywords
|
||||
'((ical:match-vcalendar-component
|
||||
(1 'ical:keyword t t)
|
||||
(2 'ical:component-name t t))
|
||||
(ical:match-other-component
|
||||
(1 'ical:keyword t t)
|
||||
(2 'ical:component-name t t))
|
||||
(ical:match-valarm-component
|
||||
(1 'ical:keyword t t)
|
||||
(2 'ical:component-name t t))
|
||||
(ical:match-daylight-component
|
||||
(1 'ical:keyword t t)
|
||||
(2 'ical:component-name t t))
|
||||
(ical:match-standard-component
|
||||
(1 'ical:keyword t t)
|
||||
(2 'ical:component-name t t))
|
||||
(ical:match-vtimezone-component
|
||||
(1 'ical:keyword t t)
|
||||
(2 'ical:component-name t t))
|
||||
(ical:match-vfreebusy-component
|
||||
(1 'ical:keyword t t)
|
||||
(2 'ical:component-name t t))
|
||||
(ical:match-vjournal-component
|
||||
(1 'ical:keyword t t)
|
||||
(2 'ical:component-name t t))
|
||||
(ical:match-vtodo-component
|
||||
(1 'ical:keyword t t)
|
||||
(2 'ical:component-name t t))
|
||||
(ical:match-vevent-component
|
||||
(1 'ical:keyword t t)
|
||||
(2 'ical:component-name t t)))
|
||||
"Entries for iCalendar components in `font-lock-keywords'.")
|
||||
|
||||
(defvar ical:font-lock-keywords
|
||||
(append ical:params-font-lock-keywords
|
||||
ical:properties-font-lock-keywords
|
||||
ical:components-font-lock-keywords
|
||||
ical:ignored-properties-font-lock-keywords)
|
||||
"Value of `font-lock-keywords' for `icalendar-mode'.")
|
||||
|
||||
|
||||
;; The major mode:
|
||||
|
||||
;;; Mode hook
|
||||
(defvar ical:mode-hook nil
|
||||
"Hook run when activating `icalendar-mode'.")
|
||||
|
||||
;;; Activating the mode for .ics files:
|
||||
(add-to-list 'auto-mode-alist '("\\.ics\\'" . icalendar-mode))
|
||||
|
||||
;;; Syntax table
|
||||
(defvar ical:mode-syntax-table
|
||||
(let ((st (make-syntax-table)))
|
||||
;; Characters for which the standard syntax table suffices:
|
||||
;; ; (punctuation): separates some property values, and property parameters
|
||||
;; " (string): begins and ends string values
|
||||
;; : (punctuation): separates property name (and parameters) from property
|
||||
;; values
|
||||
;; , (punctuation): separates values in a list
|
||||
;; CR, LF (whitespace): content line endings
|
||||
;; space (whitespace): when at the beginning of a line, continues the
|
||||
;; previous line
|
||||
|
||||
;; Characters which need to be adjusted from the standard syntax table:
|
||||
;; = is punctuation, not a symbol constituent:
|
||||
(modify-syntax-entry ?= ". " st)
|
||||
;; / is punctuation, not a symbol constituent:
|
||||
(modify-syntax-entry ?/ ". " st)
|
||||
st)
|
||||
"Syntax table used in `icalendar-mode'.")
|
||||
|
||||
;;; Coding systems
|
||||
|
||||
;; Provide a hint to the decoding system that iCalendar files use DOS
|
||||
;; line endings. This appears to be the simplest way to ensure that
|
||||
;; `find-file' will correctly decode an iCalendar file, since decoding
|
||||
;; happens before icalendar-mode starts.
|
||||
(add-to-list 'file-coding-system-alist '("\\.ics\\'" . undecided-dos))
|
||||
|
||||
;;; Format conversion
|
||||
|
||||
;; We use the format conversion infrastructure provided by format.el,
|
||||
;; `insert-file-contents', and `write-region' to automatically perform
|
||||
;; line unfolding when icalendar-mode starts in a buffer, and line
|
||||
;; folding when it is saved to a file. See Info node `(elisp)Format
|
||||
;; Conversion' for more.
|
||||
|
||||
(defconst ical:format-definition
|
||||
'(text/calendar "iCalendar format"
|
||||
nil ; no regexp - icalendar-mode runs decode instead
|
||||
ical:unfold-region ; decoding function
|
||||
ical:folding-annotations ; encoding function
|
||||
nil ; encoding function does not modify buffer
|
||||
nil ; no need to activate a minor mode
|
||||
t) ; preserve the format when saving
|
||||
"Entry for iCalendar format in `format-alist'.")
|
||||
|
||||
(add-to-list 'format-alist ical:format-definition)
|
||||
|
||||
(defun ical:-format-decode-buffer ()
|
||||
"Call `format-decode-buffer' with the \\='text/calendar format.
|
||||
This function is intended to be run from `icalendar-mode-hook'."
|
||||
(format-decode-buffer 'text/calendar))
|
||||
|
||||
(add-hook 'ical:mode-hook #'ical:-format-decode-buffer -90)
|
||||
|
||||
(defun ical:-disable-auto-fill ()
|
||||
"Disable `auto-fill-mode' in iCalendar buffers.
|
||||
Auto-fill-mode interferes with line folding and syntax highlighting, so
|
||||
it is off by default in iCalendar buffers. This function is intended to
|
||||
be run from `icalendar-mode-hook'."
|
||||
(when auto-fill-function
|
||||
(auto-fill-mode -1)))
|
||||
|
||||
(add-hook 'ical:mode-hook #'ical:-disable-auto-fill -91)
|
||||
|
||||
;;; Commands
|
||||
|
||||
(defun ical:switch-to-unfolded-buffer ()
|
||||
"Switch to a new buffer with content lines unfolded.
|
||||
The new buffer will contain the same data as the current buffer, but
|
||||
with content lines unfolded (before decoding, if possible).
|
||||
|
||||
`Folding' means inserting a line break and a single whitespace
|
||||
character to continue lines longer than 75 octets; `unfolding'
|
||||
means removing the extra whitespace inserted by folding. The
|
||||
iCalendar standard (RFC5545) requires folding lines when
|
||||
serializing data to iCalendar format, and unfolding before
|
||||
parsing it. In `icalendar-mode', folded lines may not have proper
|
||||
syntax highlighting; this command allows you to view iCalendar
|
||||
data with proper syntax highlighting, as the parser sees it.
|
||||
|
||||
If the current buffer is visiting a file, this function will
|
||||
offer to save the buffer first, and then reload the contents from
|
||||
the file, performing unfolding with `icalendar-unfold-undecoded-region'
|
||||
before decoding it. This is the most reliable way to unfold lines.
|
||||
|
||||
If it is not visiting a file, it will unfold the new buffer
|
||||
with `icalendar-unfold-region'. This can in some cases have
|
||||
undesirable effects (see its docstring), so the original contents
|
||||
are preserved unchanged in the current buffer.
|
||||
|
||||
In both cases, after switching to the new buffer, this command
|
||||
offers to kill the original buffer.
|
||||
|
||||
It is recommended to turn off `auto-fill-mode' when viewing an
|
||||
unfolded buffer, so that filling does not interfere with syntax
|
||||
highlighting. This function offers to disable `auto-fill-mode' if
|
||||
it is enabled in the new buffer; consider using
|
||||
`visual-line-mode' instead."
|
||||
(interactive)
|
||||
(when (and buffer-file-name (buffer-modified-p))
|
||||
(when (y-or-n-p (format "Save before reloading from %s?"
|
||||
(file-name-nondirectory buffer-file-name)))
|
||||
(save-buffer)))
|
||||
(let ((old-buffer (current-buffer))
|
||||
(mmode major-mode)
|
||||
(uf-buffer (if buffer-file-name
|
||||
(ical:unfolded-buffer-from-file buffer-file-name)
|
||||
(ical:unfolded-buffer-from-buffer (current-buffer)))))
|
||||
(switch-to-buffer uf-buffer)
|
||||
;; restart original major mode, in case the new buffer is
|
||||
;; still in fundamental-mode: TODO: is this necessary?
|
||||
(funcall mmode)
|
||||
(when (y-or-n-p (format "Unfolded buffer is shown. Kill %s?"
|
||||
(buffer-name old-buffer)))
|
||||
(kill-buffer old-buffer))
|
||||
(when (and auto-fill-function (y-or-n-p "Disable auto-fill-mode?"))
|
||||
(auto-fill-mode -1))))
|
||||
|
||||
;;; Mode definition
|
||||
;;;###autoload
|
||||
(define-derived-mode icalendar-mode text-mode "iCalendar"
|
||||
"Major mode for viewing and editing iCalendar (RFC5545) data.
|
||||
|
||||
This mode provides syntax highlighting for iCalendar components,
|
||||
properties, values, and property parameters, and defines a format to
|
||||
automatically handle folding and unfolding iCalendar content lines.
|
||||
|
||||
`Folding' means inserting whitespace characters to continue long
|
||||
lines; `unfolding' means removing the extra whitespace inserted
|
||||
by folding. The iCalendar standard requires folding lines when
|
||||
serializing data to iCalendar format, and unfolding before
|
||||
parsing it.
|
||||
|
||||
Thus icalendar-mode's syntax highlighting is designed to work with
|
||||
unfolded lines. When `icalendar-mode' is activated in a buffer, it will
|
||||
automatically unfold lines using a file format conversion, and
|
||||
automatically fold lines when saving the buffer to a file; see Info
|
||||
node `(elisp)Format Conversion' for more information. It also disables
|
||||
`auto-fill-mode' if it is active, since filling interferes with line
|
||||
folding and syntax highlighting. Consider using `visual-line-mode' in
|
||||
`icalendar-mode' instead."
|
||||
:group 'icalendar
|
||||
:syntax-table ical:mode-syntax-table
|
||||
;; TODO: Keymap?
|
||||
;; TODO: buffer-local variables?
|
||||
;; TODO: indent-line-function and indentation variables
|
||||
;; TODO: mode-specific menu and context menus
|
||||
;; TODO: eldoc integration
|
||||
;; TODO: completion of keywords
|
||||
;; TODO: hook for folding in change-major-mode-hook?
|
||||
(progn
|
||||
(setq font-lock-defaults '(ical:font-lock-keywords nil t))))
|
||||
|
||||
(provide 'icalendar-mode)
|
||||
|
||||
;; Local Variables:
|
||||
;; read-symbol-shorthands: (("ical:" . "icalendar-"))
|
||||
;; End:
|
||||
;;; icalendar-mode.el ends here
|
||||
4889
lisp/calendar/icalendar-parser.el
Normal file
4889
lisp/calendar/icalendar-parser.el
Normal file
File diff suppressed because it is too large
Load diff
1993
lisp/calendar/icalendar-recur.el
Normal file
1993
lisp/calendar/icalendar-recur.el
Normal file
File diff suppressed because it is too large
Load diff
749
lisp/calendar/icalendar-utils.el
Normal file
749
lisp/calendar/icalendar-utils.el
Normal file
|
|
@ -0,0 +1,749 @@
|
|||
;;; icalendar-utils.el --- iCalendar utility functions -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2024 Richard Lawrence
|
||||
|
||||
;; Author: Richard Lawrence <rwl@recursewithless.net>
|
||||
;; Created: January 2025
|
||||
;; Keywords: calendar
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
|
||||
;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; This file contains a variety of utility functions to work with
|
||||
;; iCalendar data which are used throughout the rest of the iCalendar
|
||||
;; library. Most of the functions here deal with calendar and clock
|
||||
;; arithmetic, and help smooth over the type distinction between plain
|
||||
;; dates and date-times.
|
||||
|
||||
;;; Code:
|
||||
(require 'cl-lib)
|
||||
(require 'calendar)
|
||||
(eval-when-compile (require 'icalendar-macs))
|
||||
(require 'icalendar-parser)
|
||||
|
||||
;; Accessors for commonly used properties
|
||||
|
||||
(defun ical:component-dtstart (component)
|
||||
"Return the value of the `icalendar-dtstart' property of COMPONENT.
|
||||
COMPONENT can be any component node."
|
||||
(ical:with-property-of component 'ical:dtstart nil value))
|
||||
|
||||
(defun ical:component-dtend (component)
|
||||
"Return the value of the `icalendar-dtend' property of COMPONENT.
|
||||
COMPONENT can be any component node."
|
||||
(ical:with-property-of component 'ical:dtend nil value))
|
||||
|
||||
(defun ical:component-rdate (component)
|
||||
"Return the value of the `icalendar-rdate' property of COMPONENT.
|
||||
COMPONENT can be any component node."
|
||||
(ical:with-property-of component 'ical:rdate nil value))
|
||||
|
||||
(defun ical:component-summary (component)
|
||||
"Return the value of the `icalendar-summary' property of COMPONENT.
|
||||
COMPONENT can be any component node."
|
||||
(ical:with-property-of component 'ical:summary nil value))
|
||||
|
||||
(defun ical:component-description (component)
|
||||
"Return the value of the `icalendar-description' property of COMPONENT.
|
||||
COMPONENT can be any component node."
|
||||
(ical:with-property-of component 'ical:description nil value))
|
||||
|
||||
(defun ical:component-tzname (component)
|
||||
"Return the value of the `icalendar-tzname' property of COMPONENT.
|
||||
COMPONENT can be any component node."
|
||||
(ical:with-property-of component 'ical:tzname nil value))
|
||||
|
||||
(defun ical:component-uid (component)
|
||||
"Return the value of the `icalendar-uid' property of COMPONENT.
|
||||
COMPONENT can be any component node."
|
||||
(ical:with-property-of component 'ical:uid nil value))
|
||||
|
||||
(defun ical:component-url (component)
|
||||
"Return the value of the `icalendar-url' property of COMPONENT.
|
||||
COMPONENT can be any component node."
|
||||
(ical:with-property-of component 'ical:url nil value))
|
||||
|
||||
(defun ical:property-tzid (property)
|
||||
"Return the value of the `icalendar-tzid' parameter of PROPERTY."
|
||||
(ical:with-param-of property 'ical:tzidparam nil value))
|
||||
|
||||
;; String manipulation
|
||||
|
||||
(defun ical:strip-mailto (s)
|
||||
"Remove \"mailto:\" case-insensitively from the start of S."
|
||||
(let ((case-fold-search t))
|
||||
(replace-regexp-in-string "^mailto:" "" s)))
|
||||
|
||||
|
||||
;; Date/time
|
||||
|
||||
;; N.B. Notation: "date/time" is used in function names when a function
|
||||
;; can accept either `icalendar-date' or `icalendar-date-time' values;
|
||||
;; in contrast, "date-time" means it accepts *only*
|
||||
;; `icalendar-date-time' values, not plain dates.
|
||||
;; TODO: turn all the 'date/time' functions into methods dispatched by
|
||||
;; type?
|
||||
|
||||
(defun ical:date-time-to-date (dt)
|
||||
"Convert an `icalendar-date-time' value DT to an `icalendar-date'."
|
||||
(list (decoded-time-month dt)
|
||||
(decoded-time-day dt)
|
||||
(decoded-time-year dt)))
|
||||
|
||||
(cl-defun ical:date-to-date-time (dt &key (hour 0) (minute 0) (second 0) (tz nil))
|
||||
"Convert an `icalendar-date' value DT to an `icalendar-date-time'.
|
||||
|
||||
The following keyword arguments are accepted:
|
||||
:hour, :minute, :second - integers representing a local clock time on date DT
|
||||
:tz - an `icalendar-vtimezone' in which to interpret this clock time
|
||||
|
||||
If these arguments are all unspecified, the hour, minute, and second
|
||||
slots of the returned date-time will be zero, and it will contain no
|
||||
time zone information. See `icalendar-make-date-time' for more on these
|
||||
arguments."
|
||||
(ical:make-date-time
|
||||
:year (calendar-extract-year dt)
|
||||
:month (calendar-extract-month dt)
|
||||
:day (calendar-extract-day dt)
|
||||
:hour hour
|
||||
:minute minute
|
||||
:second second
|
||||
:tz tz))
|
||||
|
||||
(defun ical:date/time-to-date (dt)
|
||||
"Extract a Gregorian date from DT.
|
||||
An `icalendar-date' value is returned unchanged.
|
||||
An `icalendar-date-time' value is converted to an `icalendar-date'."
|
||||
(if (cl-typep dt 'ical:date)
|
||||
dt
|
||||
(ical:date-time-to-date dt)))
|
||||
|
||||
;; Type-aware accessors for date/time slots that work for both ical:date
|
||||
;; and ical:date-time:
|
||||
;; NOTE: cl-typecase ONLY works here if dt is valid according to
|
||||
;; `ical:-decoded-date-time-p'! May need to adjust this if it's
|
||||
;; necessary to work with incomplete decoded-times
|
||||
(defun ical:date/time-year (dt)
|
||||
"Return DT's year slot.
|
||||
DT may be either an `icalendar-date' or an `icalendar-date-time'."
|
||||
(cl-typecase dt
|
||||
(ical:date (calendar-extract-year dt))
|
||||
(ical:date-time (decoded-time-year dt))))
|
||||
|
||||
(defun ical:date/time-month (dt)
|
||||
"Return DT's month slot.
|
||||
DT may be either an `icalendar-date' or an `icalendar-date-time'."
|
||||
(cl-typecase dt
|
||||
(ical:date (calendar-extract-month dt))
|
||||
(ical:date-time (decoded-time-month dt))))
|
||||
|
||||
(defun ical:date/time-monthday (dt)
|
||||
"Return DT's day of the month slot.
|
||||
DT may be either an `icalendar-date' or an `icalendar-date-time'."
|
||||
(cl-typecase dt
|
||||
(ical:date (calendar-extract-day dt))
|
||||
(ical:date-time (decoded-time-day dt))))
|
||||
|
||||
(defun ical:date/time-weekno (dt &optional weekstart)
|
||||
"Return DT's ISO week number.
|
||||
DT may be either an `icalendar-date' or an `icalendar-date-time'.
|
||||
WEEKSTART defaults to 1; it represents the day which starts the week,
|
||||
and should be an integer between 0 (= Sunday) and 6 (= Saturday)."
|
||||
;; TODO: Add support for weekstart.
|
||||
;; calendar-iso-from-absolute doesn't support this yet.
|
||||
(when (and weekstart (not (= weekstart 1)))
|
||||
(error "Support for WEEKSTART other than 1 (=Monday) not implemented yet"))
|
||||
(let* ((gdate (ical:date/time-to-date dt))
|
||||
(isodate (calendar-iso-from-absolute
|
||||
(calendar-absolute-from-gregorian gdate)))
|
||||
(weekno (car isodate)))
|
||||
weekno))
|
||||
|
||||
(defun ical:date/time-weekday (dt)
|
||||
"Return DT's day of the week.
|
||||
DT may be either an `icalendar-date' or an `icalendar-date-time'."
|
||||
(cl-typecase dt
|
||||
(ical:date (calendar-day-of-week dt))
|
||||
(ical:date-time
|
||||
(or (decoded-time-weekday dt)
|
||||
;; compensate for possibly-nil weekday slot if the date-time
|
||||
;; has been constructed by `make-decoded-time'; cf. comment
|
||||
;; in `icalendar--decoded-date-time-p':
|
||||
(calendar-day-of-week (ical:date-time-to-date dt))))))
|
||||
|
||||
(defun ical:date/time-hour (dt)
|
||||
"Return DT's hour slot, or nil.
|
||||
DT may be either an `icalendar-date' or an `icalendar-date-time'."
|
||||
(when (cl-typep dt 'ical:date-time)
|
||||
(decoded-time-hour dt)))
|
||||
|
||||
(defun ical:date/time-minute (dt)
|
||||
"Return DT's minute slot, or nil.
|
||||
DT may be either an `icalendar-date' or an `icalendar-date-time'."
|
||||
(when (cl-typep dt 'ical:date-time)
|
||||
(decoded-time-minute dt)))
|
||||
|
||||
(defun ical:date/time-second (dt)
|
||||
"Return DT's second slot, or nil.
|
||||
DT may be either an `icalendar-date' or an `icalendar-date-time'."
|
||||
(when (cl-typep dt 'ical:date-time)
|
||||
(decoded-time-second dt)))
|
||||
|
||||
(defun ical:date/time-zone (dt)
|
||||
"Return DT's time zone slot, or nil.
|
||||
DT may be either an `icalendar-date' or an `icalendar-date-time'."
|
||||
(when (cl-typep dt 'ical:date-time)
|
||||
(decoded-time-zone dt)))
|
||||
|
||||
;;; Date/time comparisons and arithmetic:
|
||||
(defun ical:date< (dt1 dt2)
|
||||
"Return non-nil if date DT1 is strictly earlier than date DT2.
|
||||
DT1 and DT2 must both be `icalendar-date' values of the form (MONTH DAY YEAR)."
|
||||
(< (calendar-absolute-from-gregorian dt1)
|
||||
(calendar-absolute-from-gregorian dt2)))
|
||||
|
||||
(defun ical:date<= (dt1 dt2)
|
||||
"Return non-nil if date DT1 is earlier than or the same date as DT2.
|
||||
DT1 and DT2 must both be `icalendar-date' values of the form (MONTH DAY YEAR)."
|
||||
(or (calendar-date-equal dt1 dt2) (ical:date< dt1 dt2)))
|
||||
|
||||
(defun ical:date-time-locally-earlier (dt1 dt2 &optional or-equal)
|
||||
"Return non-nil if date-time DT1 is locally earlier than DT2.
|
||||
|
||||
Unlike `icalendar-date-time<', this function assumes both times are
|
||||
local to some time zone and does not consider their zone information.
|
||||
|
||||
If OR-EQUAL is non-nil, this function acts like `<=' rather than `<':
|
||||
it will return non-nil if DT1 and DT2 are locally the same time."
|
||||
(let ((year1 (decoded-time-year dt1))
|
||||
(year2 (decoded-time-year dt2))
|
||||
(month1 (decoded-time-month dt1))
|
||||
(month2 (decoded-time-month dt2))
|
||||
(day1 (decoded-time-day dt1))
|
||||
(day2 (decoded-time-day dt2))
|
||||
(hour1 (decoded-time-hour dt1))
|
||||
(hour2 (decoded-time-hour dt2))
|
||||
(minute1 (decoded-time-minute dt1))
|
||||
(minute2 (decoded-time-minute dt2))
|
||||
(second1 (decoded-time-second dt1))
|
||||
(second2 (decoded-time-second dt2)))
|
||||
(or (< year1 year2)
|
||||
(and (= year1 year2)
|
||||
(or (< month1 month2)
|
||||
(and (= month1 month2)
|
||||
(or (< day1 day2)
|
||||
(and (= day1 day2)
|
||||
(or (< hour1 hour2)
|
||||
(and (= hour1 hour2)
|
||||
(or (< minute1 minute2)
|
||||
(and (= minute1 minute2)
|
||||
(if or-equal
|
||||
(<= second1 second2)
|
||||
(< second1 second2))))))))))))))
|
||||
|
||||
(defun ical:date-time-locally< (dt1 dt2)
|
||||
"Return non-nil if date-time DT1 is locally strictly earlier than DT2.
|
||||
|
||||
Unlike `icalendar-date-time<', this function assumes both times are
|
||||
local to some time zone and does not consider their zone information."
|
||||
(ical:date-time-locally-earlier dt1 dt2 nil))
|
||||
|
||||
(defun ical:date-time-locally<= (dt1 dt2)
|
||||
"Return non-nil if date-time DT1 is locally earlier than, or equal to, DT2.
|
||||
|
||||
Unlike `icalendar-date-time<=', this function assumes both times are
|
||||
local to some time zone and does not consider their zone information."
|
||||
(ical:date-time-locally-earlier dt1 dt2 t))
|
||||
|
||||
(defun ical:date-time< (dt1 dt2)
|
||||
"Return non-nil if date-time DT1 is strictly earlier than DT2.
|
||||
|
||||
DT1 and DT2 must both be decoded times, and either both or neither
|
||||
should have time zone information.
|
||||
|
||||
If one has a time zone offset and the other does not, the offset
|
||||
returned from `current-time-zone' is used as the missing offset; if
|
||||
`current-time-zone' cannot provide this information, an error is
|
||||
signaled."
|
||||
(let ((zone1 (decoded-time-zone dt1))
|
||||
(zone2 (decoded-time-zone dt2)))
|
||||
(cond ((and (integerp zone1) (integerp zone2))
|
||||
(time-less-p (encode-time dt1) (encode-time dt2)))
|
||||
((and (null zone1) (null zone2))
|
||||
(ical:date-time-locally< dt1 dt2))
|
||||
(t
|
||||
;; Cf. RFC5545 Sec. 3.3.5:
|
||||
;; "The recipient of an iCalendar object with a property value
|
||||
;; consisting of a local time, without any relative time zone
|
||||
;; information, SHOULD interpret the value as being fixed to whatever
|
||||
;; time zone the "ATTENDEE" is in at any given moment. This means
|
||||
;; that two "Attendees", in different time zones, receiving the same
|
||||
;; event definition as a floating time, may be participating in the
|
||||
;; event at different actual times. Floating time SHOULD only be
|
||||
;; used where that is the reasonable behavior."
|
||||
;; I'm interpreting this to mean that if we get here, where
|
||||
;; one date-time has zone information and the other doesn't,
|
||||
;; we should use the offset from (current-time-zone).
|
||||
(let* ((user-tz (current-time-zone))
|
||||
(user-offset (car user-tz))
|
||||
(dt1z (ical:date-time-variant dt1 :zone (or zone1 user-offset)))
|
||||
(dt2z (ical:date-time-variant dt2 :zone (or zone2 user-offset))))
|
||||
(if user-offset
|
||||
(time-less-p (encode-time dt1z) (encode-time dt2z))
|
||||
(error "Too little zone information for comparison: %s %s"
|
||||
dt1 dt2)))))))
|
||||
|
||||
;; Two different notions of equality are relevant to decoded times:
|
||||
;; strict equality (`icalendar-date-time=') of all slots, or
|
||||
;; simultaneity (`icalendar-date-time-simultaneous-p').
|
||||
;; Most tests probably want the strict notion, because it distinguishes
|
||||
;; between simultaneous events decoded into different time zones,
|
||||
;; whereas most user-facing functions (e.g. sorting events by date and time)
|
||||
;; probably want simultaneity.
|
||||
(defun ical:date-time= (dt1 dt2)
|
||||
"Return non-nil if DT1 and DT2 are decoded-times with identical slot values.
|
||||
|
||||
Note that this function returns nil if DT1 and DT2 represent times in
|
||||
different time zones, even if they are simultaneous. For the latter, see
|
||||
`icalendar-date-time-simultaneous-p'."
|
||||
(equal dt1 dt2))
|
||||
|
||||
(defun ical:date-time-locally-simultaneous-p (dt1 dt2)
|
||||
"Return non-nil if DT1 and DT2 are locally simultaneous date-times.
|
||||
Note that this function ignores zone information in dt1 and dt2. It
|
||||
returns non-nil if DT1 and DT2 represent the same clock time in
|
||||
different time zones, even if they encode to different absolute times."
|
||||
(and (eq (decoded-time-year dt1) (decoded-time-year dt2))
|
||||
(eq (decoded-time-month dt1) (decoded-time-month dt2))
|
||||
(eq (decoded-time-day dt1) (decoded-time-day dt2))
|
||||
(eq (decoded-time-hour dt1) (decoded-time-hour dt2))
|
||||
(eq (decoded-time-minute dt1) (decoded-time-minute dt2))
|
||||
(eq (decoded-time-second dt1) (decoded-time-second dt2))))
|
||||
|
||||
(defun ical:date-time-simultaneous-p (dt1 dt2)
|
||||
"Return non-nil if DT1 and DT2 are simultaneous date-times.
|
||||
|
||||
This function returns non-nil if DT1 and DT2 encode to the same Lisp
|
||||
timestamp. Thus they can count as simultaneous even if they represent
|
||||
times in different timezones. If both date-times lack an offset from
|
||||
UTC, they are treated as simultaneous if they encode to the same
|
||||
timestamp in UTC.
|
||||
|
||||
If only one date-time has an offset, they are treated as
|
||||
non-simultaneous if they represent different clock times according to
|
||||
`icalendar-date-time-locally-simultaneous-p'. Otherwise an error is
|
||||
signaled."
|
||||
(let ((zone1 (decoded-time-zone dt1))
|
||||
(zone2 (decoded-time-zone dt2)))
|
||||
(cond ((and (integerp zone1) (integerp zone2))
|
||||
(time-equal-p (encode-time dt1) (encode-time dt2)))
|
||||
((and (null zone1) (null zone2))
|
||||
(time-equal-p (encode-time (ical:date-time-variant dt1 :zone 0))
|
||||
(encode-time (ical:date-time-variant dt2 :zone 0))))
|
||||
(t
|
||||
;; Best effort:
|
||||
;; TODO: I'm not convinced this is the right thing to do yet.
|
||||
;; Might want to be stricter here and fix the problem of comparing
|
||||
;; times with and without zone information elsewhere.
|
||||
(if (ical:date-time-locally-simultaneous-p dt1 dt2)
|
||||
(error "Missing zone information: %s %s" dt1 dt2)
|
||||
nil)))))
|
||||
|
||||
(defun ical:date-time<= (dt1 dt2)
|
||||
"Return non-nil if DT1 is earlier than, or simultaneous with, DT2.
|
||||
DT1 and DT2 must both be decoded times, and either both or neither must have
|
||||
time zone information."
|
||||
(or (ical:date-time< dt1 dt2)
|
||||
(ical:date-time-simultaneous-p dt1 dt2)))
|
||||
|
||||
(defun ical:date/time< (dt1 dt2)
|
||||
"Return non-nil if DT1 is strictly earlier than DT2.
|
||||
DT1 and DT2 must be either `icalendar-date' or `icalendar-date-time'
|
||||
values. If they are not of the same type, only the date in the
|
||||
`icalendar-date-time' value will be considered."
|
||||
(cl-typecase dt1
|
||||
(ical:date
|
||||
(if (cl-typep dt2 'ical:date)
|
||||
(ical:date< dt1 dt2)
|
||||
(ical:date< dt1 (ical:date-time-to-date dt2))))
|
||||
|
||||
(ical:date-time
|
||||
(if (cl-typep dt2 'ical:date-time)
|
||||
(ical:date-time< dt1 dt2)
|
||||
(ical:date< (ical:date-time-to-date dt1) dt2)))))
|
||||
|
||||
(defun ical:date/time<= (dt1 dt2)
|
||||
"Return non-nil if DT1 is earlier than or simultaneous to DT2.
|
||||
DT1 and DT2 must be either `icalendar-date' or `icalendar-date-time'
|
||||
values. If they are not of the same type, only the date in the
|
||||
`icalendar-date-time' value will be considered."
|
||||
(cl-typecase dt1
|
||||
(ical:date
|
||||
(if (cl-typep dt2 'ical:date)
|
||||
(ical:date<= dt1 dt2)
|
||||
(ical:date<= dt1 (ical:date-time-to-date dt2))))
|
||||
|
||||
(ical:date-time
|
||||
(if (cl-typep dt2 'ical:date-time)
|
||||
(ical:date-time<= dt1 dt2)
|
||||
(ical:date<= (ical:date-time-to-date dt1) dt2)))))
|
||||
|
||||
(defun ical:date/time-min (&rest dts)
|
||||
"Return the earliest date or date-time among DTS.
|
||||
|
||||
The DTS may be any `icalendar-date' or `icalendar-date-time' values, and
|
||||
will be ordered by `icalendar-date/time<='."
|
||||
(car (sort dts :lessp #'ical:date/time<=)))
|
||||
|
||||
(defun ical:date/time-max (&rest dts)
|
||||
"Return the latest date or date-time among DTS.
|
||||
|
||||
The DTS may be any `icalendar-date' or `icalendar-date-time' values, and
|
||||
will be ordered by `icalendar-date/time<='."
|
||||
(car (sort dts :reverse t :lessp #'ical:date/time<=)))
|
||||
|
||||
(defun ical:date-add (date unit n)
|
||||
"Add N UNITs to DATE.
|
||||
|
||||
UNIT should be `:year', `:month', `:week', or `:day'; time units will be
|
||||
ignored. N may be a positive or negative integer."
|
||||
(if (memq unit '(:hour :minute :second))
|
||||
date
|
||||
(let* ((dt (ical:make-date-time :year (calendar-extract-year date)
|
||||
:month (calendar-extract-month date)
|
||||
:day (calendar-extract-day date)))
|
||||
(delta (if (eq unit :week)
|
||||
(make-decoded-time :day (* 7 n))
|
||||
(make-decoded-time unit n)))
|
||||
(new-dt (decoded-time-add dt delta)))
|
||||
(ical:date-time-to-date new-dt))))
|
||||
|
||||
(declare-function icalendar-recur-tz-decode-time "icalendar-recur")
|
||||
|
||||
(defun ical:date-time-add (dt delta &optional vtimezone)
|
||||
"Like `decoded-time-add', but also updates weekday and time zone slots.
|
||||
|
||||
DT and DELTA should be `icalendar-date-time' values (decoded times), as
|
||||
in `decoded-time-add'. VTIMEZONE, if given, should be an
|
||||
`icalendar-vtimezone'. The resulting date-time will be given the offset
|
||||
determined by VTIMEZONE at the local time determined by adding DELTA to
|
||||
DT.
|
||||
|
||||
This function assumes that time units in DELTA larger than an hour
|
||||
should not affect the local clock time in the result, even when crossing
|
||||
an observance boundary in VTIMEZONE. This means that e.g. if DT is at
|
||||
9AM daylight savings time on the day before the transition to standard
|
||||
time, then the result of adding a DELTA of two days will be at 9AM
|
||||
standard time, even though this is not exactly 48 hours later. Adding a
|
||||
DELTA of 48 hours, on the other hand, will result in a time exactly 48
|
||||
hours later, but at a different local time."
|
||||
(require 'icalendar-recur) ; for icr:tz-decode-time; avoids circular requires
|
||||
(if (not vtimezone)
|
||||
;; the simple case: we have no time zone info, so just use
|
||||
;; `decoded-time-add':
|
||||
(let ((sum (decoded-time-add dt delta)))
|
||||
(ical:date-time-variant sum))
|
||||
;; `decoded-time-add' does not take time zone shifts into account,
|
||||
;; so we need to do the adjustment ourselves. We first add the units
|
||||
;; larger than an hour using `decoded-time-add', holding the clock
|
||||
;; time fixed, as described in the docstring. Then we add the time
|
||||
;; units as a fixed number of seconds and re-decode the resulting
|
||||
;; absolute time into the time zone.
|
||||
(let* ((cal-delta (make-decoded-time :year (or (decoded-time-year delta) 0)
|
||||
:month (or (decoded-time-month delta) 0)
|
||||
:day (or (decoded-time-day delta) 0)))
|
||||
(cal-sum (decoded-time-add dt cal-delta))
|
||||
(dt-w/zone (ical:date-time-variant cal-sum
|
||||
:tz vtimezone))
|
||||
(secs-delta (+ (or (decoded-time-second delta) 0)
|
||||
(* 60 (or (decoded-time-minute delta) 0))
|
||||
(* 60 60 (or (decoded-time-hour delta) 0))))
|
||||
(sum-ts (time-add (encode-time dt-w/zone) secs-delta)))
|
||||
(icalendar-recur-tz-decode-time sum-ts vtimezone))))
|
||||
|
||||
;; TODO: rework so that it's possible to add dur-values to plain dates.
|
||||
;; Perhaps rename this to "date/time-inc" or so, or use kwargs to allow
|
||||
;; multiple units, or...
|
||||
(defun ical:date/time-add (dt unit n &optional vtimezone)
|
||||
"Add N UNITs to DT.
|
||||
|
||||
DT should be an `icalendar-date' or `icalendar-date-time'. UNIT should
|
||||
be `:year', `:month', `:week', `:day', `:hour', `:minute', or `:second';
|
||||
time units will be ignored if DT is an `icalendar-date'. N may be a
|
||||
positive or negative integer."
|
||||
(cl-typecase dt
|
||||
(ical:date-time
|
||||
(let ((delta (if (eq unit :week) (make-decoded-time :day (* 7 n))
|
||||
(make-decoded-time unit n))))
|
||||
(ical:date-time-add dt delta vtimezone)))
|
||||
(ical:date (ical:date-add dt unit n))))
|
||||
|
||||
(defun ical:date/time-add-duration (start duration &optional vtimezone)
|
||||
"Return the end date(-time) which is a length of DURATION after START.
|
||||
|
||||
START should be an `icalendar-date' or `icalendar-date-time'; the
|
||||
returned value will be of the same type as START. DURATION should be an
|
||||
`icalendar-dur-value'. VTIMEZONE, if specified, should be the
|
||||
`icalendar-vtimezone' representing the time zone of START."
|
||||
(if (integerp duration)
|
||||
;; number of weeks:
|
||||
(setq duration (make-decoded-time :day (* 7 duration))))
|
||||
(cl-typecase start
|
||||
(ical:date
|
||||
(ical:date-time-to-date
|
||||
(ical:date-time-add (ical:date-to-date-time start) duration)))
|
||||
(ical:date-time
|
||||
(ical:date-time-add start duration vtimezone))))
|
||||
|
||||
(defun ical:duration-between (start end)
|
||||
"Return the duration between START and END.
|
||||
|
||||
START should be an `icalendar-date' or `icalendar-date-time'; END must
|
||||
be of the same type as START. The returned value is an
|
||||
`icalendar-dur-value', i.e., a time delta in the sense of
|
||||
`decoded-time-add'."
|
||||
(cl-typecase start
|
||||
(ical:date
|
||||
(make-decoded-time :day (- (calendar-absolute-from-gregorian end)
|
||||
(calendar-absolute-from-gregorian start))))
|
||||
(ical:date-time
|
||||
(let* ((start-abs (time-convert (encode-time start) 'integer))
|
||||
(end-abs (time-convert (encode-time end) 'integer))
|
||||
(dur-secs (- end-abs start-abs))
|
||||
(days (/ dur-secs (* 60 60 24)))
|
||||
(dur-nodays (mod dur-secs (* 60 60 24)))
|
||||
(hours (/ dur-nodays (* 60 60)))
|
||||
(dur-nohours (mod dur-nodays (* 60 60)))
|
||||
(minutes (/ dur-nohours 60))
|
||||
(seconds (mod dur-nohours 60)))
|
||||
(make-decoded-time :day days
|
||||
:hour hours :minute minutes :second seconds)))))
|
||||
|
||||
(defun ical:date/time-to-local (dt)
|
||||
"Reinterpret DT in Emacs local time if necessary.
|
||||
If DT is an `icalendar-date-time', encode and re-decode it into Emacs
|
||||
local time. If DT is an `icalendar-date', return it unchanged."
|
||||
(cl-typecase dt
|
||||
(ical:date dt)
|
||||
(ical:date-time
|
||||
(ical:date-time-variant ; ensure weekday is present too
|
||||
(decode-time (encode-time dt))))))
|
||||
|
||||
(declare-function icalendar-recur-subintervals-to-dates "icalendar-recur")
|
||||
|
||||
(defun ical:dates-until (start end &optional locally)
|
||||
"Return a list of `icalendar-date' values between START and END.
|
||||
|
||||
START and END may be either `icalendar-date' or `icalendar-date-time'
|
||||
values. START is an inclusive lower bound, and END is an exclusive
|
||||
upper bound. (Note, however, that if END is a date-time and its time is
|
||||
after midnight, then its date will be included in the returned list.)
|
||||
|
||||
If LOCALLY is non-nil and START and END are date-times, these will be
|
||||
interpreted into Emacs local time, so that the dates returned are valid
|
||||
for the local time zone."
|
||||
(require 'icalendar-recur)
|
||||
(when locally
|
||||
(when (cl-typep start 'ical:date-time)
|
||||
(setq start (ical:date/time-to-local start)))
|
||||
(when (cl-typep end 'ical:date-time)
|
||||
(setq end (ical:date/time-to-local end))))
|
||||
(cl-typecase start
|
||||
(ical:date
|
||||
(cl-typecase end
|
||||
(ical:date
|
||||
(icalendar-recur-subintervals-to-dates
|
||||
(list (list (ical:date-to-date-time start)
|
||||
(ical:date-to-date-time end)))))
|
||||
(ical:date-time
|
||||
(icalendar-recur-subintervals-to-dates
|
||||
(list (list (ical:date-to-date-time start) end))))))
|
||||
(ical:date-time
|
||||
(cl-typecase end
|
||||
(ical:date
|
||||
(icalendar-recur-subintervals-to-dates
|
||||
(list (list start (ical:date-to-date-time end)))))
|
||||
(ical:date-time
|
||||
(icalendar-recur-subintervals-to-dates (list (list start end))))))))
|
||||
|
||||
|
||||
(cl-defun ical:make-date-time (&key second minute hour day month year
|
||||
(dst -1 given-dst) zone tz)
|
||||
"Make an `icalendar-date-time' from the given keyword arguments.
|
||||
|
||||
This function is like `make-decoded-time', except that it automatically
|
||||
sets the weekday slot set based on the date arguments, and it accepts an
|
||||
additional keyword argument: `:tz'. If provided, its value should be an
|
||||
`icalendar-vtimezone', and the `:zone' and `:dst' arguments should not
|
||||
be provided. In this case, the zone and dst slots in the returned
|
||||
date-time will be adjusted to the correct values in the given time zone
|
||||
for the local time represented by the remaining arguments."
|
||||
(when (and tz (or zone given-dst))
|
||||
(error "Possibly conflicting time zone data in args"))
|
||||
(apply #'ical:date-time-variant (make-decoded-time)
|
||||
`(:second ,second :minute ,minute :hour ,hour
|
||||
:day ,day :month ,month :year ,year
|
||||
;; Don't pass these keywords unless they were given explicitly.
|
||||
;; TODO: is there a cleaner way to write this?
|
||||
,@(when tz (list :tz tz))
|
||||
,@(when given-dst (list :dst dst))
|
||||
,@(when zone (list :zone zone)))))
|
||||
|
||||
(declare-function icalendar-recur-tz-set-zone "icalendar-recur")
|
||||
|
||||
(cl-defun ical:date-time-variant (dt &key second minute hour
|
||||
day month year
|
||||
(dst -1 given-dst)
|
||||
(zone nil given-zone)
|
||||
tz)
|
||||
"Return a variant of DT with slots modified as in the given arguments.
|
||||
|
||||
DT should be an `icalendar-date-time'; the keyword arguments have the
|
||||
same meanings as in `make-decoded-time'. The returned variant will have
|
||||
slot values as specified by the arguments or copied from DT, except that
|
||||
the weekday slot will be updated if necessary, and the zone and dst
|
||||
fields will not be set unless given explicitly (because varying the date
|
||||
and clock time generally invalidates the time zone information in DT).
|
||||
|
||||
One additional keyword argument is accepted: `:tz'. If provided, its
|
||||
value should be an `icalendar-vtimezone', an `icalendar-utc-offset', or
|
||||
the symbol \\='preserve. If it is a time zone component, the zone and
|
||||
dst slots in the returned variant will be adjusted to the correct
|
||||
values in the given time zone for the local time represented by the
|
||||
variant. If it is a UTC offset, the variant's zone slot will contain
|
||||
this value, but its dst slot will not be adjusted. If it is the symbol
|
||||
\\='preserve, then both the zone and dst fields are copied from DT into
|
||||
the variant."
|
||||
(require 'icalendar-recur) ; for icr:tz-set-zone; avoids circular requires
|
||||
(let ((variant
|
||||
(make-decoded-time :second (or second (decoded-time-second dt))
|
||||
:minute (or minute (decoded-time-minute dt))
|
||||
:hour (or hour (decoded-time-hour dt))
|
||||
:day (or day (decoded-time-day dt))
|
||||
:month (or month (decoded-time-month dt))
|
||||
:year (or year (decoded-time-year dt))
|
||||
;; For zone and dst slots, trust the value
|
||||
;; if explicitly specified or explicitly
|
||||
;; requested to preserve, but not otherwise
|
||||
:dst (cond (given-dst dst)
|
||||
((eq 'preserve tz) (decoded-time-dst dt))
|
||||
(t -1))
|
||||
:zone (cond (given-zone zone)
|
||||
((eq 'preserve tz) (decoded-time-zone dt))
|
||||
(t nil)))))
|
||||
;; update weekday slot when possible, since it depends on the date
|
||||
;; slots, which might have changed. (It's not always possible,
|
||||
;; because pure time values are also represented as decoded-times,
|
||||
;; with empty date slots.)
|
||||
(unless (or (null (decoded-time-year variant))
|
||||
(null (decoded-time-month variant))
|
||||
(null (decoded-time-day variant)))
|
||||
(setf (decoded-time-weekday variant)
|
||||
(calendar-day-of-week (ical:date-time-to-date variant))))
|
||||
;; if given a time zone or UTC offset, update zone and dst slots,
|
||||
;; which also might have changed:
|
||||
(when (and tz (not (eq 'preserve tz)))
|
||||
(icalendar-recur-tz-set-zone variant tz))
|
||||
variant))
|
||||
|
||||
(defun ical:date/time-in-period-p (dt period &optional vtimezone)
|
||||
"Return non-nil if DT occurs within PERIOD.
|
||||
|
||||
DT can be an `icalendar-date' or `icalendar-date-time' value. PERIOD
|
||||
should be an `icalendar-period' value. VTIMEZONE, if given, is passed
|
||||
to `icalendar-period-end' to compute the end time of the period if it
|
||||
was not specified explicitly."
|
||||
(and (ical:date/time<= (ical:period-start period) dt)
|
||||
(ical:date/time< dt (ical:period-end period vtimezone))))
|
||||
|
||||
;; TODO: surely this exists already?
|
||||
(defun ical:time<= (a b)
|
||||
"Compare two Lisp timestamps A and B: is A <= B?"
|
||||
(or (time-equal-p a b)
|
||||
(time-less-p a b)))
|
||||
|
||||
(defun ical:number-of-weeks (year &optional weekstart)
|
||||
"Return the number of weeks in (Gregorian) YEAR.
|
||||
|
||||
RFC5545 defines week 1 as the first week to include at least four days
|
||||
in the year. Weeks are assumed to start on Monday (= 1) unless WEEKSTART
|
||||
is specified, in which case it should be an integer between 0 (= Sunday)
|
||||
and 6 (= Saturday)."
|
||||
;; There are 53 weeks in a year if Jan 1 is the fourth day after
|
||||
;; WEEKSTART, e.g. if the week starts on Monday and Jan 1 is a
|
||||
;; Thursday, or in a leap year if Jan 1 is the third day after WEEKSTART
|
||||
(let* ((jan1wd (calendar-day-of-week (list 1 1 year)))
|
||||
(delta (mod (- jan1wd (or weekstart 1)) 7)))
|
||||
(if (or (= 4 delta)
|
||||
(and (= 3 delta) (calendar-leap-year-p year)))
|
||||
53
|
||||
52)))
|
||||
|
||||
(defun ical:start-of-weekno (weekno year &optional weekstart)
|
||||
"Return the start of the WEEKNOth week in the (Gregorian) YEAR.
|
||||
|
||||
RFC5545 defines week 1 as the first week to include at least four days
|
||||
in the year. Weeks are assumed to start on Monday (= 1) unless WEEKSTART
|
||||
is specified, in which case it should be an integer between 0 (= Sunday)
|
||||
and 6 (= Saturday). The returned value is an `icalendar-date'.
|
||||
|
||||
If WEEKNO is negative, it refers to the WEEKNOth week before the end of
|
||||
the year: -1 is the last week of the year, -2 second to last, etc."
|
||||
(calendar-gregorian-from-absolute
|
||||
(+
|
||||
(* 7 (if (< 0 weekno)
|
||||
(1- weekno)
|
||||
(+ 1 weekno (ical:number-of-weeks year weekstart))))
|
||||
(calendar-dayname-on-or-before
|
||||
(or weekstart 1)
|
||||
;; Three days after Jan 1. gives us the nearest occurrence;
|
||||
;; see `calendar-dayname-on-or-before'
|
||||
(+ 3 (calendar-absolute-from-gregorian (list 1 1 year)))))))
|
||||
|
||||
(defun ical:nth-weekday-in (n weekday year &optional month)
|
||||
"Return the Nth WEEKDAY in YEAR or MONTH.
|
||||
|
||||
If MONTH is specified, it refers to MONTH in YEAR, and N acts as an
|
||||
index for WEEKDAYs within the month. Otherwise, N acts as an index for
|
||||
WEEKDAYs within the entire YEAR.
|
||||
|
||||
N should be an integer. If N<0, it counts from the end of the month or
|
||||
year: if N=-1, it refers to the last WEEKDAY in the month or year, if
|
||||
N=-2 the second to last, and so on."
|
||||
(if month
|
||||
(calendar-nth-named-day n weekday month year)
|
||||
(let* ((jan1 (calendar-absolute-from-gregorian (list 1 1 year)))
|
||||
(dec31 (calendar-absolute-from-gregorian (list 12 31 year))))
|
||||
;; Adapted from `calendar-nth-named-absday'.
|
||||
;; TODO: we could generalize that function to make month an optional
|
||||
;; argument, but that would mean changing its interface.
|
||||
(calendar-gregorian-from-absolute
|
||||
(if (> n 0)
|
||||
(+ (* 7 (1- n))
|
||||
(calendar-dayname-on-or-before
|
||||
weekday
|
||||
(+ 6 jan1)))
|
||||
(+ (* 7 (1+ n))
|
||||
(calendar-dayname-on-or-before
|
||||
weekday
|
||||
dec31)))))))
|
||||
|
||||
(provide 'icalendar-utils)
|
||||
;; Local Variables:
|
||||
;; read-symbol-shorthands: (("ical:" . "icalendar-"))
|
||||
;; End:
|
||||
;;; icalendar-utils.el ends here
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,8 @@
|
|||
&5/15/2012 15:00-15:30 Query
|
||||
Location: phone
|
||||
Status: confirmed
|
||||
Organizer: A. Luser <a.luser@foo.com>
|
||||
Attendee: Luser, Other <other.luser@foo.com> (needs-action)
|
||||
Access: public
|
||||
UID: 040000008200E00074C5B7101A82E0080000000020FFAED0CFEFCC01000000000000000010000000575268034ECDB649A15349B1BF240F15
|
||||
Description: Whassup?
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
&15/5/2012 15:00-15:30 Query
|
||||
Location: phone
|
||||
Status: confirmed
|
||||
Organizer: A. Luser <a.luser@foo.com>
|
||||
Attendee: Luser, Other <other.luser@foo.com> (needs-action)
|
||||
Access: public
|
||||
UID: 040000008200E00074C5B7101A82E0080000000020FFAED0CFEFCC01000000000000000010000000575268034ECDB649A15349B1BF240F15
|
||||
Description: Whassup?
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
&2012/5/15 15:00-15:30 Query
|
||||
Location: phone
|
||||
Status: confirmed
|
||||
Organizer: A. Luser <a.luser@foo.com>
|
||||
Attendee: Luser, Other <other.luser@foo.com> (needs-action)
|
||||
Access: public
|
||||
UID: 040000008200E00074C5B7101A82E0080000000020FFAED0CFEFCC01000000000000000010000000575268034ECDB649A15349B1BF240F15
|
||||
Description: Whassup?
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
&12/8/2014 18:30-22:55 Norwegian til Tromsoe-Langnes -
|
||||
Location: Stavanger-Sola
|
||||
Category: Appointment
|
||||
Access: public
|
||||
UID: RFCALITEM1
|
||||
Description: Fly med Norwegian, reservasjon. Fra Stavanger til Tromsø 8. des 2014 18:30, DY545Fly med Norwegian, reservasjon . Fra Stavanger til Tromsø 8. des 2014 21:00, DY390
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
&8/12/2014 18:30-22:55 Norwegian til Tromsoe-Langnes -
|
||||
Location: Stavanger-Sola
|
||||
Category: Appointment
|
||||
Access: public
|
||||
UID: RFCALITEM1
|
||||
Description: Fly med Norwegian, reservasjon. Fra Stavanger til Tromsø 8. des 2014 18:30, DY545Fly med Norwegian, reservasjon . Fra Stavanger til Tromsø 8. des 2014 21:00, DY390
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
&2014/12/8 18:30-22:55 Norwegian til Tromsoe-Langnes -
|
||||
Location: Stavanger-Sola
|
||||
Category: Appointment
|
||||
Access: public
|
||||
UID: RFCALITEM1
|
||||
Description: Fly med Norwegian, reservasjon. Fra Stavanger til Tromsø 8. des 2014 18:30, DY545Fly med Norwegian, reservasjon . Fra Stavanger til Tromsø 8. des 2014 21:00, DY390
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
&%%(diary-rrule :rule '((FREQ MONTHLY) (BYDAY ((3 . 1))) (INTERVAL 1))
|
||||
:exclude
|
||||
'((0 46 11 6 1 2016 3 -1 0) (0 46 11 3 2 2016 3 -1 0)
|
||||
(0 46 11 2 3 2016 3 -1 0) (0 46 10 4 5 2016 3 -1 0)
|
||||
(0 46 10 1 6 2016 3 -1 0))
|
||||
:start '(0 46 12 2 12 2015 3 -1 nil) :duration
|
||||
'(0 14 3 0 nil nil nil -1 nil)) Summary
|
||||
Location: Loc
|
||||
Access: private
|
||||
UID: 9188710a-08a7-4061-bae3-d4cf4972599a
|
||||
Description: Desc
|
||||
|
|
@ -0,0 +1 @@
|
|||
&11/5/2018 21:00 event with same start/end time
|
||||
|
|
@ -0,0 +1 @@
|
|||
&5/11/2018 21:00 event with same start/end time
|
||||
|
|
@ -0,0 +1 @@
|
|||
&2018/11/5 21:00 event with same start/end time
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 1) (BYDAY (1 3 4 5)))
|
||||
:start '(0 30 11 21 4 2010 3 -1 nil) :duration
|
||||
'(0 30 0 0 nil nil nil -1 nil)) Scrum
|
||||
Status: confirmed
|
||||
Access: public
|
||||
UID: 8814e3f9-7482-408f-996c-3bfe486a1262
|
||||
|
||||
&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 1) (BYDAY (2 4))) :start
|
||||
'(4 22 2010) :duration
|
||||
'(nil nil nil 1 nil nil nil -1 nil)) Tues + Thurs thinking
|
||||
Access: public
|
||||
UID: 8814e3f9-7482-408f-996c-3bfe486a1263
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
&%%(diary-rrule :rule
|
||||
'((FREQ DAILY) (UNTIL (12 29 2001)) (INTERVAL 1) (WKST 0))
|
||||
:start '(12 21 2001)) Urlaub
|
||||
Access: public
|
||||
UID: 20041127T183329Z-18215-1001-4536-49109@andromeda
|
||||
|
|
@ -0,0 +1 @@
|
|||
&%%(diary-block 2 17 2005 2 23 2005) duration
|
||||
|
|
@ -0,0 +1 @@
|
|||
&%%(diary-block 17 2 2005 23 2 2005) duration
|
||||
|
|
@ -0,0 +1 @@
|
|||
&%%(diary-block 2005 2 17 2005 2 23) duration
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
SUMMARY: Testing legacy `icalendar-import-format' function
|
||||
DESCRIPTION: described
|
||||
CLASS: private
|
||||
LOCATION: somewhere
|
||||
ORGANIZER: mailto:baz@example.com
|
||||
STATUS: CONFIRMED
|
||||
URL: http://example.com/foo/baz
|
||||
UID: some-unique-id-here
|
||||
DTSTART: 20250919T090000
|
||||
DTEND: 20250919T113000
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
9/19/2025 09:00-11:30 Testing legacy `icalendar-import-format*' vars
|
||||
CLASS=private
|
||||
DESCRIPTION=described
|
||||
LOCATION=somewhere
|
||||
ORGANIZER=mailto:baz@example.com
|
||||
STATUS=confirmed
|
||||
URL=http://example.com/foo/baz
|
||||
UID=some-unique-id-here
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
19/9/2025 09:00-11:30 Testing legacy `icalendar-import-format*' vars
|
||||
CLASS=private
|
||||
DESCRIPTION=described
|
||||
LOCATION=somewhere
|
||||
ORGANIZER=mailto:baz@example.com
|
||||
STATUS=confirmed
|
||||
URL=http://example.com/foo/baz
|
||||
UID=some-unique-id-here
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
2025/9/19 09:00-11:30 Testing legacy `icalendar-import-format*' vars
|
||||
CLASS=private
|
||||
DESCRIPTION=described
|
||||
LOCATION=somewhere
|
||||
ORGANIZER=mailto:baz@example.com
|
||||
STATUS=confirmed
|
||||
URL=http://example.com/foo/baz
|
||||
UID=some-unique-id-here
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
&7/23/2011 event-1
|
||||
|
||||
&7/24/2011 event-2
|
||||
|
||||
&7/25/2011 event-3a
|
||||
|
||||
&7/25/2011 event-3b
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
&23/7/2011 event-1
|
||||
|
||||
&24/7/2011 event-2
|
||||
|
||||
&25/7/2011 event-3a
|
||||
|
||||
&25/7/2011 event-3b
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
&2011/7/23 event-1
|
||||
|
||||
&2011/7/24 event-2
|
||||
|
||||
&2011/7/25 event-3a
|
||||
|
||||
&2011/7/25 event-3b
|
||||
|
|
@ -0,0 +1 @@
|
|||
&9/19/2003 09:00-11:30 non-recurring
|
||||
|
|
@ -0,0 +1 @@
|
|||
&19/9/2003 09:00-11:30 non-recurring
|
||||
|
|
@ -0,0 +1 @@
|
|||
&2003/9/19 09:00-11:30 non-recurring
|
||||
|
|
@ -0,0 +1 @@
|
|||
&9/19/2003 non-recurring allday
|
||||
|
|
@ -0,0 +1 @@
|
|||
&19/9/2003 non-recurring allday
|
||||
|
|
@ -0,0 +1 @@
|
|||
&2003/9/19 non-recurring allday
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&11/23/2004 14:45-15:45 another example
|
||||
Status: tentative
|
||||
Access: private
|
||||
UID: 6161a312-3902-11d9-b512-f764153bb28b
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&23/11/2004 14:45-15:45 another example
|
||||
Status: tentative
|
||||
Access: private
|
||||
UID: 6161a312-3902-11d9-b512-f764153bb28b
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&2004/11/23 14:45-15:45 another example
|
||||
Status: tentative
|
||||
Access: private
|
||||
UID: 6161a312-3902-11d9-b512-f764153bb28b
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&%%(diary-block 7 19 2004 8 27 2004) Sommerferien
|
||||
Status: tentative
|
||||
Access: private
|
||||
UID: 748f2da0-0d9b-11d8-97af-b4ec8686ea61
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&%%(diary-block 19 7 2004 27 8 2004) Sommerferien
|
||||
Status: tentative
|
||||
Access: private
|
||||
UID: 748f2da0-0d9b-11d8-97af-b4ec8686ea61
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&%%(diary-block 2004 7 19 2004 8 27) Sommerferien
|
||||
Status: tentative
|
||||
Access: private
|
||||
UID: 748f2da0-0d9b-11d8-97af-b4ec8686ea61
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&11/23/2004 14:00-14:30 folded summary
|
||||
Status: tentative
|
||||
Access: private
|
||||
UID: 04979712-3902-11d9-93dd-8f9f4afe08da
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&23/11/2004 14:00-14:30 folded summary
|
||||
Status: tentative
|
||||
Access: private
|
||||
UID: 04979712-3902-11d9-93dd-8f9f4afe08da
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&2004/11/23 14:00-14:30 folded summary
|
||||
Status: tentative
|
||||
Access: private
|
||||
UID: 04979712-3902-11d9-93dd-8f9f4afe08da
|
||||
|
|
@ -0,0 +1 @@
|
|||
&9/19/2003 long summary
|
||||
|
|
@ -0,0 +1 @@
|
|||
&19/9/2003 long summary
|
||||
|
|
@ -0,0 +1 @@
|
|||
&2003/9/19 long summary
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
&5/9/2003 10:30-15:30 On-Site Interview
|
||||
Location: Cccc
|
||||
Status: confirmed
|
||||
Organizer: Aaaaaa Aaaaa <aaaaaaa@aaaaaaa.com>
|
||||
Attendees:
|
||||
Xxxxxxxx Xxxxxxxxxxxx <xxxxxxxx@xxxxxxx.com> (needs-action)
|
||||
Yyyyyyy Yyyyy <yyyyyyy@yyyyyyy.com> (needs-action)
|
||||
Zzzz Zzzzzz <zzzzzz@zzzzzzz.com> (needs-action)
|
||||
UID: 040000008200E00074C5B7101A82E0080000000080B6DE661216C301000000000000000010000000DB823520692542408ED02D7023F9DFF9
|
||||
Description: 10:30am - Blah
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
&9/5/2003 10:30-15:30 On-Site Interview
|
||||
Location: Cccc
|
||||
Status: confirmed
|
||||
Organizer: Aaaaaa Aaaaa <aaaaaaa@aaaaaaa.com>
|
||||
Attendees:
|
||||
Xxxxxxxx Xxxxxxxxxxxx <xxxxxxxx@xxxxxxx.com> (needs-action)
|
||||
Yyyyyyy Yyyyy <yyyyyyy@yyyyyyy.com> (needs-action)
|
||||
Zzzz Zzzzzz <zzzzzz@zzzzzzz.com> (needs-action)
|
||||
UID: 040000008200E00074C5B7101A82E0080000000080B6DE661216C301000000000000000010000000DB823520692542408ED02D7023F9DFF9
|
||||
Description: 10:30am - Blah
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
&6/23/2003 11:00-12:00 Dress Rehearsal for XXXX-XXXX
|
||||
Location: 555 or TN 555-5555 ID 5555 & NochWas (see below)
|
||||
Status: confirmed
|
||||
Organizer: ABCD,TECHTRAINING(A-Americas,exgen1) <xxx@xxxxx.com>
|
||||
Attendee:
|
||||
AAAAA,AAAAA (A-AAAAAAA,ex1) <aaaaa_aaaaa@aaaaa.com> (needs-action)
|
||||
UID: 040000008200E00074C5B7101A82E00800000000608AA7DA9835C3010000000000000000100000007C3A6D65EE726E40B7F3D69A23BD567E
|
||||
Description: 753 Zeichen hier radiert
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
&23/6/2003 11:00-12:00 Dress Rehearsal for XXXX-XXXX
|
||||
Location: 555 or TN 555-5555 ID 5555 & NochWas (see below)
|
||||
Status: confirmed
|
||||
Organizer: ABCD,TECHTRAINING(A-Americas,exgen1) <xxx@xxxxx.com>
|
||||
Attendee:
|
||||
AAAAA,AAAAA (A-AAAAAAA,ex1) <aaaaa_aaaaa@aaaaa.com> (needs-action)
|
||||
UID: 040000008200E00074C5B7101A82E00800000000608AA7DA9835C3010000000000000000100000007C3A6D65EE726E40B7F3D69A23BD567E
|
||||
Description: 753 Zeichen hier radiert
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
&6/23/2003 17:00-18:00 Updated: Dress Rehearsal for ABC01-15
|
||||
Desc: Viele Zeichen standen hier früher
|
||||
Location: 123 or TN 123-1234 ID abcd & SonstWo (see below)
|
||||
Organizer: MAILTO:bbb@bbbbb.com
|
||||
Status: CONFIRMED
|
||||
UID: 040000008200E00074C5B7101A82E00800000000608AA7DA9835C3010000000000000000100000007C3A6D65EE726E40B7F3D69A23BD567E
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
&23/6/2003 09:00-10:00 Updated: Dress Rehearsal for ABC01-15
|
||||
Location: 123 or TN 123-1234 ID abcd & SonstWo (see below)
|
||||
Status: confirmed
|
||||
UID: 040000008200E00074C5B7101A82E00800000000608AA7DA9835C3010000000000000000100000007C3A6D65EE726E40B7F3D69A23BD567E
|
||||
Description: Viele Zeichen standen hier früher
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 1) (BYDAY (1))) :start
|
||||
'(11 1 2004) :duration
|
||||
'(nil nil nil 1 nil nil nil -1 nil)) Wwww aa hhhh
|
||||
Status: tentative
|
||||
Access: private
|
||||
|
||||
&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 2) (BYDAY (5))) :start
|
||||
'(0 0 14 12 11 2004 5 -1 nil) :duration
|
||||
'(0 30 4 0 nil nil nil -1 nil)) MMM Aaaaaaaaa
|
||||
Status: tentative
|
||||
Access: private
|
||||
|
||||
&%%(diary-block 11 19 2004 11 19 2004) Rrrr/Cccccc ii Aaaaaaaa
|
||||
Status: tentative
|
||||
Access: private
|
||||
Description: Vvvvv Rrrr aaa Cccccc
|
||||
|
||||
&11/23/2004 11:00-12:00 Hhhhhhhh
|
||||
Status: tentative
|
||||
Access: private
|
||||
|
||||
&11/23/2004 14:00-14:30 Jjjjj & Wwwww
|
||||
Status: tentative
|
||||
Access: private
|
||||
|
||||
&11/23/2004 14:45-15:45 BB Aaaaaaaa Bbbbb
|
||||
Status: tentative
|
||||
Access: private
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 1) (BYDAY (1))) :start
|
||||
'(11 1 2004) :duration
|
||||
'(nil nil nil 1 nil nil nil -1 nil)) Wwww aa hhhh
|
||||
Status: tentative
|
||||
Access: private
|
||||
|
||||
&%%(diary-rrule :rule '((FREQ WEEKLY) (INTERVAL 2) (BYDAY (5))) :start
|
||||
'(0 0 14 12 11 2004 5 -1 nil) :duration
|
||||
'(0 30 4 0 nil nil nil -1 nil)) MMM Aaaaaaaaa
|
||||
Status: tentative
|
||||
Access: private
|
||||
|
||||
&%%(diary-block 19 11 2004 19 11 2004) Rrrr/Cccccc ii Aaaaaaaa
|
||||
Status: tentative
|
||||
Access: private
|
||||
Description: Vvvvv Rrrr aaa Cccccc
|
||||
|
||||
&23/11/2004 11:00-12:00 Hhhhhhhh
|
||||
Status: tentative
|
||||
Access: private
|
||||
|
||||
&23/11/2004 14:00-14:30 Jjjjj & Wwwww
|
||||
Status: tentative
|
||||
Access: private
|
||||
|
||||
&23/11/2004 14:45-15:45 BB Aaaaaaaa Bbbbb
|
||||
Status: tentative
|
||||
Access: private
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
&%%(diary-block 2 6 2005 2 6 2005) Waitangi Day
|
||||
Status: confirmed
|
||||
Category: Public Holiday
|
||||
Access: private
|
||||
UID: b60d398e-1dd1-11b2-a159-cf8cb05139f4
|
||||
Description: abcdef
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
&%%(diary-block 6 2 2005 6 2 2005) Waitangi Day
|
||||
Status: confirmed
|
||||
Category: Public Holiday
|
||||
Access: private
|
||||
UID: b60d398e-1dd1-11b2-a159-cf8cb05139f4
|
||||
Description: abcdef
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
&%%(diary-block 2 17 2005 2 23 2005) Hhhhhh Aaaaa ii Aaaaaaaa
|
||||
UID: 6AFA7558-6994-11D9-8A3A-000A95A0E830-RID
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
&%%(diary-block 17 2 2005 23 2 2005) Hhhhhh Aaaaa ii Aaaaaaaa
|
||||
UID: 6AFA7558-6994-11D9-8A3A-000A95A0E830-RID
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&11/16/2014 07:00-08:00 NoDST
|
||||
Location: Everywhere
|
||||
UID: 20141116T171439Z-678877132@marudot.com
|
||||
Description: Test event from timezone without DST
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&16/11/2014 07:00-08:00 NoDST
|
||||
Location: Everywhere
|
||||
UID: 20141116T171439Z-678877132@marudot.com
|
||||
Description: Test event from timezone without DST
|
||||
|
|
@ -0,0 +1 @@
|
|||
&%%(diary-rrule :rule '((FREQ YEARLY)) :start '(8 15 2004)) Maria Himmelfahrt
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ WEEKLY) (COUNT 3) (INTERVAL 2)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule count bi-weekly 3 times
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ DAILY) (COUNT 14) (INTERVAL 1)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule count daily long
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ DAILY) (COUNT 1) (INTERVAL 1)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule count daily short
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ MONTHLY) (INTERVAL 2) (COUNT 5)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule count every second month
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ YEARLY) (INTERVAL 2) (COUNT 5)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule count every second year
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ MONTHLY) (INTERVAL 1) (COUNT 5)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule count monthly
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ YEARLY) (INTERVAL 1) (COUNT 5)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule count yearly
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ DAILY) (INTERVAL 2)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule daily
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
&%%(diary-rrule :rule '((FREQ DAILY) (INTERVAL 2)) :exclude
|
||||
'((9 21 2003) (9 25 2003)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule daily with exceptions
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
&%%(diary-rrule :rule '((FREQ DAILY)) :start '(0 0 9 19 9 2003 5 -1 nil)
|
||||
:duration '(0 30 2 0 nil nil nil -1 nil)) rrule daily
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ MONTHLY)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule monthly no end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ MONTHLY) (UNTIL (8 19 2005))) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule monthly with end
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
&%%(diary-rrule :rule '((FREQ WEEKLY)) :start '(0 0 9 19 9 2003 5 -1 nil)
|
||||
:duration '(0 30 2 0 nil nil nil -1 nil)) rrule weekly
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&%%(diary-rrule :rule '((FREQ YEARLY) (INTERVAL 2)) :start
|
||||
'(0 0 9 19 9 2003 5 -1 nil) :duration
|
||||
'(0 30 2 0 nil nil nil -1 nil)) rrule yearly
|
||||
|
|
@ -0,0 +1 @@
|
|||
&2003/9/19 9.00h-11.30h 12hr blank-padded
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&2003/9/19 09:00 Has an attachment
|
||||
Attachment: R3Jl.plain
|
||||
UID: f9fee9a0-1231-4984-9078-f1357db352db
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
&2012/1/15 15:00-15:30 standardtime
|
||||
|
||||
&2012/12/15 11:00-11:30 daylightsavingtime
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
&9/19/2003 09:00-11:30 non-recurring
|
||||
UID: 1234567890uid
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
&19/9/2003 09:00-11:30 non-recurring
|
||||
UID: 1234567890uid
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
&2003/9/19 09:00-11:30 non-recurring
|
||||
UID: 1234567890uid
|
||||
1277
test/lisp/calendar/diary-icalendar-tests.el
Normal file
1277
test/lisp/calendar/diary-icalendar-tests.el
Normal file
File diff suppressed because it is too large
Load diff
2032
test/lisp/calendar/icalendar-parser-tests.el
Normal file
2032
test/lisp/calendar/icalendar-parser-tests.el
Normal file
File diff suppressed because it is too large
Load diff
2873
test/lisp/calendar/icalendar-recur-tests.el
Normal file
2873
test/lisp/calendar/icalendar-recur-tests.el
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,16 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Testing legacy `icalendar-import-format' function
|
||||
DESCRIPTION:described
|
||||
CLASS:private
|
||||
LOCATION:somewhere
|
||||
ORGANIZER;CN="Baz Foo":mailto:baz@example.com
|
||||
STATUS:CONFIRMED
|
||||
URL:http://example.com/foo/baz
|
||||
UID:some-unique-id-here
|
||||
DTSTART;VALUE=DATE-TIME:20250919T090000
|
||||
DTEND;VALUE=DATE-TIME:20250919T113000
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:Testing legacy `icalendar-import-format*' vars
|
||||
DESCRIPTION:described
|
||||
CLASS:private
|
||||
LOCATION:somewhere
|
||||
ORGANIZER;CN="Baz Foo":mailto:baz@example.com
|
||||
STATUS:CONFIRMED
|
||||
URL:http://example.com/foo/baz
|
||||
UID:some-unique-id-here
|
||||
DTSTART;VALUE=DATE-TIME:20250919T090000
|
||||
DTEND;VALUE=DATE-TIME:20250919T113000
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:non-recurring allday
|
||||
DTSTART;VALUE=DATE-TIME:20030919
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:non-recurring allday
|
||||
DTSTART;VALUE=DATE:20030919
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
DTSTART;VALUE=DATE:20040815
|
||||
DTEND;VALUE=DATE:20040816
|
||||
SUMMARY:Maria Himmelfahrt
|
||||
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=8
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
DTSTART;VALUE=DATE:20040815
|
||||
SUMMARY:Maria Himmelfahrt
|
||||
RRULE:FREQ=YEARLY
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:rrule daily with exceptions
|
||||
DTSTART;VALUE=DATE-TIME:20030919T090000
|
||||
DTEND;VALUE=DATE-TIME:20030919T113000
|
||||
RRULE:FREQ=DAILY;INTERVAL=2
|
||||
EXDATE:20030921,20030925
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:rrule daily with exceptions
|
||||
DTSTART;VALUE=DATE-TIME:20030919T090000
|
||||
DTEND;VALUE=DATE-TIME:20030919T113000
|
||||
RRULE:FREQ=DAILY;INTERVAL=2
|
||||
EXDATE;VALUE=DATE:20030921,20030925
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:rrule daily
|
||||
DTSTART;VALUE=DATE-TIME:20030919T090000
|
||||
DTEND;VALUE=DATE-TIME:20030919T113000
|
||||
RRULE:FREQ=DAILY;
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:rrule daily
|
||||
DTSTART;VALUE=DATE-TIME:20030919T090000
|
||||
DTEND;VALUE=DATE-TIME:20030919T113000
|
||||
RRULE:FREQ=DAILY
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:rrule monthly no end
|
||||
DTSTART;VALUE=DATE-TIME:20030919T090000
|
||||
DTEND;VALUE=DATE-TIME:20030919T113000
|
||||
RRULE:FREQ=MONTHLY;
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
PRODID:-//Emacs//NONSGML icalendar.el//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VEVENT
|
||||
SUMMARY:rrule monthly no end
|
||||
DTSTART;VALUE=DATE-TIME:20030919T090000
|
||||
DTEND;VALUE=DATE-TIME:20030919T113000
|
||||
RRULE:FREQ=MONTHLY
|
||||
END:VEVENT
|
||||
END:VCALENDAR
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue