Elisp: xah-fix-datetime

By Xah Lee. Date: .

Here's a command that fix datetime under cursor into yyyy-mm-dd format.

(defun xah-fix-datetime (@begin @end)
  "Change timestamp under cursor into a yyyy-mm-dd format.
If there's a text selection, use that as input, else use current line.
Replace the text in selection or current line.

Any “day of week”, or “time” info, or any other parts of the string, are discarded.
For example:
 TUESDAY, FEB 15, 2011 05:16 ET → 2011-02-15
 November 28, 1994              → 1994-11-28
 Nov. 28, 1994                  → 1994-11-28
 11/28/1994                     → 1994-11-28
 1994/11/28                     → 1994-11-28

URL `http://ergoemacs.org/emacs/elisp_datetime_parser.html'
Version 2020-09-08"
  (interactive
   (list
    (if (region-active-p) (region-beginning))
    (if (region-active-p) (region-end))))
  (require 'parse-time)
  (let ($p1 $p2 $in)
    (if @begin
        (setq $p1 @begin $p2 @end)
      (setq $p1 (line-beginning-position) $p2 (line-end-position)))
    (setq $in (replace-regexp-in-string "^ *\\(.+\\) *$" "\\1" (buffer-substring-no-properties $p1 $p2)))
  ; remove white spaces

    (setq $in
          (cond

           ;; yyyy/mm/dd
           ((string-match "\\([0-9][0-9][0-9][0-9]\\)/\\([0-9][0-9]\\)/\\([0-9][0-9]\\)" $in)
            (concat (match-string 1 $in) "-" (match-string 2 $in) "-" (match-string 3 $in)))

           ;; mm/dd/yyyy
           ((string-match "\\([0-9][0-9]\\)/\\([0-9][0-9]\\)/\\([0-9][0-9][0-9][0-9]\\)" $in)
            (concat (match-string 3 $in) "-" (match-string 1 $in) "-" (match-string 2 $in)))
           ;; m/dd/yyyy
           ((string-match "\\([0-9]\\)/\\([0-9][0-9]\\)/\\([0-9][0-9][0-9][0-9]\\)" $in)
            (concat (match-string 3 $in) "-0" (match-string 1 $in) "-" (match-string 2 $in)))

           ;; USA convention of mm/dd/yy
           ((string-match "\\([0-9][0-9]\\)/\\([0-9][0-9]\\)/\\([0-9][0-9]\\)" $in)
            (concat (format-time-string "%C") (match-string 3 $in) "-" (match-string 1 $in) "-" (match-string 2 $in)))
           ;; USA convention of m/dd/yy
           ((string-match "\\([0-9]\\)/\\([0-9][0-9]\\)/\\([0-9][0-9]\\)" $in)
            (concat (format-time-string "%C") (match-string 3 $in) "-0" (match-string 1 $in) "-" (match-string 2 $in)))

           ;; some ISO 8601. yyyy-mm-ddThh:mm
           ((string-match "\\([0-9][0-9][0-9][0-9]\\)-\\([0-9][0-9]\\)-\\([0-9][0-9]\\)T[0-9][0-9]:[0-9][0-9]" $in)
            (concat (match-string 1 $in) "-" (match-string 2 $in) "-" (match-string 3 $in)))
           ;; some ISO 8601. yyyy-mm-dd
           ((string-match "\\([0-9][0-9][0-9][0-9]\\)-\\([0-9][0-9]\\)-\\([0-9][0-9]\\)" $in)
            (concat (match-string 1 $in) "-" (match-string 2 $in) "-" (match-string 3 $in)))
           ;; some ISO 8601. yyyy-mm
           ((string-match "\\([0-9][0-9][0-9][0-9]\\)-\\([0-9][0-9]\\)" $in)
            (concat (match-string 1 $in) "-" (match-string 2 $in)))

           ;; else
           (t
            (progn
              (setq $in (replace-regexp-in-string "January " "Jan. " $in))
              (setq $in (replace-regexp-in-string "February " "Feb. " $in))
              (setq $in (replace-regexp-in-string "March " "Mar. " $in))
              (setq $in (replace-regexp-in-string "April " "Apr. " $in))
              (setq $in (replace-regexp-in-string "May " "May. " $in))
              (setq $in (replace-regexp-in-string "June " "Jun. " $in))
              (setq $in (replace-regexp-in-string "July " "Jul. " $in))
              (setq $in (replace-regexp-in-string "August " "Aug. " $in))
              (setq $in (replace-regexp-in-string "September " "Sep. " $in))
              (setq $in (replace-regexp-in-string "October " "Oct. " $in))
              (setq $in (replace-regexp-in-string "November " "Nov. " $in))
              (setq $in (replace-regexp-in-string "December " "Dec. " $in))

              (setq $in (replace-regexp-in-string "\\([0-9]+\\)st" "\\1" $in))
              (setq $in (replace-regexp-in-string "\\([0-9]+\\)nd" "\\1" $in))
              (setq $in (replace-regexp-in-string "\\([0-9]+\\)rd" "\\1" $in))
              (setq $in (replace-regexp-in-string "\\([0-9]\\)th" "\\1" $in))

              (let ($dateList $year $month $date $yyyy $mm $dd )
                (setq $dateList (parse-time-string $in))
                (setq $year (nth 5 $dateList))
                (setq $month (nth 4 $dateList))
                (setq $date (nth 3 $dateList))

                (setq $yyyy (number-to-string $year))
                (setq $mm (if $month (format "%02d" $month) "" ))
                (setq $dd (if $date (format "%02d" $date) "" ))
                (concat $yyyy "-" $mm "-" $dd))))))
    (delete-region $p1 $p2 )
    (insert $in)))
(defun xah-fix-datetime-string (@datetime)
  "Return a new string of @datetime in yyyy-mm-dd format.
Other datetime info such as hours, minutes, time zone, are discarded. This function calls `xah-fix-datetime' to do work.

URL `http://ergoemacs.org/emacs/elisp_datetime_parser.html'
Version 2020-09-08"
  (with-temp-buffer
    (insert @datetime)
    (xah-fix-datetime (point-min) (point-max))
    (buffer-substring-no-properties (point-min) (point-max))))

The whole code is just one giant conditional test.

(cond
 (TEST2 BODY)
 (TEST2 BODY)
 …
 )

In the code, the first few tests are regex match of forms like nn/nn/nnnn where each “n” is a digit. When any of these match, then basically i got what i want, and the code exits.

When none of these match, then it goes to the end of the test (t BODY), where the “t” there is always true, and run a big chunk of BODY. In the BODY, first i replace each full spelling of month names by their abbrev using replace-regexp-in-string, example

(setq $str (replace-regexp-in-string "January " "Jan. " $str))

This is done because in emacs 22 the parse-time-string doesn't understand fully spelled month names. (this has been fixed in 23.2.1 or earlier.)

Then, i also replace {1st, 2nd, nth} etc by {1, 2, n}, because emacs's parse-time-string doesn't understand those. Then, i simply feed it to parse-time-string and get a parsed date time as a list. After that, just extract the elements from the list and reformat the way i want using format.

Datetime

If you have a question, put $5 at patreon and message me.
Or Buy Xah Emacs Tutorial
Or buy JavaScript in Depth

Emacs Tutorial

Emacs Init

Emacs Keys

ELisp

ELisp Examples

ELisp Write Major Mode


ELisp Examples

Text Transform Under Cursor

Commands Do thing-at-point

Command to Insert Things

Script Examples

Misc