diff options
Diffstat (limited to 'lisp/calendar/diary-icalendar.el')
| -rw-r--r-- | lisp/calendar/diary-icalendar.el | 168 |
1 files changed, 21 insertions, 147 deletions
diff --git a/lisp/calendar/diary-icalendar.el b/lisp/calendar/diary-icalendar.el index 640e063268a..eed53bfd700 100644 --- a/lisp/calendar/diary-icalendar.el +++ b/lisp/calendar/diary-icalendar.el | |||
| @@ -673,32 +673,6 @@ recurring events for several years beyond the start time." | |||
| 673 | :version "31.1" | 673 | :version "31.1" |
| 674 | :type 'integer) | 674 | :type 'integer) |
| 675 | 675 | ||
| 676 | (defun di:-tz-info-sexp-p (_ sexp) | ||
| 677 | "Validate that SEXP gives time zone info like from `calendar-current-time-zone'." | ||
| 678 | (and (listp sexp) | ||
| 679 | (length= sexp 8) | ||
| 680 | (let ((utc-diff (nth 0 sexp)) | ||
| 681 | (dst-offset (nth 1 sexp)) | ||
| 682 | (std-zone (nth 2 sexp)) | ||
| 683 | (dst-zone (nth 3 sexp)) | ||
| 684 | (dst-starts (nth 4 sexp)) | ||
| 685 | (dst-ends (nth 5 sexp)) | ||
| 686 | (dst-starts-time (nth 6 sexp)) | ||
| 687 | (dst-ends-time (nth 7 sexp))) | ||
| 688 | (and | ||
| 689 | (integerp utc-diff) (< (abs utc-diff) (* 60 24)) | ||
| 690 | (integerp dst-offset) (< (abs utc-diff) (* 60 24)) | ||
| 691 | (stringp std-zone) | ||
| 692 | (stringp dst-zone) | ||
| 693 | (or (and (listp dst-starts) (memq 'year (flatten-list dst-starts))) | ||
| 694 | (and (null dst-starts) (equal std-zone dst-zone))) | ||
| 695 | (or (and (listp dst-ends) (memq 'year (flatten-list dst-ends))) | ||
| 696 | (and (null dst-ends) (equal std-zone dst-zone))) | ||
| 697 | (or (and (integerp dst-starts-time) (< (abs dst-starts-time) (* 60 24))) | ||
| 698 | (null dst-starts-time)) | ||
| 699 | (or (and (integerp dst-ends-time) (< (abs dst-ends-time) (* 60 24))) | ||
| 700 | (null dst-ends-time)))))) | ||
| 701 | |||
| 702 | (defcustom di:time-zone-export-strategy | 676 | (defcustom di:time-zone-export-strategy |
| 703 | 'local | 677 | 'local |
| 704 | "Strategy to use for exporting clock times in diary files. | 678 | "Strategy to use for exporting clock times in diary files. |
| @@ -741,7 +715,7 @@ the events you are exporting." | |||
| 741 | (const :tag "Convert local times to UTC" to-utc) | 715 | (const :tag "Convert local times to UTC" to-utc) |
| 742 | (const :tag "Use floating times" floating) | 716 | (const :tag "Use floating times" floating) |
| 743 | (sexp :tag "User-provided TZ information" | 717 | (sexp :tag "User-provided TZ information" |
| 744 | :match di:-tz-info-sexp-p | 718 | :match icr:-tz-info-sexp-p |
| 745 | :type-error | 719 | :type-error |
| 746 | "See `calendar-current-time-zone' for format")) | 720 | "See `calendar-current-time-zone' for format")) |
| 747 | :link '(url-link "https://www.rfc-editor.org/rfc/rfc5545#section-3.3.5")) | 721 | :link '(url-link "https://www.rfc-editor.org/rfc/rfc5545#section-3.3.5")) |
| @@ -989,12 +963,6 @@ new code." | |||
| 989 | 963 | ||
| 990 | ;;; Other utilities | 964 | ;;; Other utilities |
| 991 | 965 | ||
| 992 | (defsubst di:-nonempty (s) | ||
| 993 | "Ensure that string S is nonempty once trimmed: return the trimmed S, or nil." | ||
| 994 | (when (and s (stringp s)) | ||
| 995 | (let ((trimmed (string-trim s))) | ||
| 996 | (unless (equal "" trimmed) trimmed)))) | ||
| 997 | |||
| 998 | (defconst di:entry-regexp | 966 | (defconst di:entry-regexp |
| 999 | (rx line-start | 967 | (rx line-start |
| 1000 | (group-n 1 ; first line of entry | 968 | (group-n 1 ; first line of entry |
| @@ -1660,10 +1628,10 @@ Returns a string containing the diary entry." | |||
| 1660 | (if (eq 'icalendar-vjournal component-type) | 1628 | (if (eq 'icalendar-vjournal component-type) |
| 1661 | (mapconcat | 1629 | (mapconcat |
| 1662 | (lambda (node) | 1630 | (lambda (node) |
| 1663 | (di:-nonempty (ical:text-to-string (ical:ast-node-value node)))) | 1631 | (ical:trimp (ical:text-to-string (ical:ast-node-value node)))) |
| 1664 | description-nodes | 1632 | description-nodes |
| 1665 | "\n\n") | 1633 | "\n\n") |
| 1666 | (di:-nonempty description))) | 1634 | (ical:trimp description))) |
| 1667 | (ical-start | 1635 | (ical-start |
| 1668 | (when dtstart | 1636 | (when dtstart |
| 1669 | (if (bound-and-true-p ical-importing) | 1637 | (if (bound-and-true-p ical-importing) |
| @@ -1748,7 +1716,7 @@ Returns a string containing the diary entry." | |||
| 1748 | (when (and dtstart due-dt (bound-and-true-p ical-importing)) | 1716 | (when (and dtstart due-dt (bound-and-true-p ical-importing)) |
| 1749 | (di:format-time-block-sexp dtstart-local due-dt))) | 1717 | (di:format-time-block-sexp dtstart-local due-dt))) |
| 1750 | (ical-importing (bound-and-true-p ical-importing)) | 1718 | (ical-importing (bound-and-true-p ical-importing)) |
| 1751 | (ical-location (or (di:-nonempty location) | 1719 | (ical-location (or (ical:trimp location) |
| 1752 | (when geo (di:format-geo-coordinates geo)))) | 1720 | (when geo (di:format-geo-coordinates geo)))) |
| 1753 | (ical-nonmarking nonmarking) | 1721 | (ical-nonmarking nonmarking) |
| 1754 | (ical-organizer (di:format-attendee organizer-node)) | 1722 | (ical-organizer (di:format-attendee organizer-node)) |
| @@ -1757,11 +1725,11 @@ Returns a string containing the diary entry." | |||
| 1757 | (ical-rrule-sexp | 1725 | (ical-rrule-sexp |
| 1758 | (when (and is-recurring (bound-and-true-p ical-importing)) | 1726 | (when (and is-recurring (bound-and-true-p ical-importing)) |
| 1759 | (di:format-rrule-sexp component))) | 1727 | (di:format-rrule-sexp component))) |
| 1760 | (ical-status (when status (di:-nonempty (downcase status)))) | 1728 | (ical-status (when status (ical:trimp (downcase status)))) |
| 1761 | (ical-summary (di:-nonempty summary)) | 1729 | (ical-summary (ical:trimp summary)) |
| 1762 | (ical-transparency transp) | 1730 | (ical-transparency transp) |
| 1763 | (ical-uid (di:-nonempty uid)) | 1731 | (ical-uid (ical:trimp uid)) |
| 1764 | (ical-url (di:-nonempty url))) | 1732 | (ical-url (ical:trimp url))) |
| 1765 | (with-temp-buffer | 1733 | (with-temp-buffer |
| 1766 | (cl-case (ical:ast-node-type component) | 1734 | (cl-case (ical:ast-node-type component) |
| 1767 | (ical:vevent | 1735 | (ical:vevent |
| @@ -2108,7 +2076,7 @@ parsed as an `icalendar-organizer' node, or otherwise as an | |||
| 2108 | (unless (string-match ":" addr) ; URI scheme already present | 2076 | (unless (string-match ":" addr) ; URI scheme already present |
| 2109 | (setq addr (concat "mailto:" addr))) | 2077 | (setq addr (concat "mailto:" addr))) |
| 2110 | (when cn | 2078 | (when cn |
| 2111 | (setq cn (di:-nonempty cn))) | 2079 | (setq cn (ical:trimp cn))) |
| 2112 | (if (string-match di:organizer-regexp | 2080 | (if (string-match di:organizer-regexp |
| 2113 | (buffer-substring (line-beginning-position) | 2081 | (buffer-substring (line-beginning-position) |
| 2114 | (line-end-position))) | 2082 | (line-end-position))) |
| @@ -2130,7 +2098,7 @@ this node, or nil." | |||
| 2130 | (goto-char (point-min)) | 2098 | (goto-char (point-min)) |
| 2131 | (when (and di:location-regexp | 2099 | (when (and di:location-regexp |
| 2132 | (re-search-forward di:location-regexp nil t)) | 2100 | (re-search-forward di:location-regexp nil t)) |
| 2133 | (ical:make-property ical:location (di:-nonempty (match-string 1))))) | 2101 | (ical:make-property ical:location (ical:trimp (match-string 1))))) |
| 2134 | 2102 | ||
| 2135 | (defun di:parse-class () | 2103 | (defun di:parse-class () |
| 2136 | "Parse `icalendar-class' node from entry. | 2104 | "Parse `icalendar-class' node from entry. |
| @@ -2166,7 +2134,7 @@ Searches the entry in the current restriction for an URL matching | |||
| 2166 | (goto-char (point-min)) | 2134 | (goto-char (point-min)) |
| 2167 | (when (and di:url-regexp | 2135 | (when (and di:url-regexp |
| 2168 | (re-search-forward di:url-regexp nil t)) | 2136 | (re-search-forward di:url-regexp nil t)) |
| 2169 | (ical:make-property ical:url (di:-nonempty (match-string 1))))) | 2137 | (ical:make-property ical:url (ical:trimp (match-string 1))))) |
| 2170 | 2138 | ||
| 2171 | (defun di:parse-uid () | 2139 | (defun di:parse-uid () |
| 2172 | "Parse `icalendar-uid' node from entry. | 2140 | "Parse `icalendar-uid' node from entry. |
| @@ -2177,7 +2145,7 @@ Searches the entry in the current restriction for a UID matching | |||
| 2177 | (goto-char (point-min)) | 2145 | (goto-char (point-min)) |
| 2178 | (when (and di:uid-regexp | 2146 | (when (and di:uid-regexp |
| 2179 | (re-search-forward di:uid-regexp nil t)) | 2147 | (re-search-forward di:uid-regexp nil t)) |
| 2180 | (ical:make-property ical:uid (di:-nonempty (match-string 1))))) | 2148 | (ical:make-property ical:uid (ical:trimp (match-string 1))))) |
| 2181 | 2149 | ||
| 2182 | (defun di:parse-summary-and-description () | 2150 | (defun di:parse-summary-and-description () |
| 2183 | "Parse summary and description nodes from current restriction. | 2151 | "Parse summary and description nodes from current restriction. |
| @@ -3136,112 +3104,18 @@ times according to `diary-icalendar-time-zone-export-strategy'." | |||
| 3136 | 3104 | ||
| 3137 | ;;; Time zone handling during export: | 3105 | ;;; Time zone handling during export: |
| 3138 | 3106 | ||
| 3139 | (defconst di:-tz-warning | ||
| 3140 | "This time zone information was inferred from incomplete system information; it should be correct for the date-times within this calendar file referencing this zone, but you should not rely on it more widely.") | ||
| 3141 | |||
| 3142 | (defconst di:-emacs-local-tzid | ||
| 3143 | "Emacs_Local_") | ||
| 3144 | |||
| 3145 | (defun di:current-tz-to-vtimezone (&optional tz tzid start-year) | 3107 | (defun di:current-tz-to-vtimezone (&optional tz tzid start-year) |
| 3146 | "Convert TZ to an `icalendar-vtimezone'. | 3108 | "Convert TZ to an `icalendar-vtimezone'. |
| 3147 | 3109 | ||
| 3148 | TZ defaults to the output of `calendar-current-time-zone'; if specified, | 3110 | See `icalendar-recur-current-tz-to-vtimezone' for arguments' meanings. |
| 3149 | it should be a list of the same form as that function returns. | 3111 | This function wraps that one, but signals `icalendar-diary-export-error' |
| 3150 | 3112 | instead if TZ cannot be converted." | |
| 3151 | TZID, if specified, should be a string to identify this time zone; it | 3113 | (condition-case err |
| 3152 | defaults to `diary-icalendar--emacs-local-tzid' plus the name of the | 3114 | (icr:current-tz-to-vtimezone tz tzid start-year) |
| 3153 | standard observance according to `calendar-current-time-zone'. | 3115 | ((ical:tz-insufficient-data ical:tz-unsupported) |
| 3154 | 3116 | (di:signal-export-error | |
| 3155 | START-YEAR, if specified, should be an integer giving the year in which | 3117 | (format "Unable to export time zone data: %s.\n%s." tz |
| 3156 | to start the observances in the time zone. It defaults to 1970." | 3118 | "Check the value of `diary-icalendar-time-zone-export-strategy'"))))) |
| 3157 | (when (and tz (not (di:-tz-info-sexp-p nil tz))) | ||
| 3158 | (di:signal-export-error | ||
| 3159 | (format "Invalid time zone data: %s.\n%s." tz | ||
| 3160 | "Check the value of `diary-icalendar-time-zone-export-strategy'"))) | ||
| 3161 | (let* ((tzdata (or tz (calendar-current-time-zone))) | ||
| 3162 | (std-offset (* 60 (nth 0 tzdata))) | ||
| 3163 | (dst-offset (+ std-offset | ||
| 3164 | (* 60 (nth 1 tzdata)))) | ||
| 3165 | (std-name (nth 2 tzdata)) | ||
| 3166 | (dst-name (nth 3 tzdata)) | ||
| 3167 | (dst-starts (nth 4 tzdata)) | ||
| 3168 | (dst-ends (nth 5 tzdata)) | ||
| 3169 | (dst-start-minutes (nth 6 tzdata)) | ||
| 3170 | (dst-end-minutes (nth 7 tzdata))) | ||
| 3171 | |||
| 3172 | (unless (and std-offset | ||
| 3173 | (or (equal std-name dst-name) | ||
| 3174 | (and dst-starts dst-ends dst-start-minutes dst-end-minutes))) | ||
| 3175 | (di:signal-export-error | ||
| 3176 | "Insufficient time zone information to create VTIMEZONE")) | ||
| 3177 | |||
| 3178 | (if (equal std-name dst-name) | ||
| 3179 | ;; Local time zone doesn't use DST: | ||
| 3180 | (ical:make-vtimezone | ||
| 3181 | (ical:tzid (or tzid (concat di:-emacs-local-tzid std-name))) | ||
| 3182 | (ical:make-standard | ||
| 3183 | (ical:tzname std-name) | ||
| 3184 | (ical:dtstart (ical:make-date-time :year (or start-year 1970) | ||
| 3185 | :month 1 :day 1 | ||
| 3186 | :hour 0 :minute 0 :second 0)) | ||
| 3187 | (ical:tzoffsetfrom std-offset) | ||
| 3188 | (ical:tzoffsetto std-offset) | ||
| 3189 | (ical:comment di:-tz-warning))) | ||
| 3190 | |||
| 3191 | ;; Otherwise we can provide both STANDARD and DAYLIGHT subcomponents: | ||
| 3192 | (let* ((std->dst-rule | ||
| 3193 | (if (eq (car dst-starts) 'calendar-nth-named-day) | ||
| 3194 | `((FREQ YEARLY) | ||
| 3195 | (BYMONTH (,(nth 3 dst-starts))) | ||
| 3196 | (BYDAY (,(cons (nth 2 dst-starts) | ||
| 3197 | (nth 1 dst-starts))))) | ||
| 3198 | ;; The only other rules that `calendar-current-time-zone' | ||
| 3199 | ;; can return are based on the Persian calendar, which we | ||
| 3200 | ;; cannot express in an `icalendar-recur' value, at least | ||
| 3201 | ;; pending an implementation of RFC 7529 | ||
| 3202 | (di:signal-export-error | ||
| 3203 | (format "Unable to export DST rule for current time zone: %s" | ||
| 3204 | dst-starts)))) | ||
| 3205 | (dst-start-date (calendar-dlet ((year (or start-year 1970))) | ||
| 3206 | (eval dst-starts))) | ||
| 3207 | (dst-start | ||
| 3208 | (ical:date-to-date-time dst-start-date | ||
| 3209 | :hour (/ dst-start-minutes 60) | ||
| 3210 | :minute (mod dst-start-minutes 60) | ||
| 3211 | :second 0)) | ||
| 3212 | (dst->std-rule | ||
| 3213 | (if (eq (car dst-ends) 'calendar-nth-named-day) | ||
| 3214 | `((FREQ YEARLY) | ||
| 3215 | (BYMONTH (,(nth 3 dst-ends))) | ||
| 3216 | (BYDAY (,(cons (nth 2 dst-ends) | ||
| 3217 | (nth 1 dst-ends))))) | ||
| 3218 | (di:signal-export-error | ||
| 3219 | (format "Unable to export DST rule for current time zone: %s" | ||
| 3220 | dst-ends)))) | ||
| 3221 | (std-start-date (calendar-dlet ((year (1- (or start-year 1970)))) | ||
| 3222 | (eval dst-ends))) | ||
| 3223 | (std-start | ||
| 3224 | (ical:date-to-date-time std-start-date | ||
| 3225 | :hour (/ dst-end-minutes 60) | ||
| 3226 | :minute (mod dst-end-minutes 60) | ||
| 3227 | :second 0))) | ||
| 3228 | |||
| 3229 | (ical:make-vtimezone | ||
| 3230 | (ical:tzid (or tzid (concat di:-emacs-local-tzid std-name))) | ||
| 3231 | (ical:make-standard | ||
| 3232 | (ical:tzname std-name) | ||
| 3233 | (ical:dtstart std-start) | ||
| 3234 | (ical:rrule dst->std-rule) | ||
| 3235 | (ical:tzoffsetfrom dst-offset) | ||
| 3236 | (ical:tzoffsetto std-offset) | ||
| 3237 | (ical:comment di:-tz-warning)) | ||
| 3238 | (ical:make-daylight | ||
| 3239 | (ical:tzname dst-name) | ||
| 3240 | (ical:dtstart dst-start) | ||
| 3241 | (ical:rrule std->dst-rule) | ||
| 3242 | (ical:tzoffsetfrom std-offset) | ||
| 3243 | (ical:tzoffsetto dst-offset) | ||
| 3244 | (ical:comment di:-tz-warning))))))) | ||
| 3245 | 3119 | ||
| 3246 | ;;; Parsing complete diary entries: | 3120 | ;;; Parsing complete diary entries: |
| 3247 | 3121 | ||