The format-time-string function takes a string, and replaces a set of special constructs in that string (characters preceeded by %) with some specific text.

I'd like to implement a functionality like that in a function of my own:

  • I have an alist between chars and symbols like: '((?n . name) (?r . reputation)).
  • The function should take a string like "My name is %n, and my rep is %r".
  • It should replace %n and %r with the value of the variables name and reputation, and return the result.
  • It should handle %% just like format-time-string (replace it with %).

What's the easiest way to implement this function?
Is there a library or function that will facilitate this? Handling %% correctly is important.


The default functions to be used are format-spec and format-spec-make:

(let* ((name "Malabarba")
       (reputation "good")
       (fs (format-spec-make ?n name ?r reputation)))
  (format-spec "My name is %n, with a %r reputation.  %% is kept." fs))
  • 1
    Nice, didn't expect such a thing to exist in Emacs already. What's particularly good is that there's rudimentary error handling for invalid format codes included. – wasamasa Jan 7 '15 at 12:34

Since elisp has rather primitive facilities for dealing with strings, it is better to put your format string into a buffer and iterate over that:

(defvar malabarba-alist '((?n . "Malabarba") (?r . 7488) (?% . "%")))

(defun malabarba-format (string)
    (insert string)
    (goto-char 1)
    (while (search-forward "%" nil t)
      (let ((s (cdr (assoc (char-after) malabarba-alist))))
          (s (delete-char -1) (delete-char 1) (insert (format "%s" (eval s))))
          (t (unless (eobp) (forward-char))))))

A minor subtlety — this doesn't break when there's a lone % at the end of the string because char-after returns nil at the end of the buffer.

  • Yes, this approach, of progressing through the string once, used also by @AlanShutko, makes more sense. I've deleted my answer. – Drew Jan 7 '15 at 7:20

Here is code that I use for a format-time-string for a fake calendar:

(defun mystcal-format-time (format-string time)
  "Format time
%Y is the year.
%m is the numeric month.
%B is the full name of the month.
%d is the day of the month, zero-padded, %e is blank-padded.
%u is the numeric day of week from 1 (Monday) to 7, %w from 0 (Sunday) to 6.
%A is the locale's full name of the day of week.
%H is the hour on a 24-hour clock, %I is on a 12-hour clock, %k is like %H
 only blank-padded, %l is like %I blank-padded.
%p is the locale's equivalent of either AM or PM.
%M is the minute.
%S is the second."
  (let* (output
         (start 0)
         (decoded-time (if (listp time)
                         (mystcal-decode-time time)))
         (turn (nth 0 decoded-time))
         (minute (nth 1 decoded-time))
         (hour (nth 2 decoded-time))
         (day (nth 3 decoded-time))
         (month (mystcal-month decoded-time))
         (year (nth 5 decoded-time))
         (weekday (mystcal-weekday-of-day day))
         (hour12 (mod hour 12)))
      (while (string-match "%" format-string start)
        (let ((index (match-beginning 0)))
          ;; First copy non-format text
          (setq output (concat output (substring format-string start 
          ;; Process format codes here
          (let (fmted)
            (setq output (concat output 
                                 (case (aref format-string (1+ index))
                                   (?Y (number-to-string year))
                                   (?m (number-to-string month))
                                   (?B (mystcal-month-name month))
                                   (?d (format "%02d" day))
                                   (?e (format "%2d" day))
                                   (?u (number-to-string (if (zerop weekday)
                                   (?w (number-to-string weekday))
                                   (?A (mystcal-weekday-name 
                                        (mystcal-weekday-of-day day)))
                                   (?H (format "%02d" hour))
                                   (?k (format "%2d" hour))
                                   (?I (format "%02d" (if (zerop hour12) 12 hour12)))
                                   (?l (format "%2d" (if (zerop hour12) 12 hour12)))
                                   (?p (if (< hour 12)
                                           "AM" "PM"))
                                   (?M (format "%02d" minute))
                                   (?S "00")))))
          (setq start (+ 2 index)))))
    (setq output (concat output (substring format-string start)))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.