2025-05-31 Converting integers to ISO-8601 timestamps

Two weeks ago I wrote about using defcustom​’s :get and :set keywords, allowing the user to set an option containing a Unix timestamp (that is, a number) using an ISO-8601 timestamp. Today I am going to use such an option.

One of the most problematic things in IT is dealing with time in various aspects. Code whose behavior is time-dependent is often hard to write, test and debug. Timezones and DST are a complete mess. The topic of this post – different representations of time — is not the most difficult problem, but it’s still annoying. While I more or less learned to parse (I mean with my eyes, not in code) the American format of date (M/D/Y), I personally use ISO-8601 (YYYY-MM-DD) even though it’s not the the one in use here in Poland (where DD.MM.YYYY is the most widespread). However, Unix timestamps (as the number of seconds since the “Unix epoch”, that is, 1970-01-01) or JavaScript Date​s (as the number of seconds since the same epoch) still defeat my skills. (Note: it is perhaps more correct to say ECMAScript than JavaScript, but come on – who actually says “ECMAScript”?) I can usually guess that an integer represents a timestamp (from the number of digits and the fact that it begins with 17 for current-ish times), I have no idea whether it’s in the past or in the future, not to mention which day (or even year, for that matter) it is.

Of course, the places I most often see timestamps in are the terminal and Emacs, and of course moving stuff from the former to the latter (or even using Emacs as a terminal) is easy to do. So, why not write some Elisp to help me convert the Unix or JS integer timestamps to a more human-friendly representation?

(defun unix-to-iso-8601 (unix-time &optional time-zone)
  "Convert Unix time to ISO-8601."
  (format-time-string "%FT%T%:z" (seconds-to-time unix-time) time-zone))

(defun jsdate-to-iso-8601 (jsdate &optional time-zone)
  "Convert Unix time to ISO-8601."
  (format-time-string "%FT%T%:z" (seconds-to-time (/ jsdate 1000.0)) time-zone))

Note that by default these functions return the local time, but setting time-zone to t will yield UTC (see the reference for other possible values of time-zone).

Let’s make this even better. How about making Emacs recognize whether a number is a Unix time or a JS Date? I will use he fact that most dates we actually see are more or less near to the current date – for example, 1747884031473 is the time I am writing this as JavaScript Date.now(), and treating it as a Unix time yields 57358-03-29T08:04:33+02 – not a date very probable to be used unless you are a Doctor Who fan. On the other hand, treating 1747884031 as JavaScript Date gives 1970-01-21T06:31:24+01:00 – very close to the Unix epoch (obviously) and supposedly mostly interesting for historians.

So, why not have some “threshold” and treat all numbers higher than it as JS dates, and the rest as Unix timestamps?

(require 'iso8601)
(defcustom maximum-unix-timestamp-for-conversion "2038-01-19T03:14:07Z"
  "Threshold for differentiating between Unix time and JS time.
Integers representing Unix times after this timestamp are treated as JS
times, that is, milliseconds from 1970-01-01."
  :type '(string
          :match (lambda (widget val)
                   (iso8601-valid-p val)))
  :set (lambda (sym val)
         (set sym (float-time (encode-time (decoded-time-set-defaults
                                            (iso8601-parse val))))))
  :get (lambda (sym)
         (format-time-string "%FT%T%:::z"
                             (seconds-to-time (symbol-value sym)))))

(It seemed natural to use 2³¹-1 as the cutoff point, but YMMV.)

Now it only remains to use the defined option.

(defun number-to-iso-8601 (number)
  "Convert NUMBER to ISO-8601.
Treat it as Unix timestamp if less than
`maximum-unix-timestamp-for-conversion' and as JavaScript Date
otherwise."
  (if (< number maximum-unix-timestamp-for-conversion)
      (cons 'unix (unix-to-iso-8601 number))
    (cons 'javascript (jsdate-to-iso-8601 number))))

(defun show-number-at-point-as-timestamp ()
  "Echo the number at point as an ISO-8601 timestamp."
  (interactive)
  (when-let* ((number-at-point (thing-at-point 'number))
              (timestamp (number-to-iso-8601 number-at-point)))
    (message "%s: %s" (car timestamp) (cdr timestamp))))

From now on, I can invoke this command when the point is on a number and see the correct timestamp in the ISO-8601 format for both Unix timestamps and JavaScript Date​s.

CategoryEnglish, CategoryBlog, CategoryEmacs