This is a complete refactor of `org-habit-parse-todo'. I have also added
tests in order to make sure the behavior of `org-habit-parse-todo' did
not change. Going forward, the key HABIT-TYPE can optionally be used
to set the type of a habit. I have assigned the symbol `log-done' to
the current and only habit-type supported by org-habit.
`org-habit--get-rx-for-state-change-notes',
`org-habit--get-done-dates-for-repeater',
`org-habit--parse-todo-with-repeater' and `org-habit-parse-todo' take
a habit-type as one of their arguments. They will only receive
`log-done' for that value until other habit-types are implemented. The
plan is to use those functions for numeric habits. This refactor will
also simplify the process of adding time based habits.
From 881c039fab3dbdda3f5bfd01b1bb4e499d238050 Mon Sep 17 00:00:00 2001
From: ApollonDeParnasse <[email protected]>
Date: Sun, 31 May 2026 13:10:27 -0500
Subject: [PATCH] org-habit.el: `org-habit-parse-todo' refactor

* lisp/org-habit.el (org-habit-parse-todo): Use `org-habit--parse-todo'
to get the list of data that represents a habit.
(org-habit--parse-todo): Choose a function to use to parse a todo
based on the habit-type of the todo.
(org-habit--parse-todo-with-repeater): Choose a function to use to
parse a todo with a repeater based on the habit-type of the todo.
(org-habit--get-done-dates-for-repeater): Get the dates a todo
with a repeater was completed.
(org-habit--get-rx-for-state-change-notes): Create a regex for the
state change notes of a todo with a repeater.
(org-habit--get-repeater-days-and-type): Get the number of days
and the type of the repeater of a todo.
(org-habit--get-repeater-deadline-days-and-date): Get the number of days
and the date of the deadline of a repeater.
(org-habit--get-scheduled-date): Get the scheduled date from
a timestamp element.
(org-habit--timestamp-to-days): Convert a timestamp string into
a number of days.
(org-habit--repeater-value-to-days): Convert a repeater into
a number of days.
(org-habit-build-graph): Use repeater symbols instead of the
actual string repeater values.

* testing/lisp/test-org-habit.el
(org-habit--get-done-dates-for-repeater-asserter): Helper function
to create asserters for `org-habit--get-done-dates-for-repeater'
tests.
(org-habit--get-done-dates-for-repeater-log-done-asserter): Helper
function for `org-habit--get-done-dates-for-repeater' tests.
(org-habit-parse-todo-asserter): Helper function for
`org-habit-parse-todo' tests.
(test-org-habit--get-done-dates-for-repeater/log-done): Tests for
`org-habit--get-done-dates-for-repeater'.
(test-org-habit-parse-todo/log-done/no-deadline): New tests for
`org-habit-parse-todo'.
(test-org-habit-parse-todo/log-done/with-deadline): New tests for
`org-habit-parse-todo'.
---
 lisp/org-habit.el              | 186 ++++++++-----
 testing/lisp/test-org-habit.el | 458 +++++++++++++++++++++++++++++++++
 2 files changed, 585 insertions(+), 59 deletions(-)

diff --git a/lisp/org-habit.el b/lisp/org-habit.el
index 8d0108639..a7d1568e0 100644
--- a/lisp/org-habit.el
+++ b/lisp/org-habit.el
@@ -35,6 +35,9 @@
 (require 'org)
 (require 'org-agenda)
 
+(declare-function org-element-property "org-element-ast" (property node))
+(declare-function org-element-end "org-element" (node))
+
 (defgroup org-habit nil
   "Options concerning habit tracking in Org mode."
   :tag "Org Habit"
@@ -173,6 +176,123 @@ means of creating calendar-based reminders."
 EPOM is an element, marker, or buffer position."
   (string= "habit" (org-entry-get epom "STYLE" 'selective)))
 
+(defun org-habit--repeater-value-to-days (repeater-unit repeater-value)
+  "Convert repeater duration into days.
+REPEATER-UNIT should be one of h, d, w, m or y.
+REPEATER-VALUE should be a number."
+  (let ((multiple
+         (pcase-exhaustive repeater-unit
+	   (`day 1)
+           (`week 7)
+	   (`month 30.4)
+           (`year 365.25)
+           (_ (error "Invalid unit: %s" repeater-unit)))))
+    (* repeater-value multiple)))
+
+(defun org-habit--timestamp-to-days (timestamp)
+  "Convert TIMESTAMP into a number of days since the epoch."
+  (time-to-days (org-time-string-to-time timestamp)))
+
+(defun org-habit--get-scheduled-date (timestamp-element)
+  "Get the scheduled date in days for TIMESTAMP-ELEMENT."
+  (if-let* ((scheduled (org-element-property :raw-value timestamp-element)))
+      (org-habit--timestamp-to-days scheduled)
+    (error "Habit %s has no scheduled date" (org-element-property :title timestamp-element))))
+
+(defun org-habit--get-repeater-deadline-days-and-date (timestamp-element sr-days scheduled)
+  "Get the scheduled date in days for TIMESTAMP-ELEMENT.
+SR-DAYS should be the value of TIMESTAMP-ELEMENT's
+repeater in days.  SCHEDULED should be the date of
+the next scheduled repeat of the habit, in days."
+  (when-let* ((deadline-unit (org-element-property :repeater-deadline-unit timestamp-element))
+              (deadline-value (org-element-property :repeater-deadline-value timestamp-element))
+              (dr-days (org-habit--repeater-value-to-days deadline-unit deadline-value)))
+    (if (<= dr-days sr-days)
+	(error "Habit %s deadline repeat period is less than or equal to scheduled (%s)"
+	       (org-element-property :title timestamp-element) sr-days)
+      (list dr-days (+ scheduled (- dr-days sr-days))))))
+
+(defun org-habit--get-repeater-days-and-type (timestamp-element)
+  "Get the scheduled date in days for TIMESTAMP-ELEMENT."
+  (let* ((repeater-type (org-element-property :repeater-type timestamp-element))
+         (repeater-unit (org-element-property :repeater-unit timestamp-element))
+         (repeater-value (org-element-property :repeater-value timestamp-element))
+         (sr-days (org-habit--repeater-value-to-days repeater-unit repeater-value)))
+    (cond
+     ((not repeater-type) (error "Habit `%s' has no scheduled repeat period or has an incorrect one" (org-element-property :title timestamp-element)))
+     ((<= sr-days 0) (error "Habit %s scheduled repeat period is less than 1d" (org-element-property :title timestamp-element)))
+     (t (list sr-days repeater-type)))))
+
+(defun org-habit--get-rx-for-state-change-notes (habit-type)
+  "Returns a regex for the state changes notes.
+HABIT-TYPE should be `log-done'."
+  (let ((headings-key (pcase-exhaustive habit-type
+                        (`log-done 'done)
+                        (_ (error "Not implemented yet")))))
+    (format
+     "^[ \t]*-[ \t]+\\(?:State \"%s\".*%s%s\\)"
+     (regexp-opt org-done-keywords)
+     org-ts-regexp-inactive
+     (let ((value (alist-get headings-key org-log-note-headings)))
+       (if (not value) ""
+         (concat "\\|"
+	         (org-replace-escapes
+		  (regexp-quote value)
+		  `(("%d" . ,org-ts-regexp-inactive)
+		    ("%D" . ,org-ts-regexp)
+		    ("%s" . "\"\\S-+\"")
+		    ("%S" . "\"\\S-+\"")
+		    ("%t" . ,org-ts-regexp-inactive)
+		    ("%T" . ,org-ts-regexp)
+		    ("%u" . ".*?")
+		    ("%U" . ".*?")))))))))
+
+(defun org-habit--get-done-dates-for-repeater (element habit-type)
+  "Get closed dates from ELEMENT for HABIT-TYPE.
+ELEMENT should be an org-element.
+HABIT-TYPE should be `log-done'."
+  (org-back-to-heading t)
+  (let* ((maxdays (+ org-habit-preceding-days org-habit-following-days))
+	 (reversed org-log-states-order-reversed)
+	 (search (if reversed 're-search-forward 're-search-backward))
+         (end (org-element-end element))
+	 (limit (if reversed end (point)))
+	 (count 0)
+         (done-dates)
+	 (re (org-habit--get-rx-for-state-change-notes habit-type)))
+    (unless reversed (goto-char end))
+    (while (and (< count maxdays) (funcall search re limit t))
+      (push (time-to-days
+	     (org-time-string-to-time
+	      (or (match-string-no-properties 1)
+		  (match-string-no-properties 2))))
+	    done-dates)
+      (setq count (1+ count)))
+    done-dates))
+
+(defun org-habit--parse-todo-with-repeater (element habit-type)
+  "Get data from ELEMENT with repeater for HABIT-TYPE.
+ELEMENT should be an org-element.
+HABIT-TYPE should be `log-done'."
+  (let* ((scheduled-timestamp (org-element-property :scheduled element))
+         (scheduled (org-habit--get-scheduled-date scheduled-timestamp))
+         (sr-days-and-type (org-habit--get-repeater-days-and-type scheduled-timestamp))
+         (sr-days (car sr-days-and-type))
+         (sr-type (cadr sr-days-and-type))
+         (dr-days-and-deadline (org-habit--get-repeater-deadline-days-and-date scheduled-timestamp sr-days scheduled))
+         (dr-days (car dr-days-and-deadline))
+         (deadline (cadr dr-days-and-deadline))
+	 (done-dates (org-habit--get-done-dates-for-repeater element habit-type)))
+    (list scheduled sr-days deadline dr-days done-dates sr-type)))
+
+(defun org-habit--parse-todo (element habit-type)
+  "Get data for HABIT-TYPE from ELEMENT.
+ELEMENT should be an org-element.
+HABIT-TYPE should be `log-done'."
+  (pcase-exhaustive habit-type
+    (`log-done (org-habit--parse-todo-with-repeater element habit-type))
+    (_ (error "Not implemented yet"))))
+
 (defun org-habit-parse-todo (&optional pom)
   "Parse the TODO surrounding point for its habit-related data.
 Returns a list with the following elements:
@@ -184,66 +304,14 @@ Returns a list with the following elements:
   4: A list of all the past dates this todo was mark closed
   5: Repeater type as a string
 
-This list represents a \"habit\" for the rest of this module."
+This list represents a \"habit\" for the rest of this module.
+When POM is non-nil, it should be a marker or point."
   (save-excursion
     (if pom (goto-char pom))
     (cl-assert (org-is-habit-p (point)))
-    (let* ((scheduled (org-get-scheduled-time (point)))
-	   (scheduled-repeat (org-get-repeat (org-entry-get (point) "SCHEDULED")))
-	   (end (org-entry-end-position))
-	   (habit-entry (org-no-properties (nth 4 (org-heading-components))))
-	   closed-dates deadline dr-days sr-days sr-type)
-      (if scheduled
-	  (setq scheduled (time-to-days scheduled))
-	(error "Habit %s has no scheduled date" habit-entry))
-      (unless scheduled-repeat
-	(error
-	 "Habit `%s' has no scheduled repeat period or has an incorrect one"
-	 habit-entry))
-      (setq sr-days (org-habit-duration-to-days scheduled-repeat)
-	    sr-type (progn (string-match "[\\.+]?\\+" scheduled-repeat)
-			   (match-string-no-properties 0 scheduled-repeat)))
-      (unless (> sr-days 0)
-	(error "Habit %s scheduled repeat period is less than 1d" habit-entry))
-      (when (string-match "/\\([0-9]+[dwmy]\\)" scheduled-repeat)
-	(setq dr-days (org-habit-duration-to-days
-		       (match-string-no-properties 1 scheduled-repeat)))
-	(if (<= dr-days sr-days)
-	    (error "Habit %s deadline repeat period is less than or equal to scheduled (%s)"
-		   habit-entry scheduled-repeat))
-	(setq deadline (+ scheduled (- dr-days sr-days))))
-      (org-back-to-heading t)
-      (let* ((maxdays (+ org-habit-preceding-days org-habit-following-days))
-	     (reversed org-log-states-order-reversed)
-	     (search (if reversed 're-search-forward 're-search-backward))
-	     (limit (if reversed end (point)))
-	     (count 0)
-	     (re (format
-		  "^[ \t]*-[ \t]+\\(?:State \"%s\".*%s%s\\)"
-		  (regexp-opt org-done-keywords)
-		  org-ts-regexp-inactive
-		  (let ((value (cdr (assq 'done org-log-note-headings))))
-		    (if (not value) ""
-		      (concat "\\|"
-			      (org-replace-escapes
-			       (regexp-quote value)
-			       `(("%d" . ,org-ts-regexp-inactive)
-				 ("%D" . ,org-ts-regexp)
-				 ("%s" . "\"\\S-+\"")
-				 ("%S" . "\"\\S-+\"")
-				 ("%t" . ,org-ts-regexp-inactive)
-				 ("%T" . ,org-ts-regexp)
-				 ("%u" . ".*?")
-				 ("%U" . ".*?")))))))))
-	(unless reversed (goto-char end))
-	(while (and (< count maxdays) (funcall search re limit t))
-	  (push (time-to-days
-		 (org-time-string-to-time
-		  (or (match-string-no-properties 1)
-		      (match-string-no-properties 2))))
-		closed-dates)
-	  (setq count (1+ count))))
-      (list scheduled sr-days deadline dr-days closed-dates sr-type))))
+    (let* ((element (org-element-at-point))
+           (habit-type (org-element-property :HABIT_TYPE element 'log-done)))
+      (org-habit--parse-todo element habit-type))))
 
 (defsubst org-habit-scheduled (habit)
   (nth 0 habit))
@@ -363,8 +431,8 @@ current time."
 			 ;; At the last done date, use current
 			 ;; scheduling in all cases.
 			 ((null done-dates) scheduled)
-			 ((equal type ".+") (+ last-done-date s-repeat))
-			 ((equal type "+")
+			 ((equal type 'restart) (+ last-done-date s-repeat))
+			 ((equal type 'completed)
 			  ;; Since LAST-DONE-DATE, each done mark
 			  ;; shifted scheduled date by S-REPEAT.
 			  (- scheduled (* (length done-dates) s-repeat)))
diff --git a/testing/lisp/test-org-habit.el b/testing/lisp/test-org-habit.el
index b79bdf068..76bcaf923 100644
--- a/testing/lisp/test-org-habit.el
+++ b/testing/lisp/test-org-habit.el
@@ -410,6 +410,464 @@ SCHEDULED: <2009-10-17 Sat +2d/" deadline ">
       (should-error
        (org-agenda nil "a")))))
 
+(defun org-habit--get-done-dates-for-repeater-asserter (test-habit-type)
+  "Helper for `org-habit--get-done-dates-for-repeater' tests.
+Returns a closure that will create an org buffer with text.
+The closure can be used to assert actual value returned by
+ `org-habit--get-done-dates-for-repeater' is equal to a list
+of values.  TEST-HABIT-TYPE should be the type of the habit."
+  (lambda (text expected-value)
+    (org-test-with-temp-text text (org-element-at-point)
+                             (should (equal (org-habit--get-done-dates-for-repeater (org-element-at-point) test-habit-type) expected-value)))))
+
+
+(defalias 'org-habit--get-done-dates-for-repeater-log-done-asserter (org-habit--get-done-dates-for-repeater-asserter 'log-done))
+
+(defun org-habit-parse-todo-asserter (text expected-value)
+  "Helper for `org-habit-parse-todo' tests.
+Creates an org buffer with TEXT.  Asserts
+actual value returned by `org-habit-parse-todo'
+is equal to EXPECTED-VALUE."
+  (org-test-with-temp-text text
+    (should (equal (org-habit-parse-todo) expected-value))))
+
+
+(ert-deftest test-org-habit--get-done-dates-for-repeater/log-done ()
+  "Test `org-habit--get-done-dates-for-repeater' for `log-done' style habit."
+  ;; no logbook
+  (org-habit--get-done-dates-for-repeater-log-done-asserter
+   "* TODO habit
+SCHEDULED: <2009-10-21 Sat ++2d>
+:PROPERTIES:
+:STYLE:    habit
+:END:
+- State \"DONE\"       from \"TODO\"       [2009-10-19 Sun]
+- State \"DONE\"       from \"TODO\"       [2009-10-17 Sun]"
+   (list 733697 733699))
+
+  (org-habit--get-done-dates-for-repeater-log-done-asserter
+   "** TODO Read
+  SCHEDULED: <2026-05-31 Sun .+1d>
+  :PROPERTIES:
+  :LAST_REPEAT: [2026-05-30 Sat 23:03]
+  :STYLE:    habit
+  :ORDERED:  t
+  :END:
+  - State \"DONE\"       from \"TODO\"       [2026-05-30 Sat 23:03]
+  CLOCK: [2026-05-30 Sat 22:58]--[2026-05-30 Sat 23:03] =>  0:05
+  - State \"DONE\"       from \"TODO\"       [2026-05-29 Fri 23:31]
+  CLOCK: [2026-05-29 Fri 23:04]--[2026-05-29 Fri 23:31] =>  0:27
+  CLOCK: [2026-05-28 Thu 22:18]--[2026-05-28 Thu 22:43] =>  0:25
+  CLOCK: [2026-05-28 Thu 10:28]--[2026-05-28 Thu 10:33] =>  0:05
+  - State \"DONE\"       from \"TODO\"       [2026-05-28 Thu 10:05]
+  CLOCK: [2026-05-28 Thu 09:59]--[2026-05-28 Thu 10:05] =>  0:06
+  CLOCK: [2026-05-27 Wed 22:19]--[2026-05-27 Wed 22:40] =>  0:21
+  - State \"DONE\"       from \"TODO\"       [2026-05-27 Wed 09:31]"
+   (list 739763 739764 739765 739766))
+
+  (org-habit--get-done-dates-for-repeater-log-done-asserter
+   "* TODO Brush teeth
+  SCHEDULED: <2026-06-01 Mon .+1d>
+  :PROPERTIES:
+  :STYLE:  habit
+  :LAST_REPEAT: [2026-05-31 Sun 05:09]
+  :END:
+  - State \"DONE\"       from \"TODO\"       [2026-05-31 Sun 05:09]
+  - State \"DONE\"       from \"TODO\"       [2026-05-30 Sat 22:36]
+  - State \"DONE\"       from \"TODO\"       [2026-05-30 Sat 07:26]
+  - State \"DONE\"       from \"TODO\"       [2026-05-29 Fri 05:09]
+  - State \"DONE\"       from \"TODO\"       [2026-05-28 Thu 23:02]
+  - State \"DONE\"       from \"TODO\"       [2026-05-28 Thu 05:09]
+  - State \"DONE\"       from \"TODO\"       [2026-05-27 Wed 22:18]
+  - State \"DONE\"       from \"TODO\"       [2026-05-27 Wed 05:11]
+  - State \"DONE\"       from \"TODO\"       [2026-05-26 Tue 22:26]
+  - State \"DONE\"       from \"TODO\"       [2026-05-26 Tue 05:09]"
+   (list 739762 739762 739763 739763 739764 739764 739765 739766 739766 739767))
+
+  ;; with logbook
+  (org-habit--get-done-dates-for-repeater-log-done-asserter
+   "* TODO Read
+  SCHEDULED: <2026-05-31 Sun .+1d>
+  :PROPERTIES:
+  :STYLE:    habit
+  :LAST_REPEAT: [2026-05-30 Sat 16:53]
+  :END:
+  :LOGBOOK:
+  CLOCK: [2026-05-30 Sat 23:08]--[2026-05-30 Sat 23:21] =>  0:13
+  - State \"DONE\"       from \"TODO\"       [2026-05-30 Sat 16:53]
+  CLOCK: [2026-05-30 Sat 16:38]--[2026-05-30 Sat 16:53] =>  0:15
+  CLOCK: [2026-05-29 Fri 23:42]--[2026-05-29 Fri 23:48] =>  0:06
+  CLOCK: [2026-05-29 Fri 19:44]--[2026-05-29 Fri 19:57] =>  0:13
+  - State \"DONE\"       from \"TODO\"       [2026-05-29 Fri 15:23]
+  CLOCK: [2026-05-29 Fri 15:09]--[2026-05-29 Fri 15:23] =>  0:14
+  CLOCK: [2026-05-28 Thu 23:02]--[2026-05-28 Thu 23:26] =>  0:24
+  CLOCK: [2026-05-28 Thu 18:40]--[2026-05-28 Thu 18:59] =>  0:19
+  CLOCK: [2026-05-28 Thu 13:41]--[2026-05-28 Thu 14:12] =>  0:31
+  CLOCK: [2026-05-27 Wed 22:43]--[2026-05-27 Wed 22:48] =>  0:05
+  CLOCK: [2026-05-27 Wed 19:21]--[2026-05-27 Wed 19:31] =>  0:10
+  - State \"DONE\"       from \"TODO\"       [2026-05-27 Wed 12:58]
+  CLOCK: [2026-05-27 Wed 12:47]--[2026-05-27 Wed 12:58] =>  0:11
+  CLOCK: [2026-05-26 Tue 22:53]--[2026-05-26 Tue 23:10] =>  0:17
+  - State \"DONE\"       from \"TODO\"       [2026-05-26 Tue 17:03]
+  :END:"
+   (list 739762 739763 739765 739766))
+
+  (org-habit--get-done-dates-for-repeater-log-done-asserter
+   "** TODO Good Habit
+  SCHEDULED: <2026-06-01 Mon .+3d>
+  :PROPERTIES:
+  :LAST_REPEAT: [2026-05-29 Fri 18:51]
+  :STYLE:    habit
+  :END:
+  :LOGBOOK:
+  - State \"DONE\"       from \"TODO\"         [2026-05-29 Fri 18:51]
+  - State \"DONE\"       from \"TODO\"         [2026-05-27 Wed 19:12]
+  - State \"DONE\"       from \"TODO\"         [2026-05-24 Sun 19:13]
+  - State \"DONE\"       from \"TODO\"         [2026-05-23 Sat 00:17]
+  - State \"DONE\"       from \"TODO\"         [2026-05-20 Wed 17:12]
+  - State \"DONE\"       from \"TODO\"         [2026-05-20 Wed 17:10]
+  - State \"DONE\"       from \"TODO\"         [2026-05-13 Wed 12:55]
+  - State \"DONE\"       from \"TODO\"         [2026-05-11 Mon 17:32]
+  - State \"DONE\"       from \"TODO\"         [2026-05-07 Thu 12:18]
+  - State \"DONE\"       from \"TODO\"         [2026-05-03 Sun 17:47]
+  :END:"
+   (list 739739 739743 739747 739749 739756 739756 739759 739760 739763 739765))
+
+  (org-habit--get-done-dates-for-repeater-log-done-asserter
+   "*** TODO Write
+  SCHEDULED: <2026-05-31 Sun .+1d>
+  :PROPERTIES:
+  :LAST_REPEAT: [2026-05-30 Sat 23:03]
+  :STYLE:    habit
+  :ID:       bd4bd850-a1c7-40c9-b076-f5c4219d44dc
+  :ORDERED:  t
+  :END:
+  :LOGBOOK:
+  - State \"DONE\"       from \"TODO\"       [2026-05-30 Sat 23:03]
+  CLOCK: [2026-05-30 Sat 22:58]--[2026-05-30 Sat 23:03] =>  0:05
+  - State \"DONE\"       from \"TODO\"       [2026-05-29 Fri 23:31]
+  CLOCK: [2026-05-29 Fri 23:04]--[2026-05-29 Fri 23:31] =>  0:27
+  CLOCK: [2026-05-28 Thu 22:18]--[2026-05-28 Thu 22:43] =>  0:25
+  CLOCK: [2026-05-28 Thu 10:28]--[2026-05-28 Thu 10:33] =>  0:05
+  - State \"DONE\"       from \"TODO\"       [2026-05-28 Thu 10:05]
+  CLOCK: [2026-05-28 Thu 09:59]--[2026-05-28 Thu 10:05] =>  0:06
+  CLOCK: [2026-05-27 Wed 22:19]--[2026-05-27 Wed 22:40] =>  0:21
+  - State \"DONE\"       from \"TODO\"       [2026-05-27 Wed 09:31]
+  :END:"
+   (list 739763 739764 739765 739766)))
+
+(ert-deftest test-org-habit-parse-todo/log-done/no-deadline ()
+  "Test `org-habit-parse-todo' for `log-done' style habit.
+Repeater don't have deadlines."
+  (org-habit-parse-todo-asserter
+   "* TODO habit
+SCHEDULED: <2009-10-21 Sat ++2d>
+:PROPERTIES:
+:STYLE:    habit
+:END:
+- State \"DONE\"       from \"TODO\"       [2009-10-19 Sun]
+- State \"DONE\"       from \"TODO\"       [2009-10-17 Sun]"
+   (list 733701 2 nil nil (list 733697 733699) 'catch-up))
+
+  (org-habit-parse-todo-asserter
+   "* TODO Write
+  SCHEDULED: <2026-05-26 Tue +1d>
+  :PROPERTIES:
+  :LAST_REPEAT: [2026-05-25 Mon 12:45]
+  :STYLE:    habit
+  :END:
+  CLOCK: [2026-05-25 Mon 13:08]--[2026-05-25 Mon 13:31] =>  0:23
+  - State \"DONE\"       from \"TODO\"       [2026-05-25 Mon 12:45]
+  CLOCK: [2026-05-25 Mon 12:39]--[2026-05-25 Mon 12:45] =>  0:06
+  CLOCK: [2026-05-24 Sun 22:36]--[2026-05-24 Sun 22:47] =>  0:11
+  - State \"DONE\"       from \"TODO\"       [2026-05-24 Sun 07:54]
+  CLOCK: [2026-05-24 Sun 07:50]--[2026-05-24 Sun 07:54] =>  0:04
+  CLOCK: [2026-05-24 Sun 07:23]--[2026-05-24 Sun 07:28] =>  0:05
+  CLOCK: [2026-05-24 Sun 06:50]--[2026-05-24 Sun 06:58] =>  0:08
+  - State \"DONE\"       from \"TODO\"       [2026-05-23 Sat 22:56]
+  CLOCK: [2026-05-23 Sat 22:35]--[2026-05-23 Sat 22:56] =>  0:21
+  CLOCK: [2026-05-22 Fri 11:00]--[2026-05-22 Fri 11:03] =>  0:03
+  CLOCK: [2026-05-22 Fri 07:23]--[2026-05-22 Fri 07:29] =>  0:06
+  - State \"DONE\"       from \"TODO\"       [2026-05-22 Fri 07:01]
+  CLOCK: [2026-05-22 Fri 06:44]--[2026-05-22 Fri 07:01] =>  0:17
+  CLOCK: [2026-05-21 Thu 22:31]--[2026-05-21 Thu 22:48] =>  0:17
+  - State \"DONE\"       from \"TODO\"       [2026-05-21 Thu 17:12]
+  CLOCK: [2026-05-21 Thu 17:02]--[2026-05-21 Thu 17:12] =>  0:10
+  - State \"DONE\"       from \"TODO\"       [2026-05-20 Wed 22:54]
+  CLOCK: [2026-05-20 Wed 18:39]--[2026-05-20 Wed 18:43] =>  0:04
+  CLOCK: [2026-05-20 Wed 07:47]--[2026-05-20 Wed 08:04] =>  0:17
+  CLOCK: [2026-05-19 Tue 22:28]--[2026-05-19 Tue 22:43] =>  0:15
+  - State \"DONE\"       from \"TODO\"       [2026-05-19 Tue 09:33]
+  CLOCK: [2026-05-19 Tue 09:24]--[2026-05-19 Tue 09:33] =>  0:09
+  CLOCK: [2026-05-19 Tue 07:27]--[2026-05-pp19 Tue 07:52] =>  0:25"
+   (list 739762 1 nil nil (list 739755 739756 739757 739758 739759 739760 739761) 'cumulate))
+
+  (org-habit-parse-todo-asserter
+   "* TODO Shave
+SCHEDULED: <2009-10-17 Sat .+2d>
+:PROPERTIES:
+:STYLE:    habit
+:LAST_REPEAT: [2009-10-19 Mon 00:36]
+:END:
+- State \"DONE\"       from \"TODO\"       [2009-10-12 Mon]
+- CLOSING NOTE [2009-10-10 Sat] \\
+  this style occurs when `org-log-done' is `note'.
+- State \"DONE\"       from \"TODO\"       [2009-10-04 Sun]
+- State \"DONE\"       from \"TODO\"       [2009-10-02 Fri]
+- State \"DONE\"       from \"TODO\"       [2009-09-29 Tue]
+- State \"DONE\"       from \"TODO\"       [2009-09-25 Fri]
+- State \"DONE\"       from \"TODO\"       [2009-09-19 Sat]
+- State \"DONE\"       from \"TODO\"       [2009-09-16 Wed]
+- State \"DONE\"       from \"TODO\"       [2009-09-12 Sat]
+- State \"DONE\"       from \"TODO\"       [2009-09-12 Sat]
+- State \"DONE\"       from \"TODO\"       [2009-09-16 Wed]
+- State \"DONE\"       from \"TODO\"       [2009-09-19 Sat]
+- State \"DONE\"       from \"TODO\"       [2009-09-25 Fri]
+- State \"DONE\"       from \"TODO\"       [2009-09-29 Tue]
+- State \"DONE\"       from \"TODO\"       [2009-10-02 Fri]
+- State \"DONE\"       from \"TODO\"       [2009-10-04 Sun]
+- CLOSING NOTE [2009-10-10 Sat] \\
+  this style occurs when `org-log-done' is `note'.
+- State \"DONE\"       from \"TODO\"       [2009-10-12 Mon]
+- State \"DONE\"       from \"TODO\"       [2009-10-15 Thu]"
+   (list 733697 2 nil nil (list 733695 733692 733690 733684 733682 733679 733675 733669 733666 733662 733662 733666 733669 733675 733679 733682 733684 733690 733692) 'restart))
+
+  (org-habit-parse-todo-asserter
+   "* TODO good habit
+  SCHEDULED: <2026-05-26 Tue ++1d>
+  :PROPERTIES:
+  :STYLE:    habit
+  :LAST_REPEAT: [2026-05-25 Mon 10:32]
+  :ID:       9b61c3b9-8efd-4c58-9aa8-2afcb6724b0d
+  :END:
+  :LOGBOOK:
+  - State \"DONE\"       from \"TODO\"       [2026-05-25 Mon 10:32]
+  CLOCK: [2026-05-25 Mon 10:14]--[2026-05-25 Mon 10:32] =>  0:18
+  CLOCK: [2026-05-24 Sun 15:29]--[2026-05-24 Sun 15:40] =>  0:11
+  - State \"DONE\"       from \"TODO\"       [2026-05-24 Sun 08:56]
+  CLOCK: [2026-05-24 Sun 08:44]--[2026-05-24 Sun 08:56] =>  0:12
+  CLOCK: [2026-05-23 Sat 22:58]--[2026-05-23 Sat 23:11] =>  0:13
+  CLOCK: [2026-05-23 Sat 18:42]--[2026-05-23 Sat 18:44] =>  0:02
+  - State \"DONE\"       from \"TODO\"       [2026-05-23 Sat 15:24]
+  CLOCK: [2026-05-23 Sat 15:09]--[2026-05-23 Sat 15:24] =>  0:15
+  CLOCK: [2026-05-23 Sat 00:34]--[2026-05-23 Sat 00:45] =>  0:11
+  CLOCK: [2026-05-22 Fri 18:20]--[2026-05-22 Fri 18:30] =>  0:10
+  CLOCK: [2026-05-22 Fri 16:00]--[2026-05-22 Fri 16:14] =>  0:14
+  - State \"DONE\"       from \"TODO\"       [2026-05-22 Fri 08:29]
+  CLOCK: [2026-05-22 Fri 08:10]--[2026-05-22 Fri 08:29] =>  0:19
+  CLOCK: [2026-05-21 Thu 22:53]--[2026-05-21 Thu 23:11] =>  0:18
+  CLOCK: [2026-05-21 Thu 18:08]--[2026-05-21 Thu 18:27] =>  0:19
+  - State \"DONE\"       from \"TODO\"       [2026-05-21 Thu 15:01]
+  :END:"
+   (list 739762 1 nil nil (list 739757 739758 739759 739760 739761) 'catch-up))
+
+  (org-habit-parse-todo-asserter
+   "* TODO Floss
+  SCHEDULED: <2026-05-25 Mon +1d>
+  :PROPERTIES:
+  :STYLE:  habit
+  :LAST_REPEAT: [2026-05-24 Sun 22:25]
+  :END:
+  :LOGBOOK:
+  - State \"DONE\"       from \"TODO\"       [2026-05-24 Sun 22:25]
+  - State \"DONE\"       from \"TODO\"       [2026-05-23 Sat 22:34]
+  - State \"DONE\"       from \"TODO\"       [2026-05-22 Fri 23:46]
+  - State \"DONE\"       from \"TODO\"       [2026-05-21 Thu 22:30]
+  - State \"DONE\"       from \"TODO\"       [2026-05-21 Thu 05:55]
+  - State \"DONE\"       from \"TODO\"       [2026-05-20 Wed 23:05]
+  :END:"
+   (list 739761 1 nil nil (list 739756 739757 739757 739758 739759 739760) 'cumulate))
+
+  (org-habit-parse-todo-asserter
+   "* TODO Read
+  SCHEDULED: <2027-01-06 Wed .+1d>
+  :PROPERTIES:
+  :STYLE:    habit
+  :LAST_REPEAT: [2026-05-25 Mon 06:19]
+  :END:
+  :LOGBOOK:
+  CLOCK: [2026-05-25 Mon 06:29]--[2026-05-25 Mon 06:31] =>  0:02
+  CLOCK: [2026-05-25 Mon 06:25]--[2026-05-25 Mon 06:27] =>  0:02
+  CLOCK: [2026-05-25 Mon 06:22]--[2026-05-25 Mon 06:24] =>  0:02
+  - State \"DONE\"       from \"TODO\"       [2026-05-25 Mon 06:19]
+  CLOCK: [2026-05-14 Thu 06:39]--[2026-05-14 Thu 06:40] =>  0:01
+  - State \"DONE\"       from \"TODO\"       [2026-05-14 Thu 06:02]
+  - State \"DONE\"       from \"TODO\"       [2026-05-13 Wed 05:58]
+  CLOCK: [2026-05-12 Tue 06:17]--[2026-05-12 Tue 06:19] =>  0:02
+  CLOCK: [2026-05-12 Tue 06:11]--[2026-05-12 Tue 06:13] =>  0:02
+  CLOCK: [2026-05-12 Tue 06:05]--[2026-05-12 Tue 06:07] =>  0:02
+  - State \"DONE\"       from \"TODO\"       [2026-05-12 Tue 06:00]
+  CLOCK: [2026-05-11 Mon 06:01]--[2026-05-11 Mon 06:03] =>  0:02
+  - State \"DONE\"       from \"TODO\"       [2026-05-11 Mon 05:57]
+  CLOCK: [2026-05-11 Mon 05:56]--[2026-05-11 Mon 05:56] =>  0:00
+  CLOCK: [2026-05-08 Fri 06:31]--[2026-05-08 Fri 06:32] =>  0:01
+  CLOCK: [2026-05-08 Fri 06:26]--[2026-05-08 Fri 06:27] =>  0:01
+  - State \"DONE\"       from \"TODO\"       [2026-05-08 Fri 06:23]
+  CLOCK: [2026-05-08 Fri 06:21]--[2026-05-08 Fri 06:22] =>  0:01
+  CLOCK: [2026-05-07 Thu 06:09]--[2026-05-07 Thu 06:11] =>  0:02
+  CLOCK: [2026-05-07 Thu 06:05]--[2026-05-07 Thu 06:06] =>  0:01
+  :END:"
+   (list 739987 1 nil nil (list 739744 739747 739748 739749 739750 739761) 'restart)))
+
+(ert-deftest test-org-habit-parse-todo/log-done/with-deadline ()
+  "Test `org-habit-parse-todo' for `log-done' style habit.
+Repeater have deadlines."
+  ;; no logbook
+  (org-habit-parse-todo-asserter
+   "* TODO habit
+SCHEDULED: <2009-10-21 Sat ++2d/3d>
+:PROPERTIES:
+:STYLE:    habit
+:END:
+- State \"DONE\"       from \"TODO\"       [2009-10-19 Sun]
+- State \"DONE\"       from \"TODO\"       [2009-10-17 Sun]"
+   (list 733701 2 733702 3 (list 733697 733699) 'catch-up))
+
+  (org-habit-parse-todo-asserter
+   "* TODO Write
+  SCHEDULED: <2026-05-26 Tue +1w/2w>
+  :PROPERTIES:
+  :LAST_REPEAT: [2026-05-25 Mon 12:45]
+  :STYLE:    habit
+  :END:
+  CLOCK: [2026-05-25 Mon 13:08]--[2026-05-25 Mon 13:31] =>  0:23
+  - State \"DONE\"       from \"TODO\"       [2026-05-25 Mon 12:45]
+  CLOCK: [2026-05-25 Mon 12:39]--[2026-05-25 Mon 12:45] =>  0:06
+  CLOCK: [2026-05-24 Sun 22:36]--[2026-05-24 Sun 22:47] =>  0:11
+  - State \"DONE\"       from \"TODO\"       [2026-05-24 Sun 07:54]
+  CLOCK: [2026-05-24 Sun 07:50]--[2026-05-24 Sun 07:54] =>  0:04
+  CLOCK: [2026-05-24 Sun 07:23]--[2026-05-24 Sun 07:28] =>  0:05
+  CLOCK: [2026-05-24 Sun 06:50]--[2026-05-24 Sun 06:58] =>  0:08
+  - State \"DONE\"       from \"TODO\"       [2026-05-23 Sat 22:56]
+  CLOCK: [2026-05-23 Sat 22:35]--[2026-05-23 Sat 22:56] =>  0:21
+  CLOCK: [2026-05-22 Fri 11:00]--[2026-05-22 Fri 11:03] =>  0:03
+  CLOCK: [2026-05-22 Fri 07:23]--[2026-05-22 Fri 07:29] =>  0:06
+  - State \"DONE\"       from \"TODO\"       [2026-05-22 Fri 07:01]
+  CLOCK: [2026-05-22 Fri 06:44]--[2026-05-22 Fri 07:01] =>  0:17
+  CLOCK: [2026-05-21 Thu 22:31]--[2026-05-21 Thu 22:48] =>  0:17
+  - State \"DONE\"       from \"TODO\"       [2026-05-21 Thu 17:12]
+  CLOCK: [2026-05-21 Thu 17:02]--[2026-05-21 Thu 17:12] =>  0:10
+  - State \"DONE\"       from \"TODO\"       [2026-05-20 Wed 22:54]
+  CLOCK: [2026-05-20 Wed 18:39]--[2026-05-20 Wed 18:43] =>  0:04
+  CLOCK: [2026-05-20 Wed 07:47]--[2026-05-20 Wed 08:04] =>  0:17
+  CLOCK: [2026-05-19 Tue 22:28]--[2026-05-19 Tue 22:43] =>  0:15
+  - State \"DONE\"       from \"TODO\"       [2026-05-19 Tue 09:33]
+  CLOCK: [2026-05-19 Tue 09:24]--[2026-05-19 Tue 09:33] =>  0:09
+  CLOCK: [2026-05-19 Tue 07:27]--[2026-05-19 Tue 07:52] =>  0:25"
+   (list 739762 7 739769 14 (list 739755 739756 739757 739758 739759 739760 739761) 'cumulate))
+
+  (org-habit-parse-todo-asserter
+   "* TODO Shave
+SCHEDULED: <2009-10-17 Sat .+3d/5d>
+:PROPERTIES:
+:STYLE:    habit
+:LAST_REPEAT: [2009-10-19 Mon 00:36]
+:END:
+- State \"DONE\"       from \"TODO\"       [2009-10-12 Mon]
+- CLOSING NOTE [2009-10-10 Sat] \\
+  this style occurs when `org-log-done' is `note'.
+- State \"DONE\"       from \"TODO\"       [2009-10-04 Sun]
+- State \"DONE\"       from \"TODO\"       [2009-10-02 Fri]
+- State \"DONE\"       from \"TODO\"       [2009-09-29 Tue]
+- State \"DONE\"       from \"TODO\"       [2009-09-25 Fri]
+- State \"DONE\"       from \"TODO\"       [2009-09-19 Sat]
+- State \"DONE\"       from \"TODO\"       [2009-09-16 Wed]
+- State \"DONE\"       from \"TODO\"       [2009-09-12 Sat]
+- State \"DONE\"       from \"TODO\"       [2009-09-12 Sat]
+- State \"DONE\"       from \"TODO\"       [2009-09-16 Wed]
+- State \"DONE\"       from \"TODO\"       [2009-09-19 Sat]
+- State \"DONE\"       from \"TODO\"       [2009-09-25 Fri]
+- State \"DONE\"       from \"TODO\"       [2009-09-29 Tue]
+- State \"DONE\"       from \"TODO\"       [2009-10-02 Fri]
+- State \"DONE\"       from \"TODO\"       [2009-10-04 Sun]
+- CLOSING NOTE [2009-10-10 Sat] \\
+  this style occurs when `org-log-done' is `note'.
+- State \"DONE\"       from \"TODO\"       [2009-10-12 Mon]
+- State \"DONE\"       from \"TODO\"       [2009-10-15 Thu]"
+   (list 733697 3 733699 5 (list 733695 733692 733690 733684 733682 733679 733675 733669 733666 733662 733662 733666 733669 733675 733679 733682 733684 733690 733692) 'restart))
+
+  ;; with logbook
+  (org-habit-parse-todo-asserter
+   "* TODO good habit
+  SCHEDULED: <2026-05-26 Tue ++6d/7d>
+  :PROPERTIES:
+  :STYLE:    habit
+  :LAST_REPEAT: [2026-05-25 Mon 10:32]
+  :ID:       9b61c3b9-8efd-4c58-9aa8-2afcb6724b0d
+  :END:
+  :LOGBOOK:
+  - State \"DONE\"       from \"TODO\"       [2026-05-25 Mon 10:32]
+  CLOCK: [2026-05-25 Mon 10:14]--[2026-05-25 Mon 10:32] =>  0:18
+  CLOCK: [2026-05-24 Sun 15:29]--[2026-05-24 Sun 15:40] =>  0:11
+  - State \"DONE\"       from \"TODO\"       [2026-05-24 Sun 08:56]
+  CLOCK: [2026-05-24 Sun 08:44]--[2026-05-24 Sun 08:56] =>  0:12
+  CLOCK: [2026-05-23 Sat 22:58]--[2026-05-23 Sat 23:11] =>  0:13
+  CLOCK: [2026-05-23 Sat 18:42]--[2026-05-23 Sat 18:44] =>  0:02
+  - State \"DONE\"       from \"TODO\"       [2026-05-23 Sat 15:24]
+  CLOCK: [2026-05-23 Sat 15:09]--[2026-05-23 Sat 15:24] =>  0:15
+  CLOCK: [2026-05-23 Sat 00:34]--[2026-05-23 Sat 00:45] =>  0:11
+  CLOCK: [2026-05-22 Fri 18:20]--[2026-05-22 Fri 18:30] =>  0:10
+  CLOCK: [2026-05-22 Fri 16:00]--[2026-05-22 Fri 16:14] =>  0:14
+  - State \"DONE\"       from \"TODO\"       [2026-05-22 Fri 08:29]
+  CLOCK: [2026-05-22 Fri 08:10]--[2026-05-22 Fri 08:29] =>  0:19
+  CLOCK: [2026-05-21 Thu 22:53]--[2026-05-21 Thu 23:11] =>  0:18
+  CLOCK: [2026-05-21 Thu 18:08]--[2026-05-21 Thu 18:27] =>  0:19
+  - State \"DONE\"       from \"TODO\"       [2026-05-21 Thu 15:01]
+  :END:"
+   (list 739762 6 739763 7 (list 739757 739758 739759 739760 739761) 'catch-up))
+
+  (org-habit-parse-todo-asserter
+   "* TODO Floss
+  SCHEDULED: <2026-05-25 Mon +1d/2d>
+  :PROPERTIES:
+  :STYLE:  habit
+  :LAST_REPEAT: [2026-05-24 Sun 22:25]
+  :END:
+  :LOGBOOK:
+  - State \"DONE\"       from \"TODO\"       [2026-05-24 Sun 22:25]
+  - State \"DONE\"       from \"TODO\"       [2026-05-23 Sat 22:34]
+  - State \"DONE\"       from \"TODO\"       [2026-05-22 Fri 23:46]
+  - State \"DONE\"       from \"TODO\"       [2026-05-21 Thu 22:30]
+  - State \"DONE\"       from \"TODO\"       [2026-05-21 Thu 05:55]
+  - State \"DONE\"       from \"TODO\"       [2026-05-20 Wed 23:05]
+  :END:"
+   (list 739761 1 739762 2 (list 739756 739757 739757 739758 739759 739760) 'cumulate))
+
+  (org-habit-parse-todo-asserter
+   "* TODO Read
+  SCHEDULED: <2027-01-06 Wed .+1d/5d>
+  :PROPERTIES:
+  :STYLE:    habit
+  :LAST_REPEAT: [2026-05-25 Mon 06:19]
+  :END:
+  :LOGBOOK:
+  CLOCK: [2026-05-25 Mon 06:29]--[2026-05-25 Mon 06:31] =>  0:02
+  CLOCK: [2026-05-25 Mon 06:25]--[2026-05-25 Mon 06:27] =>  0:02
+  CLOCK: [2026-05-25 Mon 06:22]--[2026-05-25 Mon 06:24] =>  0:02
+  - State \"DONE\"       from \"TODO\"       [2026-05-25 Mon 06:19]
+  CLOCK: [2026-05-14 Thu 06:39]--[2026-05-14 Thu 06:40] =>  0:01
+  - State \"DONE\"       from \"TODO\"       [2026-05-14 Thu 06:02]
+  - State \"DONE\"       from \"TODO\"       [2026-05-13 Wed 05:58]
+  CLOCK: [2026-05-12 Tue 06:17]--[2026-05-12 Tue 06:19] =>  0:02
+  CLOCK: [2026-05-12 Tue 06:11]--[2026-05-12 Tue 06:13] =>  0:02
+  CLOCK: [2026-05-12 Tue 06:05]--[2026-05-12 Tue 06:07] =>  0:02
+  - State \"DONE\"       from \"TODO\"       [2026-05-12 Tue 06:00]
+  CLOCK: [2026-05-11 Mon 06:01]--[2026-05-11 Mon 06:03] =>  0:02
+  - State \"DONE\"       from \"TODO\"       [2026-05-11 Mon 05:57]
+  CLOCK: [2026-05-11 Mon 05:56]--[2026-05-11 Mon 05:56] =>  0:00
+  CLOCK: [2026-05-08 Fri 06:31]--[2026-05-08 Fri 06:32] =>  0:01
+  CLOCK: [2026-05-08 Fri 06:26]--[2026-05-08 Fri 06:27] =>  0:01
+  - State \"DONE\"       from \"TODO\"       [2026-05-08 Fri 06:23]
+  CLOCK: [2026-05-08 Fri 06:21]--[2026-05-08 Fri 06:22] =>  0:01
+  CLOCK: [2026-05-07 Thu 06:09]--[2026-05-07 Thu 06:11] =>  0:02
+  CLOCK: [2026-05-07 Thu 06:05]--[2026-05-07 Thu 06:06] =>  0:01
+  :END:"
+   (list 739987 1 739991 5 (list 739744 739747 739748 739749 739750 739761) 'restart)))
+
+
+
+
+
 
 (provide 'test-org-habit)
 
-- 
2.54.0

Reply via email to