aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/calendar/icalendar-recur.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/calendar/icalendar-recur.el')
-rw-r--r--lisp/calendar/icalendar-recur.el157
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
2030TZ defaults to the output of `calendar-current-time-zone'; if specified,
2031it should be a list of the same form as that function returns.
2032Depending 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
2039TZID, if specified, should be a string to identify this time zone; it
2040defaults to `icalendar-recur--emacs-local-tzid' plus the name of the
2041standard observance according to `calendar-current-time-zone'.
2042
2043START-YEAR, if specified, should be an integer giving the year in which
2044to 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