aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/calendar/diary-icalendar.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/calendar/diary-icalendar.el')
-rw-r--r--lisp/calendar/diary-icalendar.el168
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
3148TZ defaults to the output of `calendar-current-time-zone'; if specified, 3110See `icalendar-recur-current-tz-to-vtimezone' for arguments' meanings.
3149it should be a list of the same form as that function returns. 3111This function wraps that one, but signals `icalendar-diary-export-error'
3150 3112instead if TZ cannot be converted."
3151TZID, if specified, should be a string to identify this time zone; it 3113 (condition-case err
3152defaults to `diary-icalendar--emacs-local-tzid' plus the name of the 3114 (icr:current-tz-to-vtimezone tz tzid start-year)
3153standard observance according to `calendar-current-time-zone'. 3115 ((ical:tz-insufficient-data ical:tz-unsupported)
3154 3116 (di:signal-export-error
3155START-YEAR, if specified, should be an integer giving the year in which 3117 (format "Unable to export time zone data: %s.\n%s." tz
3156to 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