35

I read the org-mode manual but couldn't find an easy way to add a CREATED field to newly created TODOs. In combination with org-log-done one could then compute the time it took to close a particular TODO. This is especially useful when using archive files.

Example:

* TODO Do something
  CREATED:  [2012-09-02 Sun 23:02]
* DONE Do something else
  CREATED: [2012-09-02 Sun 20:02]
  CLOSED: [2012-09-02 Sun 22:02]

I would expect the CREATED field to be added to new tasks (tasks which don't have that field) whenever the file is saved.

Any suggestions on how to achieve this? Using something like Git is not a solution for me to track the creations of TODOS.

Renke Grunwald
  • 855
  • 1
  • 9
  • 15
  • 2
    Are you intending to add the tasks using a Capture template? Or manually inserting them? If you're using a Capture template you can include it through the template. Adding it on save would be slightly trickier. – Jonathan Leech-Pepin Sep 04 '12 at 16:09
  • 1
    I usually manually insert tasks and I don't actually intend to change that, because I want to keep the feeling of just editing a text file. – Renke Grunwald Sep 04 '12 at 17:21

8 Answers8

19

I use org-expiry to implement that functionality, which is in the contrib directory of org.

The base configuration I use is:

;; Allow automatically handing of created/expired meta data.
(require 'org-expiry)
;; Configure it a bit to my liking
(setq
  org-expiry-created-property-name "CREATED" ; Name of property when an item is created
  org-expiry-inactive-timestamps   t         ; Don't have everything in the agenda view
)

(defun mrb/insert-created-timestamp()
  "Insert a CREATED property using org-expiry.el for TODO entries"
  (org-expiry-insert-created)
  (org-back-to-heading)
  (org-end-of-line)
  (insert " ")
)

;; Whenever a TODO entry is created, I want a timestamp
;; Advice org-insert-todo-heading to insert a created timestamp using org-expiry
(defadvice org-insert-todo-heading (after mrb/created-timestamp-advice activate)
  "Insert a CREATED property using org-expiry.el for TODO entries"
  (mrb/insert-created-timestamp)
)
;; Make it active
(ad-activate 'org-insert-todo-heading)

If you are using capture it does not automatically work and needs a little glue. I have posted the complete config here: https://gist.github.com/4037694

Wilfred Hughes
  • 29,846
  • 15
  • 139
  • 192
mrb
  • 306
  • 2
  • 4
  • 5
    Thank you. I think this is a good solution. However, I only put this into my init file `(require 'org-expiry) (org-expiry-insinuate) (setq org-expiry-inactive-timestamps t)` – Renke Grunwald Dec 04 '12 at 22:30
  • Also, the good thing about this is that it adds the CREATED property whenever you change the tags of a todo item; since I do this with every todo item anyway I never have an item with a missing CREATED property. Very nice. – Renke Grunwald Dec 08 '12 at 12:14
  • I added your code to my config but it doesn't seem to have any effect on the org-todos. – azureai Mar 16 '21 at 22:22
9

You don't need to modify functions with 'defadvice' to run expiry code on capture. You should use hook:

(add-hook 'org-capture-before-finalize-hook 
          (lambda()
               (save-excursion
                    (org-back-to-heading)
                    (org-expiry-insert-created))))

Same for 'org-insert-todo-heading'. There is a hook:

(add-hook 'org-insert-todo-heading-hook 
          (lambda()
               (save-excursion
                    (org-back-to-heading)
                    (org-expiry-insert-created))))
Clément
  • 12,299
  • 15
  • 75
  • 115
  • 1
    This is a good answer, but putting a lambda in a hook is a bad idea; especially when you reuse it twice :) – Clément Jul 15 '16 at 12:51
  • Is calling (org-back-to-heading) necessary? – Moyamo Sep 28 '17 at 20:22
  • @Moyamo Doesn't seem to be, and neither does `(save-excursion)`. `org-expiry-insert-created` seems to do both things on its own, so one can use it directly for the hook, without the lambda. – JoL Aug 13 '20 at 06:20
  • Great answer. This also works for normal heading creation and not just a TODO heading. – xji Sep 19 '20 at 19:22
  • 1
    What do I need to do for this to work? I use doom emacs and included the code in my config.el, then did doom sync, but nothing happened. org-todos still behave the same, no creation timestamp is added. – azureai Mar 16 '21 at 22:26
9

A more lightweight solution would be to add ! flag to the TODO state:

(setq org-todo-keywords '((sequence "TODO(!)" "DONE")))

Then:

* TODO get of your ass
  - State "TODO"    from    [2016-06-03 to. 10:35]

It isn't very pretty though.

Ref: http://orgmode.org/org.html#Tracking-TODO-state-changes

olejorgenb
  • 1,203
  • 14
  • 25
6

Org provides a hook org-after-todo-state-change-hook which you can use here:

org-after-todo-state-change-hook is a variable defined in ‘org.el’.

Documentation:

Hook which is run after the state of a TODO item was changed. The new state (a string with a TODO keyword, or nil) is available in the Lisp variable ‘org-state’.

Use it as follows:

(require 'org-expiry)

(add-hook 'org-after-todo-state-change-hook
          (lambda ()
            (when (string= org-state "TODO")
              (save-excursion
                (org-back-to-heading)
                (org-expiry-insert-created)))))

org-expiry is part of org-contrib, which is included in the org-plus-contrib package on the org ELPA.

Wilfred Hughes
  • 29,846
  • 15
  • 139
  • 192
6

Here's a buried treasure:

(setq org-treat-insert-todo-heading-as-state-change t)

I found it here, in response to someone saying they wanted an org-insert-todo-heading-hook.

Just tried it out and, true to form, when you org-insert-todo-heading, it counts as a state change, so ex: #+TODO: TODO(t!) | ... will add a log.

Josh.F
  • 3,666
  • 2
  • 27
  • 37
  • @JohnDeBord see [Tracking state changes](https://orgmode.org/manual/Tracking-TODO-state-changes.html) – NickD Feb 04 '23 at 14:47
4

If you create all your TODOs with org-capture the following capture template does the trick:

(setq org-capture-templates
'(
    ("t" "TODO Task" entry (file+headline "~/inbox.org" "Tasks")
         "* TODO %?\nCREATED: %u\nSRC: %a\n%i\n")
    ))

The result will look something like this:

* Tasks
** TODO Dummy task
CREATED: [2015-05-08 Fri]
SRC: [[file:~/path/to/file/where/you/created/the/task.org::*heading"][heading]]
Stefan
  • 1,246
  • 1
  • 9
  • 13
2

Here is a lightweight solution that does not require an external package. I got it from the answer by @MarcinAntczak, the comments by @Clément, and this similar thread. It works with org-capture and with M-S-RET. Put this in your Emacs initialization file (e.g. ~/.emacs):

(defun insert-created-date(&rest ignore)
  (insert (format-time-string
       (concat "\nCREATED: "
           (cdr org-time-stamp-formats))
       ))
  (org-back-to-heading) ; in org-capture, this folds the entry; when inserting a heading, this moves point back to the heading line
  (move-end-of-line()) ; when inserting a heading, this moves point to the end of the line
  )


                    ; add to the org-capture hook
(add-hook 'org-capture-before-finalize-hook 
         #'insert-created-date
)

                    ; hook it to adding headings with M-S-RET
                    ; do not add this to org-insert-heading-hook, otherwise this also works in non-TODO items
                    ; and Org-mode has no org-insert-todo-heading-hook
(advice-add 'org-insert-todo-heading :after #'insert-created-date)

I did not add this function to state changes (e.g., from plain heading to TODO) because it would need to be in a properties drawer and I prefer to not have those extra lines. If you prefer to have it in properties, use the function defined in see this thread.

miguelmorin
  • 5,025
  • 4
  • 29
  • 64
1

You can add a time stamp at creation time with zero config, but it won't be labeled CREATED. Rather than manually typing TODO, use C-c C-t. It will then be logged as "state changed to TODO from """ and time stamped.

mankoff
  • 2,225
  • 6
  • 25
  • 42