Custom org-id format

Problem: Make org-id more human readable while ensuring uniqueness.

Persistent daily counter – highjacking 'ts format.

;; org-id-counter - definition and infrastructure for org-id format %date-%counter

(defvar org-id-counter (make-hash-table :size 1 :test 'equal)
  "Hash table to store the counter for each day.")

(defvar org-id-counter-location (locate-user-emacs-file "org-id-counter"))

(setq org-id-method 'ts)

(defun org-id-counter-format ()
  "Generate an Org ID using the format %Y/%m/%d-%counter."
  (let* ((today (format-time-string "%Y/%m/%d"))
         (counter (or (gethash today org-id-counter) 1))
         (id (format "%s:%s" today (number-to-string counter))))
    (puthash today (1+ counter) org-id-counter)

;; iteration logic
(defun org-id-counter-advice (&rest args)
  "Advice function to set `org-id-ts-format` before calling `org-id-new`."
  (setq org-id-ts-format (funcall #'org-id-counter-format)))

(advice-add 'org-id-new :before #'org-id-counter-advice)

;; Persistent counter for the day
(defun org-id-counter-save ()
  "Save org-id-counter hash table to disk."
  (with-temp-file org-id-counter-location
    (prin1 org-id-counter (current-buffer))))

(add-hook 'kill-emacs-hook #'org-id-counter-save) ;; Save the hash table on Emacs exit

(defun org-id-counter-load ()
  "Load org-id-counter hash table from disk."
  (when (file-exists-p org-id-counter-location)
    (if (= 0 (nth 7 (file-attributes org-id-counter-location))) ; Check if file is empty
        (setq org-id-counter (make-hash-table :test 'equal))    ; Initialize with empty hash table
        (insert-file-contents org-id-counter-location)
        (let ((data (read (current-buffer))))
          (when (hash-table-p data)
            (setq org-id-counter data)))))))

(org-id-counter-load) ;; Load the hash table on Emacs start

;; debug
(defun org-id-counter-show ()
  "Display the contents of org-id-counter hash table."
  (message "Contents of org-id-counter hash table:")
  (maphash (lambda (key value)
             (message "%s: %s" key value))

(defun org-id-counter-reset ()
    "Reset the counter on request.
Caution: make sure you make appropriate provisions to not have conflicting IDs"
(setq org-id-counter (make-hash-table :test 'equal)))

(provide 'org-id-counter)


Lightly tested - will update on finding bugs and the like.

1 Like

FWIW, I am happy with the normal timestamp format, via

  ;; Use an ISO8601 timestamp for IDs
  (org-id-method 'ts)

Which shows up as 20240430T181635.273283

Ofcourse its a very simple yet effective way to ensure uniqueness - although I have trouble reading them at a glance and also keeping manual records of them.

1 Like

I use a variation of what @alanz uses. I do not like to use the global variable to keep track of the counter for the day (feels a bit too complex for what we want).

The time stamp format is now customizable with Org 9.5 = Emacs 28.

I use this format: "%Y-%m-%dT%H%M%S":
=> 2024-05-09T122208.

The part Thhmmss makes the ID unique to the second (enough for me) and still readable for me. It’s useful as a time stamp as well.

Other variations without global counter may be like these.
For what it’s worth…

;; ISO 8601 format
(let ((org-id-method 'ts)
      (org-id-ts-format "%FT%T"))

=> 2024-05-09T12:19:30

;; Uniquify without global variable
(let ((org-id-method 'ts)
      (org-id-ts-format "%F:"))
  (concat (org-id-new) (substring (org-id-uuid) 0 8)))

=> 2024-05-09:a24dd299

You can use a forward slash to divide year, month, and day, of course:

(let ((org-id-method 'ts)
      (org-id-ts-format "%Y/%m/%d:"))
  (concat (org-id-new) (substring (org-id-uuid) 0 8)))

=> 2024/05/09:fdb00bd5


I agree with both you and @alanz in that I think its too overcomplicated for trying to do something simple - I was working in a different context, but I ended by creating this out of whatever was left. I treat this as a more proof of concept - rather than something we should use.

– this has some big limitations - insofar as much, syncing between different computers becomes a forgone task without making the thing more complex –

I had given up on this and I simply have learnt to live life with the full 'ts format – the absoluteness of time is in itself a global variable.

Perhaps this could be an example in context for future readers in how to handle variables :- and save and load them from disk – nothing much more should be used out of it – for it imposes handicaps that are too expensive in nature.