Function: icalendar-rrule-validator

icalendar-rrule-validator is a byte-compiled function defined in icalendar-parser.el.gz.

Signature

(icalendar-rrule-validator NODE)

Documentation

Validate that NODE has the properties required by a recurrence rule.

NODE should represent an iCalendar component. When NODE has an icalendar-rrule property, this function validates that its icalendar-dtstart, icalendar-rdate, and icalendar-exdate properties satisfy the requirements imposed by this rule.

This function is called by the additional validator functions for component nodes (e.g. icalendar-vevent-validator); it is not normally necessary to call it directly.

Source Code

;; Defined in /usr/src/emacs/lisp/calendar/icalendar-parser.el.gz
(defun ical:rrule-validator (node)
  "Validate that NODE has the properties required by a recurrence rule.

NODE should represent an iCalendar component.  When NODE has an
`icalendar-rrule' property, this function validates that its
`icalendar-dtstart', `icalendar-rdate', and `icalendar-exdate'
properties satisfy the requirements imposed by this rule.

This function is called by the additional validator functions for
component nodes (e.g. `icalendar-vevent-validator'); it is not normally
necessary to call it directly."
  (let* ((rrule (ical:ast-node-first-child-of 'ical:rrule node))
         (recval (when rrule (ical:ast-node-value rrule)))
         (dtstart (ical:ast-node-first-child-of 'ical:dtstart node))
         (start (when dtstart (ical:ast-node-value dtstart)))
         (rdates (ical:ast-node-children-of 'ical:rdate node))
         (included (when rdates
                     (mapcar #'ical:ast-node-value
                             (apply #'append
                                    (mapcar #'ical:ast-node-value rdates))))))
    (when rrule
      (unless dtstart
        (ical:signal-validation-error
         "An `icalendar-rrule' requires an `icalendar-dtstart' property"
         :node node))
      (when included
        ;; ""RDATE" in this usage [i.e., in STANDARD and DAYLIGHT
        ;; subcomponents] MUST be specified as a date with local time
        ;; value, relative to the UTC offset specified in the
        ;; "TZOFFSETFROM" property."
        (when (and (memq (ical:ast-node-type node) '(ical:standard ical:daylight)))
          (unless (ical:list-of-p included 'ical:date-time)
            (ical:signal-validation-error
             (format
              (concat "`icalendar-rdate' values must be `icalendar-date-time' "
                      "values in %s components")
              (ical:ast-node-type node))
             :node node))
          (when (seq-some #'decoded-time-zone included)
            (ical:signal-validation-error
             (format
              (concat "`icalendar-rdate' values must be in local (\"floating\")"
                      "time in %s components")
              (ical:ast-node-type node))
             :node node))))

      (let* ((freq (car (alist-get 'FREQ recval)))
             (until (car (alist-get 'UNTIL recval))))
        (when (eq 'ical:date (ical:ast-node-type start))
          (when (or (memq freq '(HOURLY MINUTELY SECONDLY))
                    (assq 'BYSECOND recval)
                    (assq 'BYMINUTE recval)
                    (assq 'BYHOUR recval))
            (ical:signal-validation-error
             (concat "`icalendar-rrule' must not contain time-based "
                     "rules when `icalendar-dtstart' is a plain date")
             :node node)))
        (when until
          (unless (eq (ical:ast-node-type start)
                      (ical:ast-node-type until))
            (ical:signal-validation-error
             (concat "`icalendar-rrule' UNTIL clause must agree with "
                     "type of `icalendar-dtstart' property")
             :node node))
          (when (eq 'ical:date-time (ical:ast-node-type until))
            (let ((until-zone
                   (decoded-time-zone (ical:ast-node-value until)))
                  (start-zone
                   (decoded-time-zone (ical:ast-node-value start))))
              ;; "If the "DTSTART" property is specified as a date
              ;; with local time, then the UNTIL rule part MUST also
              ;; be specified as a date with local time":
              (when (and (null start-zone) (not (null until-zone)))
                (ical:signal-validation-error
                  (concat "`icalendar-rrule' UNTIL clause must be in "
                          "local time if `icalendar-dtstart' is")
                  :node node))
              ;; "If the "DTSTART" property is specified as a date
              ;; with UTC time or a date with local time and time zone
              ;; reference, then the UNTIL rule part MUST be specified
              ;; as a date with UTC time":
              (when (and (integerp start-zone)
                         (not (ical:date-time-is-utc-p until)))
                (ical:signal-validation-error
                  (concat "`icalendar-rrule' UNTIL clause must be in UTC time "
                          "if `icalendar-dtstart' has a defined time zone")
                  :node node))))
          (when (memq (ical:ast-node-type node) '(ical:standard ical:daylight))
            ;; "In the case of the "STANDARD" and "DAYLIGHT"
            ;; sub-components the UNTIL rule part MUST always be
            ;; specified as a date with UTC time":
            (unless (ical:date-time-is-utc-p until)
              (ical:signal-validation-error
               (concat "`icalendar-rrule' UNTIL clause must be in UTC time in "
                       "`icalendar-standard' and `icalendar-daylight' components")
               :node node))))

        ;; "DTSTART in this usage [i.e., in STANDARD and DAYLIGHT
        ;; subcomponents] MUST be specified as a date with a local
        ;; time value."
        (when (memq (ical:ast-node-type node) '(ical:standard ical:daylight))
          (unless (eq 'ical:date-time (ical:ast-node-type start))
            (ical:signal-validation-error
              (concat "`icalendar-dtstart' must be an `icalendar-date-time' in "
                      "`icalendar-standard' and `icalendar-daylight' components")
              :node node))

          (when (decoded-time-zone (ical:ast-node-value start))
            (ical:signal-validation-error
             (concat "`icalendar-dtstart' must be in local (\"floating\") time in "
                     "`icalendar-standard' and `icalendar-daylight' components")
             :node node)))))

    ;; Success:
    node))