diff options
Diffstat (limited to 'lisp/calendar/icalendar-recur.el')
| -rw-r--r-- | lisp/calendar/icalendar-recur.el | 157 |
1 files changed, 156 insertions, 1 deletions
diff --git a/lisp/calendar/icalendar-recur.el b/lisp/calendar/icalendar-recur.el index 2f9045f278e..e3bee0923a9 100644 --- a/lisp/calendar/icalendar-recur.el +++ b/lisp/calendar/icalendar-recur.el | |||
| @@ -76,6 +76,7 @@ | |||
| 76 | (require 'icalendar-utils) | 76 | (require 'icalendar-utils) |
| 77 | (require 'cl-lib) | 77 | (require 'cl-lib) |
| 78 | (require 'calendar) | 78 | (require 'calendar) |
| 79 | (require 'cal-dst) | ||
| 79 | (require 'simple) | 80 | (require 'simple) |
| 80 | (require 'seq) | 81 | (require 'seq) |
| 81 | (eval-when-compile '(require 'icalendar-macs)) | 82 | (eval-when-compile '(require 'icalendar-macs)) |
| @@ -1478,6 +1479,14 @@ UTC offsets local to that time zone." | |||
| 1478 | (define-error 'ical:tz-no-observance "No observance found for date-time" | 1479 | (define-error 'ical:tz-no-observance "No observance found for date-time" |
| 1479 | 'ical:error) | 1480 | 'ical:error) |
| 1480 | 1481 | ||
| 1482 | (define-error 'ical:tz-data-insufficient | ||
| 1483 | "Insufficient time zone data to create VTIMEZONE" | ||
| 1484 | 'ical:error) | ||
| 1485 | |||
| 1486 | (define-error 'ical:tz-unsupported | ||
| 1487 | "Time zone rules not expressible as iCalendar RRULE" | ||
| 1488 | 'ical:error) | ||
| 1489 | |||
| 1481 | ;; In RFC5545 Section 3.3.10, we read: "If the computed local start time | 1490 | ;; In RFC5545 Section 3.3.10, we read: "If the computed local start time |
| 1482 | ;; of a recurrence instance does not exist ... the time of the | 1491 | ;; of a recurrence instance does not exist ... the time of the |
| 1483 | ;; recurrence instance is interpreted in the same manner as an explicit | 1492 | ;; recurrence instance is interpreted in the same manner as an explicit |
| @@ -1983,7 +1992,153 @@ observance." | |||
| 1983 | (observance (car obs/onset))) | 1992 | (observance (car obs/onset))) |
| 1984 | (ical:with-property-of observance 'ical:tzname))) | 1993 | (ical:with-property-of observance 'ical:tzname))) |
| 1985 | 1994 | ||
| 1986 | 1995 | (defconst icr:-tz-warning | |
| 1996 | "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.") | ||
| 1997 | |||
| 1998 | (defconst icr:-emacs-local-tzid | ||
| 1999 | "Emacs_Local_") | ||
| 2000 | |||
| 2001 | (defun icr:-tz-info-sexp-p (_ sexp) | ||
| 2002 | "Validate that SEXP gives time zone info like from `calendar-current-time-zone'." | ||
| 2003 | (and (listp sexp) | ||
| 2004 | (length= sexp 8) | ||
| 2005 | (let ((utc-diff (nth 0 sexp)) | ||
| 2006 | (dst-offset (nth 1 sexp)) | ||
| 2007 | (std-zone (nth 2 sexp)) | ||
| 2008 | (dst-zone (nth 3 sexp)) | ||
| 2009 | (dst-starts (nth 4 sexp)) | ||
| 2010 | (dst-ends (nth 5 sexp)) | ||
| 2011 | (dst-starts-time (nth 6 sexp)) | ||
| 2012 | (dst-ends-time (nth 7 sexp))) | ||
| 2013 | (and | ||
| 2014 | (integerp utc-diff) (< (abs utc-diff) (* 60 24)) | ||
| 2015 | (integerp dst-offset) (< (abs utc-diff) (* 60 24)) | ||
| 2016 | (stringp std-zone) | ||
| 2017 | (stringp dst-zone) | ||
| 2018 | (or (and (listp dst-starts) (memq 'year (flatten-list dst-starts))) | ||
| 2019 | (and (null dst-starts) (equal std-zone dst-zone))) | ||
| 2020 | (or (and (listp dst-ends) (memq 'year (flatten-list dst-ends))) | ||
| 2021 | (and (null dst-ends) (equal std-zone dst-zone))) | ||
| 2022 | (or (and (integerp dst-starts-time) (< (abs dst-starts-time) (* 60 24))) | ||
| 2023 | (null dst-starts-time)) | ||
| 2024 | (or (and (integerp dst-ends-time) (< (abs dst-ends-time) (* 60 24))) | ||
| 2025 | (null dst-ends-time)))))) | ||
| 2026 | |||
| 2027 | (defun icr:current-tz-to-vtimezone (&optional tz tzid start-year) | ||
| 2028 | "Convert TZ to an `icalendar-vtimezone'. | ||
| 2029 | |||
| 2030 | TZ defaults to the output of `calendar-current-time-zone'; if specified, | ||
| 2031 | it should be a list of the same form as that function returns. | ||
| 2032 | Depending on TZ, this function might signal the following errors: | ||
| 2033 | |||
| 2034 | `icalendar-tz-data-insufficient' if the data in TZ is not complete | ||
| 2035 | enough to determine time zone rules. | ||
| 2036 | `icalendar-tz-unsupported' if the data in TZ cannot be expressed as an | ||
| 2037 | RFC5545 `icalendar-rrule' property. | ||
| 2038 | |||
| 2039 | TZID, if specified, should be a string to identify this time zone; it | ||
| 2040 | defaults to `icalendar-recur--emacs-local-tzid' plus the name of the | ||
| 2041 | standard observance according to `calendar-current-time-zone'. | ||
| 2042 | |||
| 2043 | START-YEAR, if specified, should be an integer giving the year in which | ||
| 2044 | to start the observances in the time zone. It defaults to 1970." | ||
| 2045 | (when (and tz (not (icr:-tz-info-sexp-p nil tz))) | ||
| 2046 | (signal 'ical:tz-data-insufficient | ||
| 2047 | (list :tz tz | ||
| 2048 | :level 2 | ||
| 2049 | :message | ||
| 2050 | "Badly formed TZ data; see `calendar-current-time-zone'"))) | ||
| 2051 | (let* ((tzdata (or tz (calendar-current-time-zone))) | ||
| 2052 | (std-offset (* 60 (nth 0 tzdata))) | ||
| 2053 | (dst-offset (+ std-offset | ||
| 2054 | (* 60 (nth 1 tzdata)))) | ||
| 2055 | (std-name (nth 2 tzdata)) | ||
| 2056 | (dst-name (nth 3 tzdata)) | ||
| 2057 | (dst-starts (nth 4 tzdata)) | ||
| 2058 | (dst-ends (nth 5 tzdata)) | ||
| 2059 | (dst-start-minutes (nth 6 tzdata)) | ||
| 2060 | (dst-end-minutes (nth 7 tzdata))) | ||
| 2061 | |||
| 2062 | (unless (and std-offset | ||
| 2063 | (or (equal std-name dst-name) | ||
| 2064 | (and dst-starts dst-ends dst-start-minutes dst-end-minutes))) | ||
| 2065 | (signal 'ical:tz-data-insufficient | ||
| 2066 | (list :tz tz :level 2 | ||
| 2067 | :message "Unable to create VTIMEZONE from TZ"))) | ||
| 2068 | |||
| 2069 | (if (equal std-name dst-name) | ||
| 2070 | ;; Local time zone doesn't use DST: | ||
| 2071 | (ical:make-vtimezone | ||
| 2072 | (ical:tzid (or tzid (concat icr:-emacs-local-tzid std-name))) | ||
| 2073 | (ical:make-standard | ||
| 2074 | (ical:tzname std-name) | ||
| 2075 | (ical:dtstart (ical:make-date-time :year (or start-year 1970) | ||
| 2076 | :month 1 :day 1 | ||
| 2077 | :hour 0 :minute 0 :second 0)) | ||
| 2078 | (ical:tzoffsetfrom std-offset) | ||
| 2079 | (ical:tzoffsetto std-offset) | ||
| 2080 | (ical:comment icr:-tz-warning))) | ||
| 2081 | |||
| 2082 | ;; Otherwise we can provide both STANDARD and DAYLIGHT subcomponents: | ||
| 2083 | (let* ((std->dst-rule | ||
| 2084 | (if (eq (car dst-starts) 'calendar-nth-named-day) | ||
| 2085 | `((FREQ YEARLY) | ||
| 2086 | (BYMONTH (,(nth 3 dst-starts))) | ||
| 2087 | (BYDAY (,(cons (nth 2 dst-starts) | ||
| 2088 | (nth 1 dst-starts))))) | ||
| 2089 | ;; The only other rules that `calendar-current-time-zone' | ||
| 2090 | ;; can return are based on the Persian calendar, which we | ||
| 2091 | ;; cannot express in an `icalendar-recur' value, at least | ||
| 2092 | ;; pending an implementation of RFC 7529 | ||
| 2093 | (signal 'ical:tz-unsupported | ||
| 2094 | (list :tz tz | ||
| 2095 | :level 2 | ||
| 2096 | :message | ||
| 2097 | (format "Unable to export DST rule for time zone: %s" | ||
| 2098 | dst-starts))))) | ||
| 2099 | (dst-start-date (calendar-dlet ((year (or start-year 1970))) | ||
| 2100 | (eval dst-starts))) | ||
| 2101 | (dst-start | ||
| 2102 | (ical:date-to-date-time dst-start-date | ||
| 2103 | :hour (/ dst-start-minutes 60) | ||
| 2104 | :minute (mod dst-start-minutes 60) | ||
| 2105 | :second 0)) | ||
| 2106 | (dst->std-rule | ||
| 2107 | (if (eq (car dst-ends) 'calendar-nth-named-day) | ||
| 2108 | `((FREQ YEARLY) | ||
| 2109 | (BYMONTH (,(nth 3 dst-ends))) | ||
| 2110 | (BYDAY (,(cons (nth 2 dst-ends) | ||
| 2111 | (nth 1 dst-ends))))) | ||
| 2112 | (signal 'ical:tz-unsupported | ||
| 2113 | (list :tz tz | ||
| 2114 | :level 2 | ||
| 2115 | :message | ||
| 2116 | (format "Unable to export DST rule for time zone: %s" | ||
| 2117 | dst-ends))))) | ||
| 2118 | (std-start-date (calendar-dlet ((year (1- (or start-year 1970)))) | ||
| 2119 | (eval dst-ends))) | ||
| 2120 | (std-start | ||
| 2121 | (ical:date-to-date-time std-start-date | ||
| 2122 | :hour (/ dst-end-minutes 60) | ||
| 2123 | :minute (mod dst-end-minutes 60) | ||
| 2124 | :second 0))) | ||
| 2125 | |||
| 2126 | (ical:make-vtimezone | ||
| 2127 | (ical:tzid (or tzid (concat icr:-emacs-local-tzid std-name))) | ||
| 2128 | (ical:make-standard | ||
| 2129 | (ical:tzname std-name) | ||
| 2130 | (ical:dtstart std-start) | ||
| 2131 | (ical:rrule dst->std-rule) | ||
| 2132 | (ical:tzoffsetfrom dst-offset) | ||
| 2133 | (ical:tzoffsetto std-offset) | ||
| 2134 | (ical:comment icr:-tz-warning)) | ||
| 2135 | (ical:make-daylight | ||
| 2136 | (ical:tzname dst-name) | ||
| 2137 | (ical:dtstart dst-start) | ||
| 2138 | (ical:rrule std->dst-rule) | ||
| 2139 | (ical:tzoffsetfrom std-offset) | ||
| 2140 | (ical:tzoffsetto dst-offset) | ||
| 2141 | (ical:comment icr:-tz-warning))))))) | ||
| 1987 | 2142 | ||
| 1988 | (provide 'icalendar-recur) | 2143 | (provide 'icalendar-recur) |
| 1989 | 2144 | ||