changeset 36851:ea0bb03a9658

see ChangeLog
author John Wiegley <johnw@newartisans.com>
date Fri, 16 Mar 2001 21:39:31 +0000
parents e1167ad75cde
children 57da1ff61f52
files lisp/calendar/timeclock.el
diffstat 1 files changed, 318 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/calendar/timeclock.el	Fri Mar 16 18:53:54 2001 +0000
+++ b/lisp/calendar/timeclock.el	Fri Mar 16 21:39:31 2001 +0000
@@ -431,7 +431,7 @@
   (interactive)
   (setq timeclock-discrepancy nil)
   (timeclock-find-discrep)
-  (if timeclock-modeline-display
+  (if (and timeclock-discrepancy timeclock-modeline-display)
       (timeclock-update-modeline))
   timeclock-discrepancy)
 
@@ -913,7 +913,7 @@
 	 (now (current-time))
 	 (todays-date (timeclock-time-to-date now))
 	 last-date-limited last-date-seconds last-date
-	 (line 0) last beg day entry)
+	 (line 0) last beg day entry event)
     (with-temp-buffer
       (insert-file-contents (or filename timeclock-file))
       (when recent-only
@@ -940,11 +940,15 @@
 	       (let ((date (timeclock-time-to-date (cadr event))))
 		 (if (and last-date
 			  (not (equal date last-date)))
-		   (setcar (cdr log-data)
-			   (cons (cons last-date day)
-				 (cadr log-data)))
-		   (setq day (list (and last-date-limited
-					last-date-seconds))))
+		     (progn
+		       (setcar (cdr log-data)
+			       (cons (cons last-date day)
+				     (cadr log-data)))
+		       (setq day (list (and last-date-limited
+					    last-date-seconds))))
+		   (unless day
+		     (setq day (list (and last-date-limited
+					  last-date-seconds)))))
 		 (setq last-date date
 		       last-date-limited nil)))
 	      ((equal (downcase (car event)) "o")
@@ -963,7 +967,7 @@
 		 (nconc day (list entry))
 		 (setq desc (nth 2 entry))
 		 (let ((proj (assoc desc (nth 2 log-data))))
-		   (if (not proj)
+		   (if (null proj)
 		       (setcar (cddr log-data)
 			       (cons (cons desc (list entry))
 				     (car (cddr log-data))))
@@ -983,90 +987,313 @@
   ;; This is not implemented in terms of the functions above, because
   ;; it's a bit wasteful to read all of that data in, just to throw
   ;; away more than 90% of the information afterwards.
-  (let* ((now (current-time))
-	 (todays-date (timeclock-time-to-date now))
-	 (first t) (accum 0)
-	 event beg last-date avg
-	 last-date-limited last-date-seconds)
-    (unless timeclock-discrepancy
-      (setq timeclock-project-list nil
-	    timeclock-last-project nil
-	    timeclock-reason-list nil
-	    timeclock-elapsed 0)
-      (with-temp-buffer
-	(insert-file-contents timeclock-file)
-	(goto-char (point-max))
-	(unless (re-search-backward "^b\\s-+" nil t)
-	  (goto-char (point-min)))
-	(while (setq event (timeclock-read-moment))
-	  (cond ((equal (car event) "b")
-		 (setq accum (string-to-number (nth 2 event))))
-		((equal (car event) "h")
-		 (setq last-date-limited
-		       (timeclock-time-to-date (cadr event))
-		       last-date-seconds
-		       (* (string-to-number (nth 2 event)) 3600.0)))
-		((equal (car event) "i")
-		 (when (and (nth 2 event)
+  (when (file-readable-p timeclock-file)
+    (let* ((now (current-time))
+	   (todays-date (timeclock-time-to-date now))
+	   (first t) (accum 0)
+	   event beg last-date avg
+	   last-date-limited last-date-seconds)
+      (unless timeclock-discrepancy
+	(setq timeclock-project-list nil
+	      timeclock-last-project nil
+	      timeclock-reason-list nil
+	      timeclock-elapsed 0)
+	(with-temp-buffer
+	  (insert-file-contents timeclock-file)
+	  (goto-char (point-max))
+	  (unless (re-search-backward "^b\\s-+" nil t)
+	    (goto-char (point-min)))
+	  (while (setq event (timeclock-read-moment))
+	    (cond ((equal (car event) "b")
+		   (setq accum (string-to-number (nth 2 event))))
+		  ((equal (car event) "h")
+		   (setq last-date-limited
+			 (timeclock-time-to-date (cadr event))
+			 last-date-seconds
+			 (* (string-to-number (nth 2 event)) 3600.0)))
+		  ((equal (car event) "i")
+		   (when (and (nth 2 event)
+			      (> (length (nth 2 event)) 0))
+		     (add-to-list 'timeclock-project-list (nth 2 event))
+		     (setq timeclock-last-project (nth 2 event)))
+		   (let ((date (timeclock-time-to-date (cadr event))))
+		     (if (and timeclock-relative
+			      (if last-date
+				  (not (equal date last-date))
+				first))
+			 (setq first nil
+			       accum (- accum
+					(if last-date-limited
+					    last-date-seconds
+					  timeclock-workday))))
+		     (setq last-date date
+			   last-date-limited nil)
+		     (if beg
+			 (error "Error in format of timelog file!")
+		       (setq beg (timeclock-time-to-seconds (cadr event))))))
+		  ((equal (downcase (car event)) "o")
+		   (if (and (nth 2 event)
 			    (> (length (nth 2 event)) 0))
-		   (add-to-list 'timeclock-project-list (nth 2 event))
-		   (setq timeclock-last-project (nth 2 event)))
-		 (let ((date (timeclock-time-to-date (cadr event))))
-		   (if (and timeclock-relative
-			    (if last-date
-				(not (equal date last-date))
-			      first))
-		       (setq first nil
-			     accum (- accum
-				      (if last-date-limited
-					  last-date-seconds
-					timeclock-workday))))
-		   (setq last-date date
-			 last-date-limited nil)
-		   (if beg
-		       (error "Error in format of timelog file!")
-		     (setq beg (timeclock-time-to-seconds (cadr event))))))
-		((equal (downcase (car event)) "o")
-		 (if (and (nth 2 event)
-			  (> (length (nth 2 event)) 0))
-		     (add-to-list 'timeclock-reason-list (nth 2 event)))
-		 (if (or timeclock-relative
-			 (equal last-date todays-date))
-		     (if (not beg)
-			 (error "Error in format of timelog file!")
-		       (setq timeclock-last-period
-			     (- (timeclock-time-to-seconds (cadr event)) beg)
-			     accum (+ timeclock-last-period accum)
-			     beg nil)))
-		 (if (equal last-date todays-date)
-		     (setq timeclock-elapsed
-			   (+ timeclock-last-period timeclock-elapsed)))))
-	  (setq timeclock-last-event event
-		timeclock-last-event-workday
-		(if (equal (timeclock-time-to-date now)
-			   last-date-limited)
-		    last-date-seconds
-		  timeclock-workday))
-	  (forward-line))
-	(setq timeclock-discrepancy accum)))
-    (setq accum (if today-only
-		    timeclock-elapsed
-		  timeclock-discrepancy))
-    (if timeclock-last-event
-	(if (equal (car timeclock-last-event) "i")
-	    (setq accum (+ accum (timeclock-last-period now)))
-	  (if (not (equal (timeclock-time-to-date
-			   (cadr timeclock-last-event))
-			  (timeclock-time-to-date now)))
-	      (setq accum (- accum timeclock-last-event-workday)))))
-    (setq accum
-	  (- accum
-	     (if (and timeclock-last-event
-		      (equal (timeclock-time-to-date
-			      (cadr timeclock-last-event))
-			     (timeclock-time-to-date now)))
-		 timeclock-last-event-workday
-	       timeclock-workday)))))
+		       (add-to-list 'timeclock-reason-list (nth 2 event)))
+		   (if (or timeclock-relative
+			   (equal last-date todays-date))
+		       (if (not beg)
+			   (error "Error in format of timelog file!")
+			 (setq timeclock-last-period
+			       (- (timeclock-time-to-seconds (cadr event))
+				  beg)
+			       accum (+ timeclock-last-period accum)
+			       beg nil)))
+		   (if (equal last-date todays-date)
+		       (setq timeclock-elapsed
+			     (+ timeclock-last-period timeclock-elapsed)))))
+	    (setq timeclock-last-event event
+		  timeclock-last-event-workday
+		  (if (equal (timeclock-time-to-date now)
+			     last-date-limited)
+		      last-date-seconds
+		    timeclock-workday))
+	    (forward-line))
+	  (setq timeclock-discrepancy accum)))
+      (setq accum (if today-only
+		      timeclock-elapsed
+		    timeclock-discrepancy))
+      (if timeclock-last-event
+	  (if (equal (car timeclock-last-event) "i")
+	      (setq accum (+ accum (timeclock-last-period now)))
+	    (if (not (equal (timeclock-time-to-date
+			     (cadr timeclock-last-event))
+			    (timeclock-time-to-date now)))
+		(setq accum (- accum timeclock-last-event-workday)))))
+      (setq accum
+	    (- accum
+	       (if (and timeclock-last-event
+			(equal (timeclock-time-to-date
+				(cadr timeclock-last-event))
+			       (timeclock-time-to-date now)))
+		   timeclock-last-event-workday
+		 timeclock-workday))))))
+
+;;; A reporting function that uses timeclock-log-data
+
+(defun timeclock-time-less-p (t1 t2)
+  "Say whether time T1 is less than time T2."
+  (or (< (car t1) (car t2))
+      (and (= (car t1) (car t2))
+	   (< (nth 1 t1) (nth 1 t2)))))
+
+(defun timeclock-day-base (&optional time)
+  "Given a time within a day, return 0:0:0 within that day."
+  (let ((decoded (decode-time (or time (current-time)))))
+    (setcar (nthcdr 0 decoded) 0)
+    (setcar (nthcdr 1 decoded) 0)
+    (setcar (nthcdr 2 decoded) 0)
+    (apply 'encode-time decoded)))
+
+(defun timeclock-geometric-mean (l)
+  "Compute the geometric mean of the list L."
+  (let ((total 0)
+	(count 0))
+    (while l
+      (setq total (+ total (car l))
+	    count (1+ count)
+	    l (cdr l)))
+    (if (> count 0)
+	(/ total count)
+      0)))
+
+(defun timeclock-generate-report (&optional html-p)
+  "Generate a summary report based on the current timelog file."
+  (interactive)
+  (let ((log (timeclock-log-data))
+	(today (timeclock-day-base)))
+    (if html-p (insert "<p>"))
+    (insert "Currently ")
+    (let ((project (nth 2 timeclock-last-event))
+	  (begin (nth 1 timeclock-last-event))
+	  done)
+      (if (timeclock-currently-in-p)
+	  (insert "IN")
+	(if (or (null project) (= (length project) 0))
+	    (progn (insert "Done Working Today")
+		   (setq done t))
+	  (insert "OUT")))
+      (unless done
+	(insert " since " (format-time-string "%Y/%m/%d %-I:%M %p" begin))
+	(if html-p
+	    (insert "<br>\n<b>")
+	  (insert "\n*"))
+	(if (timeclock-currently-in-p)
+	    (insert "Working on "))
+	(if html-p
+	    (insert "</b><br>\n")
+	  (insert project "*\n"))
+	(let ((proj-data (cdr (assoc project (timeclock-project-alist log))))
+	      (two-weeks-ago (timeclock-seconds-to-time
+			      (- (timeclock-time-to-seconds today)
+				 (* 2 7 24 60 60))))
+	      two-week-len today-len)
+	  (while proj-data
+	    (if (not (timeclock-time-less-p
+		      (timeclock-entry-begin (car proj-data)) today))
+		(setq today-len (timeclock-entry-list-length proj-data)
+		      proj-data nil)
+	      (if (and (null two-week-len)
+		       (not (timeclock-time-less-p
+			     (timeclock-entry-begin (car proj-data))
+			     two-weeks-ago)))
+		  (setq two-week-len (timeclock-entry-list-length proj-data)))
+	      (setq proj-data (cdr proj-data))))
+	  (if (null two-week-len)
+	      (setq two-week-len today-len))
+	  (if html-p (insert "<p>"))
+	  (insert "\nTime spent on this task today: "
+		  (timeclock-seconds-to-string today-len)
+		  ".  In the last two weeks: "
+		  (timeclock-seconds-to-string two-week-len))
+	  (if html-p (insert "<br>"))
+	  (insert "\n"
+		  (timeclock-seconds-to-string (timeclock-workday-elapsed))
+		  " worked today, "
+		  (timeclock-seconds-to-string (timeclock-workday-remaining))
+		  " remaining, done at "
+		  (timeclock-when-to-leave-string) "\n")))
+      (if html-p (insert "<p>"))
+      (insert "\nThere have been "
+	      (number-to-string
+	       (length (timeclock-day-alist log)))
+	      " days of activity, starting "
+	      (caar (last (timeclock-day-alist log))))
+      (if html-p (insert "</p>"))
+      (when html-p
+	(insert "<p>
+<table>
+<td width=\"25\"><br></td><td>
+<table border=1 cellpadding=3>
+<tr><th><i>Statistics</i></th>
+    <th>Entire</th>
+    <th>-30 days</th>
+    <th>-3 mons</th>
+    <th>-6 mons</th>
+    <th>-1 year</th>
+</tr>")
+	(let* ((day-list (timeclock-day-list))
+	       (thirty-days-ago (timeclock-seconds-to-time
+				 (- (timeclock-time-to-seconds today)
+				    (* 30 24 60 60))))
+	       (three-months-ago (timeclock-seconds-to-time
+				  (- (timeclock-time-to-seconds today)
+				     (* 90 24 60 60))))
+	       (six-months-ago (timeclock-seconds-to-time
+				(- (timeclock-time-to-seconds today)
+				   (* 180 24 60 60))))
+	       (one-year-ago (timeclock-seconds-to-time
+			      (- (timeclock-time-to-seconds today)
+				 (* 365 24 60 60))))
+	       (time-in  (vector (list t) (list t) (list t) (list t) (list t)))
+	       (time-out (vector (list t) (list t) (list t) (list t) (list t)))
+	       (breaks   (vector (list t) (list t) (list t) (list t) (list t)))
+	       (workday  (vector (list t) (list t) (list t) (list t) (list t)))
+	       (lengths  (vector '(0 0) thirty-days-ago three-months-ago
+				 six-months-ago one-year-ago)))
+	  ;; collect statistics from complete timelog
+	  (while day-list
+	    (let ((i 0) (l 5))
+	      (while (< i l)
+		(unless (timeclock-time-less-p
+			 (timeclock-day-begin (car day-list))
+			 (aref lengths i))
+		  (let ((base (timeclock-time-to-seconds
+			       (timeclock-day-base
+				(timeclock-day-begin (car day-list))))))
+		    (nconc (aref time-in i)
+			   (list (- (timeclock-time-to-seconds
+				     (timeclock-day-begin (car day-list)))
+				    base)))
+		    (let ((span (timeclock-day-span (car day-list)))
+			  (len (timeclock-day-length (car day-list)))
+			  (req (timeclock-day-required (car day-list))))
+		      ;; If the day's actual work length is less than
+		      ;; 70% of its span, then likely the exit time
+		      ;; and break amount are not worthwhile adding to
+		      ;; the statistic
+		      (when (and (> span 0)
+				 (> (/ (float len) (float span)) 0.70))
+			(nconc (aref time-out i)
+			       (list (- (timeclock-time-to-seconds
+					 (timeclock-day-end (car day-list)))
+					base)))
+			(nconc (aref breaks i) (list (- span len))))
+		      (if req
+			  (setq len (+ len (- timeclock-workday req))))
+		      (nconc (aref workday i) (list len)))))
+		(setq i (1+ i))))
+	    (setq day-list (cdr day-list)))
+	  ;; average statistics
+	  (let ((i 0) (l 5))
+	    (while (< i l)
+	      (aset time-in i (timeclock-geometric-mean
+			       (cdr (aref time-in i))))
+	      (aset time-out i (timeclock-geometric-mean
+				(cdr (aref time-out i))))
+	      (aset breaks i (timeclock-geometric-mean
+			      (cdr (aref breaks i))))
+	      (aset workday i (timeclock-geometric-mean
+			       (cdr (aref workday i))))
+	      (setq i (1+ i))))
+	  ;; Output the HTML table
+	  (insert "<tr>\n")
+	  (insert "<td align=\"center\">Time in</td>\n")
+	  (let ((i 0) (l 5))
+	    (while (< i l)
+	      (insert "<td align=\"right\">"
+		      (timeclock-seconds-to-string (aref time-in i))
+		      "</td>\n")
+	      (setq i (1+ i))))
+	  (insert "</tr>\n")
+	  
+	  (insert "<tr>\n")
+	  (insert "<td align=\"center\">Time out</td>\n")
+	  (let ((i 0) (l 5))
+	    (while (< i l)
+	      (insert "<td align=\"right\">"
+		      (timeclock-seconds-to-string (aref time-out i))
+		      "</td>\n")
+	      (setq i (1+ i))))
+	  (insert "</tr>\n")
+	  
+	  (insert "<tr>\n")
+	  (insert "<td align=\"center\">Break</td>\n")
+	  (let ((i 0) (l 5))
+	    (while (< i l)
+	      (insert "<td align=\"right\">"
+		      (timeclock-seconds-to-string (aref breaks i))
+		      "</td>\n")
+	      (setq i (1+ i))))
+	  (insert "</tr>\n")
+	  
+	  (insert "<tr>\n")
+	  (insert "<td align=\"center\">Workday</td>\n")
+	  (let ((i 0) (l 5))
+	    (while (< i l)
+	      (insert "<td align=\"right\">"
+		      (timeclock-seconds-to-string (aref workday i))
+		      "</td>\n")
+	      (setq i (1+ i))))
+	  (insert "</tr>\n"))
+	(insert "<tfoot>
+<td colspan=\"6\" align=\"center\">
+  <i>These are approximate figures</i></td>
+</tfoot>
+</table>
+</td></table>")))))
+
+;;; A helpful little function
+
+(defun timeclock-visit-timelog ()
+  "Open up the .timelog file in another window."
+  (interactive)
+  (find-file-other-window timeclock-file))
 
 (provide 'timeclock)