aboutsummaryrefslogtreecommitdiffstats
path: root/elpa/compat-28.1.1.0/compat-27.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/compat-28.1.1.0/compat-27.el')
-rw-r--r--elpa/compat-28.1.1.0/compat-27.el642
1 files changed, 642 insertions, 0 deletions
diff --git a/elpa/compat-28.1.1.0/compat-27.el b/elpa/compat-28.1.1.0/compat-27.el
new file mode 100644
index 0000000..b74450f
--- /dev/null
+++ b/elpa/compat-28.1.1.0/compat-27.el
@@ -0,0 +1,642 @@
+;;; compat-27.el --- Compatibility Layer for Emacs 27.1 -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021, 2022 Free Software Foundation, Inc.
+
+;; Author: Philip Kaludercic <philipk@posteo.net>
+;; Maintainer: Compat Development <~pkal/compat-devel@lists.sr.ht>
+;; URL: https://git.sr.ht/~pkal/compat/
+;; Keywords: lisp
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Find here the functionality added in Emacs 27.1, needed by older
+;; versions.
+;;
+;; Do NOT load this library manually. Instead require `compat'.
+
+;;; Code:
+
+(eval-when-compile (require 'compat-macs))
+
+;;;; Defined in fns.c
+
+(compat-defun proper-list-p (object)
+ "Return OBJECT's length if it is a proper list, nil otherwise.
+A proper list is neither circular nor dotted (i.e., its last cdr
+is nil)."
+ :min-version "26.1"
+ :max-version "26.3"
+ :realname compat--proper-list-p-length-signal
+ (condition-case nil
+ (and (listp object) (length object))
+ (wrong-type-argument nil)
+ (circular-list nil)))
+
+(compat-defun proper-list-p (object)
+ "Return OBJECT's length if it is a proper list, nil otherwise.
+A proper list is neither circular nor dotted (i.e., its last cdr
+is nil)."
+ :max-version "25.3"
+ :realname compat--proper-list-p-tortoise-hare
+ (when (listp object)
+ (catch 'cycle
+ (let ((hare object) (tortoise object)
+ (max 2) (q 2))
+ (while (consp hare)
+ (setq hare (cdr hare))
+ (when (and (or (/= 0 (setq q (1- q)))
+ (ignore
+ (setq max (ash max 1)
+ q max
+ tortoise hare)))
+ (eq hare tortoise))
+ (throw 'cycle nil)))
+ (and (null hare) (length object))))))
+
+(compat-defun string-distance (string1 string2 &optional bytecompare)
+ "Return Levenshtein distance between STRING1 and STRING2.
+The distance is the number of deletions, insertions, and substitutions
+required to transform STRING1 into STRING2.
+If BYTECOMPARE is nil or omitted, compute distance in terms of characters.
+If BYTECOMPARE is non-nil, compute distance in terms of bytes.
+Letter-case is significant, but text properties are ignored."
+ ;; https://en.wikipedia.org/wiki/Levenshtein_distance
+ (let ((s1 (if bytecompare
+ (encode-coding-string string1 'raw-text)
+ (concat string1 "")))
+ (s2 (if bytecompare
+ (encode-coding-string string2 'raw-text)
+ string2)))
+ (let* ((len1 (length s1))
+ (len2 (length s2))
+ (column (make-vector (1+ len1) 0)))
+ (dotimes (y len1)
+ (setf (aref column (1+ y)) y))
+ (dotimes (x len2)
+ (setf (aref column 0) (1+ x))
+ (let ((lastdiag x) olddiag)
+ (dotimes (y len1)
+ (setf olddiag (aref column (1+ y))
+ (aref column (1+ y))
+ (min (+ (if (= (aref s1 y) (aref s2 x)) 0 1)
+ lastdiag)
+ (1+ (aref column (1+ y)))
+ (1+ (aref column y)))
+ lastdiag olddiag))))
+ (aref column len1))))
+
+;;;; Defined in window.c
+
+(compat-defun recenter (&optional arg redisplay)
+ "Handle optional argument REDISPLAY."
+ :prefix t
+ (recenter arg)
+ (when (and redisplay recenter-redisplay)
+ (redisplay)))
+
+;;;; Defined in keymap.c
+
+(compat-defun lookup-key (keymap key &optional accept-default)
+ "Allow for KEYMAP to be a list of keymaps."
+ :prefix t
+ (cond
+ ((keymapp keymap)
+ (lookup-key keymap key accept-default))
+ ((listp keymap)
+ (catch 'found
+ (dolist (map keymap)
+ (let ((fn (lookup-key map key accept-default)))
+ (when fn (throw 'found fn))))))
+ ((signal 'wrong-type-argument (list 'keymapp keymap)))))
+
+;;;; Defined in json.c
+
+(declare-function json-parse-string nil (string &rest args))
+(declare-function json-encode-string "json" (object))
+(declare-function json-read-from-string "json" (string))
+(declare-function json-read "json" ())
+(defvar json-object-type)
+(defvar json-array-type)
+(defvar json-false)
+(defvar json-null)
+
+(compat-defun json-serialize (object &rest args)
+ "Return the JSON representation of OBJECT as a string.
+
+OBJECT must be t, a number, string, vector, hashtable, alist, plist,
+or the Lisp equivalents to the JSON null and false values, and its
+elements must recursively consist of the same kinds of values. t will
+be converted to the JSON true value. Vectors will be converted to
+JSON arrays, whereas hashtables, alists and plists are converted to
+JSON objects. Hashtable keys must be strings without embedded null
+characters and must be unique within each object. Alist and plist
+keys must be symbols; if a key is duplicate, the first instance is
+used.
+
+The Lisp equivalents to the JSON null and false values are
+configurable in the arguments ARGS, a list of keyword/argument pairs:
+
+The keyword argument `:null-object' specifies which object to use
+to represent a JSON null value. It defaults to `:null'.
+
+The keyword argument `:false-object' specifies which object to use to
+represent a JSON false value. It defaults to `:false'.
+
+In you specify the same value for `:null-object' and `:false-object',
+a potentially ambiguous situation, the JSON output will not contain
+any JSON false values."
+ :cond (condition-case nil
+ (let ((inhibit-message t))
+ (equal (json-parse-string "[]") nil))
+ (json-unavailable t)
+ (void-function t))
+ :realname compat--json-serialize
+ (require 'json)
+ (let ((json-false (or (plist-get args :false-object) :false))
+ (json-null (or (plist-get args :null-object) :null)))
+ (json-encode-string object)))
+
+(compat-defun json-insert (object &rest args)
+ "Insert the JSON representation of OBJECT before point.
+This is the same as (insert (json-serialize OBJECT)), but potentially
+faster. See the function `json-serialize' for allowed values of
+OBJECT."
+ :cond (condition-case nil
+ (let ((inhibit-message t))
+ (equal (json-parse-string "[]") nil))
+ (json-unavailable t)
+ (void-function t))
+ (insert (apply #'compat--json-serialize object args)))
+
+(compat-defun json-parse-string (string &rest args)
+ "Parse the JSON STRING into a Lisp object.
+This is essentially the reverse operation of `json-serialize', which
+see. The returned object will be the JSON null value, the JSON false
+value, t, a number, a string, a vector, a list, a hashtable, an alist,
+or a plist. Its elements will be further objects of these types. If
+there are duplicate keys in an object, all but the last one are
+ignored. If STRING doesn't contain a valid JSON object, this function
+signals an error of type `json-parse-error'.
+
+The arguments ARGS are a list of keyword/argument pairs:
+
+The keyword argument `:object-type' specifies which Lisp type is used
+to represent objects; it can be `hash-table', `alist' or `plist'. It
+defaults to `hash-table'.
+
+The keyword argument `:array-type' specifies which Lisp type is used
+to represent arrays; it can be `array' (the default) or `list'.
+
+The keyword argument `:null-object' specifies which object to use
+to represent a JSON null value. It defaults to `:null'.
+
+The keyword argument `:false-object' specifies which object to use to
+represent a JSON false value. It defaults to `:false'."
+ :cond (condition-case nil
+ (let ((inhibit-message t))
+ (equal (json-parse-string "[]") nil))
+ (json-unavailable t)
+ (void-function t))
+ (require 'json)
+ (condition-case err
+ (let ((json-object-type (or (plist-get args :object-type) 'hash-table))
+ (json-array-type (or (plist-get args :array-type) 'vector))
+ (json-false (or (plist-get args :false-object) :false))
+ (json-null (or (plist-get args :null-object) :null)))
+ (when (eq json-array-type 'array)
+ (setq json-array-type 'vector))
+ (json-read-from-string string))
+ (json-error (signal 'json-parse-error err))))
+
+(compat-defun json-parse-buffer (&rest args)
+ "Read JSON object from current buffer starting at point.
+Move point after the end of the object if parsing was successful.
+On error, don't move point.
+
+The returned object will be a vector, list, hashtable, alist, or
+plist. Its elements will be the JSON null value, the JSON false
+value, t, numbers, strings, or further vectors, lists, hashtables,
+alists, or plists. If there are duplicate keys in an object, all
+but the last one are ignored.
+
+If the current buffer doesn't contain a valid JSON object, the
+function signals an error of type `json-parse-error'.
+
+The arguments ARGS are a list of keyword/argument pairs:
+
+The keyword argument `:object-type' specifies which Lisp type is used
+to represent objects; it can be `hash-table', `alist' or `plist'. It
+defaults to `hash-table'.
+
+The keyword argument `:array-type' specifies which Lisp type is used
+to represent arrays; it can be `array' (the default) or `list'.
+
+The keyword argument `:null-object' specifies which object to use
+to represent a JSON null value. It defaults to `:null'.
+
+The keyword argument `:false-object' specifies which object to use to
+represent a JSON false value. It defaults to `:false'."
+ :cond (condition-case nil
+ (let ((inhibit-message t))
+ (equal (json-parse-string "[]") nil))
+ (json-unavailable t)
+ (void-function t))
+ (require 'json)
+ (condition-case err
+ (let ((json-object-type (or (plist-get args :object-type) 'hash-table))
+ (json-array-type (or (plist-get args :array-type) 'vector))
+ (json-false (or (plist-get args :false-object) :false))
+ (json-null (or (plist-get args :null-object) :null)))
+ (when (eq json-array-type 'array)
+ (setq json-array-type 'vector))
+ (json-read))
+ (json-error (signal 'json-parse-buffer err))))
+
+;;;; Defined in timefns.c
+
+(compat-defun time-equal-p (t1 t2)
+ "Return non-nil if time value T1 is equal to time value T2.
+A nil value for either argument stands for the current time."
+ :note "This function is not as accurate as the actual `time-equal-p'."
+ (cond
+ ((eq t1 t2))
+ ((and (consp t1) (consp t2))
+ (equal t1 t2))
+ ((let ((now (current-time)))
+ ;; Due to inaccuracies and the relatively slow evaluating of
+ ;; Emacs Lisp compared to C, we allow for slight inaccuracies
+ ;; (less than a millisecond) when comparing time values.
+ (< (abs (- (float-time (or t1 now))
+ (float-time (or t2 now))))
+ 1e-5)))))
+
+;;;; Defined in subr.el
+
+(compat-defmacro setq-local (&rest pairs)
+ "Handle multiple assignments."
+ :prefix t
+ (unless (zerop (mod (length pairs) 2))
+ (error "PAIRS must have an even number of variable/value members"))
+ (let (body)
+ (while pairs
+ (let* ((sym (pop pairs))
+ (val (pop pairs)))
+ (unless (symbolp sym)
+ (error "Attempting to set a non-symbol: %s" (car pairs)))
+ (push `(set (make-local-variable ,sym) ,val)
+ body)))
+ (cons 'progn (nreverse body))))
+
+;;* UNTESTED
+(compat-defmacro ignore-error (condition &rest body)
+ "Execute BODY; if the error CONDITION occurs, return nil.
+Otherwise, return result of last form in BODY.
+
+CONDITION can also be a list of error conditions."
+ (declare (debug t) (indent 1))
+ `(condition-case nil (progn ,@body) (,condition nil)))
+
+;;* UNTESTED
+(compat-defmacro dolist-with-progress-reporter (spec reporter-or-message &rest body)
+ "Loop over a list and report progress in the echo area.
+Evaluate BODY with VAR bound to each car from LIST, in turn.
+Then evaluate RESULT to get return value, default nil.
+
+REPORTER-OR-MESSAGE is a progress reporter object or a string. In the latter
+case, use this string to create a progress reporter.
+
+At each iteration, print the reporter message followed by progress
+percentage in the echo area. After the loop is finished,
+print the reporter message followed by the word \"done\".
+
+\(fn (VAR LIST [RESULT]) REPORTER-OR-MESSAGE BODY...)"
+ (declare (indent 2) (debug ((symbolp form &optional form) form body)))
+ (let ((prep (make-symbol "--dolist-progress-reporter--"))
+ (count (make-symbol "--dolist-count--"))
+ (list (make-symbol "--dolist-list--")))
+ `(let ((,prep ,reporter-or-message)
+ (,count 0)
+ (,list ,(cadr spec)))
+ (when (stringp ,prep)
+ (setq ,prep (make-progress-reporter ,prep 0 (1- (length ,list)))))
+ (dolist (,(car spec) ,list)
+ ,@body
+ (progress-reporter-update ,prep (setq ,count (1+ ,count))))
+ (progress-reporter-done ,prep)
+ (or ,@(cdr (cdr spec)) nil))))
+
+(compat-defun flatten-tree (tree)
+ "Return a \"flattened\" copy of TREE.
+In other words, return a list of the non-nil terminal nodes, or
+leaves, of the tree of cons cells rooted at TREE. Leaves in the
+returned list are in the same order as in TREE.
+
+\(flatten-tree \\='(1 (2 . 3) nil (4 5 (6)) 7))
+=> (1 2 3 4 5 6 7)"
+ (let (elems)
+ (while (consp tree)
+ (let ((elem (pop tree)))
+ (while (consp elem)
+ (push (cdr elem) tree)
+ (setq elem (car elem)))
+ (if elem (push elem elems))))
+ (if tree (push tree elems))
+ (nreverse elems)))
+
+(compat-defun xor (cond1 cond2)
+ "Return the boolean exclusive-or of COND1 and COND2.
+If only one of the arguments is non-nil, return it; otherwise
+return nil."
+ (declare (pure t) (side-effect-free error-free))
+ (cond ((not cond1) cond2)
+ ((not cond2) cond1)))
+
+(compat-defvar regexp-unmatchable "\\`a\\`"
+ "Standard regexp guaranteed not to match any string at all."
+ :constant t)
+
+(compat-defun assoc-delete-all (key alist &optional test)
+ "Delete from ALIST all elements whose car is KEY.
+Compare keys with TEST. Defaults to `equal'.
+Return the modified alist.
+Elements of ALIST that are not conses are ignored."
+ :prefix t
+ (unless test (setq test #'equal))
+ (while (and (consp (car alist))
+ (funcall test (caar alist) key))
+ (setq alist (cdr alist)))
+ (let ((tail alist) tail-cdr)
+ (while (setq tail-cdr (cdr tail))
+ (if (and (consp (car tail-cdr))
+ (funcall test (caar tail-cdr) key))
+ (setcdr tail (cdr tail-cdr))
+ (setq tail tail-cdr))))
+ alist)
+
+;;;; Defined in simple.el
+
+;;* UNTESTED
+(compat-defun decoded-time-second (time)
+ "The seconds in TIME, which is a value returned by `decode-time'.
+This is an integer between 0 and 60 (inclusive). (60 is a leap
+second, which only some operating systems support.)"
+ (nth 0 time))
+
+;;* UNTESTED
+(compat-defun decoded-time-minute (time)
+ "The minutes in TIME, which is a value returned by `decode-time'.
+This is an integer between 0 and 59 (inclusive)."
+ (nth 1 time))
+
+;;* UNTESTED
+(compat-defun decoded-time-hour (time)
+ "The hours in TIME, which is a value returned by `decode-time'.
+This is an integer between 0 and 23 (inclusive)."
+ (nth 2 time))
+
+;;* UNTESTED
+(compat-defun decoded-time-day (time)
+ "The day-of-the-month in TIME, which is a value returned by `decode-time'.
+This is an integer between 1 and 31 (inclusive)."
+ (nth 3 time))
+
+;;* UNTESTED
+(compat-defun decoded-time-month (time)
+ "The month in TIME, which is a value returned by `decode-time'.
+This is an integer between 1 and 12 (inclusive). January is 1."
+ (nth 4 time))
+
+;;* UNTESTED
+(compat-defun decoded-time-year (time)
+ "The year in TIME, which is a value returned by `decode-time'.
+This is a four digit integer."
+ (nth 5 time))
+
+;;* UNTESTED
+(compat-defun decoded-time-weekday (time)
+ "The day-of-the-week in TIME, which is a value returned by `decode-time'.
+This is a number between 0 and 6, and 0 is Sunday."
+ (nth 6 time))
+
+;;* UNTESTED
+(compat-defun decoded-time-dst (time)
+ "The daylight saving time in TIME, which is a value returned by `decode-time'.
+This is t if daylight saving time is in effect, and nil if not."
+ (nth 7 time))
+
+;;* UNTESTED
+(compat-defun decoded-time-zone (time)
+ "The time zone in TIME, which is a value returned by `decode-time'.
+This is an integer indicating the UTC offset in seconds, i.e.,
+the number of seconds east of Greenwich."
+ (nth 8 time))
+
+;; TODO define gv-setters
+
+;;;; Defined in files.el
+
+(compat-defun file-size-human-readable (file-size &optional flavor space unit)
+ "Handle the optional third and forth argument:
+
+Optional third argument SPACE is a string put between the number and unit.
+It defaults to the empty string. We recommend a single space or
+non-breaking space, unless other constraints prohibit a space in that
+position.
+
+Optional fourth argument UNIT is the unit to use. It defaults to \"B\"
+when FLAVOR is `iec' and the empty string otherwise. We recommend \"B\"
+in all cases, since that is the standard symbol for byte."
+ :prefix t
+ (let ((power (if (or (null flavor) (eq flavor 'iec))
+ 1024.0
+ 1000.0))
+ (prefixes '("" "k" "M" "G" "T" "P" "E" "Z" "Y")))
+ (while (and (>= file-size power) (cdr prefixes))
+ (setq file-size (/ file-size power)
+ prefixes (cdr prefixes)))
+ (let* ((prefix (car prefixes))
+ (prefixed-unit (if (eq flavor 'iec)
+ (concat
+ (if (string= prefix "k") "K" prefix)
+ (if (string= prefix "") "" "i")
+ (or unit "B"))
+ (concat prefix unit))))
+ (format (if (and (>= (mod file-size 1.0) 0.05)
+ (< (mod file-size 1.0) 0.95))
+ "%.1f%s%s"
+ "%.0f%s%s")
+ file-size
+ (if (string= prefixed-unit "") "" (or space ""))
+ prefixed-unit))))
+
+(declare-function compat--file-name-quote "compat-26"
+ (name &optional top))
+
+;;*UNTESTED
+(compat-defun exec-path ()
+ "Return list of directories to search programs to run in remote subprocesses.
+The remote host is identified by `default-directory'. For remote
+hosts that do not support subprocesses, this returns nil.
+If `default-directory' is a local directory, this function returns
+the value of the variable `exec-path'."
+ :realname compat--exec-path
+ (cond
+ ((let ((handler (find-file-name-handler default-directory 'exec-path)))
+ ;; FIXME: The handler was added in 27.1, and this compatibility
+ ;; function only applies to versions of Emacs before that.
+ (when handler
+ (condition-case nil
+ (funcall handler 'exec-path)
+ (error nil)))))
+ ((file-remote-p default-directory)
+ ;; TODO: This is not completely portable, even if "sh" and
+ ;; "getconf" should be provided on every POSIX system, the chance
+ ;; of this not working are greater than zero.
+ ;;
+ ;; FIXME: This invokes a shell process every time exec-path is
+ ;; called. It should instead be cached on a host-local basis.
+ (with-temp-buffer
+ (if (condition-case nil
+ (zerop (process-file "sh" nil t nil "-c" "getconf PATH"))
+ (file-missing t))
+ (list "/bin" "/usr/bin")
+ (let (path)
+ (while (re-search-forward "\\([^:]+?\\)[\n:]" nil t)
+ (push (match-string 1) path))
+ (nreverse path)))))
+ (exec-path)))
+
+(declare-function compat--file-local-name "compat-26"
+ (file))
+
+;;*UNTESTED
+(compat-defun executable-find (command &optional remote)
+ "Search for COMMAND in `exec-path' and return the absolute file name.
+Return nil if COMMAND is not found anywhere in `exec-path'. If
+REMOTE is non-nil, search on the remote host indicated by
+`default-directory' instead."
+ :prefix t
+ (if (and remote (file-remote-p default-directory))
+ (let ((res (locate-file
+ command
+ (mapcar
+ (apply-partially
+ #'concat (file-remote-p default-directory))
+ (compat--exec-path))
+ exec-suffixes 'file-executable-p)))
+ (when (stringp res) (compat--file-local-name res)))
+ (executable-find command)))
+
+;; TODO provide advice for directory-files-recursively
+
+;;;; Defined in format-spec.el
+
+;; TODO provide advice for format-spec
+
+;;;; Defined in regexp-opt.el
+
+(compat-defun regexp-opt (strings &optional paren)
+ "Handle an empty list of strings."
+ :prefix t
+ (if (null strings)
+ (let ((re "\\`a\\`"))
+ (cond ((null paren)
+ (concat "\\(?:" re "\\)"))
+ ((stringp paren)
+ (concat paren re "\\)"))
+ ((eq paren 'words)
+ (concat "\\<\\(" re "\\)\\>"))
+ ((eq paren 'symbols)
+ (concat "\\_\\(<" re "\\)\\_>"))
+ ((concat "\\(" re "\\)"))))
+ (regexp-opt strings paren)))
+
+;;;; Defined in package.el
+
+(declare-function lm-header "lisp-mnt")
+
+;;* UNTESTED
+(compat-defun package-get-version ()
+ "Return the version number of the package in which this is used.
+Assumes it is used from an Elisp file placed inside the top-level directory
+of an installed ELPA package.
+The return value is a string (or nil in case we can’t find it)."
+ ;; In a sense, this is a lie, but it does just what we want: precompute
+ ;; the version at compile time and hardcodes it into the .elc file!
+ (declare (pure t))
+ ;; Hack alert!
+ (let ((file
+ (or (and (boundp 'byte-compile-current-file) byte-compile-current-file)
+ load-file-name
+ buffer-file-name)))
+ (cond
+ ((null file) nil)
+ ;; Packages are normally installed into directories named "<pkg>-<vers>",
+ ;; so get the version number from there.
+ ((string-match
+ "/[^/]+-\\([0-9]\\(?:[0-9.]\\|pre\\|beta\\|alpha\\|snapshot\\)+\\)/[^/]+\\'"
+ file)
+ (match-string 1 file))
+ ;; For packages run straight from the an elpa.git clone, there's no
+ ;; "-<vers>" in the directory name, so we have to fetch the version
+ ;; the hard way.
+ ((let* ((pkgdir (file-name-directory file))
+ (pkgname (file-name-nondirectory (directory-file-name pkgdir)))
+ (mainfile (expand-file-name (concat pkgname ".el") pkgdir)))
+ (when (file-readable-p mainfile)
+ (require 'lisp-mnt)
+ (with-temp-buffer
+ (insert-file-contents mainfile)
+ (or (lm-header "package-version")
+ (lm-header "version")))))))))
+
+
+;;;; Defined in dired.el
+
+(declare-function
+ dired-get-marked-files "dired.el"
+ (&optional localp arg filter distinguish-one-marked error))
+
+;;* UNTESTED
+(compat-defun dired-get-marked-files
+ (&optional localp arg filter distinguish-one-marked error)
+ "Return the marked files’ names as list of strings."
+ :feature 'dired
+ :prefix t
+ (let ((result (dired-get-marked-files localp arg filter distinguish-one-marked)))
+ (if (and (null result) error)
+ (user-error (if (stringp error) error "No files specified"))
+ result)))
+
+;;;; Defined in time-date.el
+
+(compat-defun date-days-in-month (year month)
+ "The number of days in MONTH in YEAR."
+ :feature 'time-date
+ (unless (and (numberp month)
+ (<= 1 month)
+ (<= month 12))
+ (error "Month %s is invalid" month))
+ (if (= month 2)
+ (if (date-leap-year-p year)
+ 29
+ 28)
+ (if (memq month '(1 3 5 7 8 10 12))
+ 31
+ 30)))
+
+(provide 'compat-27)
+;;; compat-27.el ends here