Skip to content

Instantly share code, notes, and snippets.

@flambard
Created May 31, 2010 12:04
Show Gist options
  • Save flambard/419770 to your computer and use it in GitHub Desktop.
Save flambard/419770 to your computer and use it in GitHub Desktop.
Extension to GNU Emacs timeclock that I use at work.
(require 'timeclock)
(require 'cl)
(defun timelog-read-date ()
(save-excursion
(beginning-of-line)
(buffer-substring-no-properties (+ 2 (point)) (+ 12 (point)))))
(defun timelog-read-time (&optional use-current-time-if-eobp)
(if (and (eobp) use-current-time-if-eobp)
(timelog-current-time)
(progn
(forward-char 15)
(let ((hour (current-word nil t)))
(forward-word 1)
(let ((minute (current-word nil t)))
(forward-word 1)
(let ((second (current-word nil t)))
(+
(* (string-to-number hour) 3600)
(* (string-to-number minute) 60)
(string-to-number second)) ))))))
(defun timelog-read-time-and-project ()
(beginning-of-line)
(let ((time (timelog-read-time)))
(forward-char 1)
(list time (current-word))))
(defun timelog-seconds-to-time (total-seconds)
(let ((hours (/ total-seconds 3600))
(%hours (% total-seconds 3600)))
(format "%d:%02d:%02d" hours (/ %hours 60) (% %hours 60))))
(defun timelog-current-time ()
(destructuring-bind (seconds minutes hours &rest ignored) (decode-time)
(+ seconds (* minutes 60) (* hours 3600))))
(defun add-to-time-list (project time-spent time-list)
(let ((previous (assoc project time-list)))
(if previous
(cons (cons project (+ time-spent (cdr previous)))
(remove previous time-list))
(cons (cons project time-spent) time-list))))
(defun timelog-narrow-to-date-range (first-date last-date) ;; YYYY/MM/DD
(widen)
(beginning-of-buffer)
(when (re-search-forward (format "^[io] %s" first-date) nil t)
(beginning-of-line)
(let ((start (point)))
(end-of-buffer)
(re-search-backward (format "^[io] %s" last-date))
(end-of-line)
(narrow-to-region start (point))
(beginning-of-buffer)
t)))
(defun timelog-narrow-to-date (date-string) ;; YYYY/MM/DD
(timelog-narrow-to-date-range date-string date-string))
(defun timelog-narrow-to-month (month-string) ;; YYYY/MM
(timelog-narrow-to-date month-string))
(defun timelog-get-dates-in-range (first-date last-date)
(let ((dates (list))
(extant-timelog-buffer (find-buffer-visiting timeclock-file)))
(with-current-buffer (find-file-noselect timeclock-file t)
(save-excursion
(when (timelog-narrow-to-date-range first-date last-date)
(let ((current-date ""))
(while (not (eobp))
(let ((this-date (timelog-read-date)))
(unless (string= current-date this-date)
(push this-date dates)
(setq current-date this-date)))
(beginning-of-line 2))))))
(reverse dates)))
(defun timelog-do-summarize-day (date-string) ;; YYYY/MM/DD
(let ((time-list (list))
(first-start-time)
(last-stop-time)
(extant-timelog-buffer (find-buffer-visiting timeclock-file)))
(with-current-buffer (find-file-noselect timeclock-file t)
(save-excursion
(save-restriction
(when (timelog-narrow-to-date date-string)
(setq first-start-time (timelog-read-time))
(while (not (eobp))
(destructuring-bind (start-time project)
(timelog-read-time-and-project)
(beginning-of-line 2)
(let ((stop-time (timelog-read-time t)))
(setq time-list
(add-to-time-list
project (- stop-time start-time) time-list))
(setq last-stop-time stop-time)))
(beginning-of-line 2) ) )))
(unless extant-timelog-buffer (kill-buffer (current-buffer))))
(setq time-list (sort time-list #'(lambda (a b) (> (cdr a) (cdr b)))))
(list first-start-time last-stop-time time-list)))
(defun timelog-do-summarize-month (month-string) ;; YYYY/MM
(let ((time-list (list))
(first-day "")
(last-day "")
(total-days 0)
(extant-timelog-buffer (find-buffer-visiting timeclock-file)))
(with-current-buffer (find-file-noselect timeclock-file t)
(save-excursion
(save-restriction
(when (timelog-narrow-to-month month-string)
(setq first-day (timelog-read-date))
(while (not (eobp))
(let ((this-day (timelog-read-date)))
(unless (string= last-day this-day)
(incf total-days)
(setq last-day this-day)))
(destructuring-bind (start-time project)
(timelog-read-time-and-project)
(beginning-of-line 2)
(let ((stop-time (timelog-read-time t)))
(setq time-list
(add-to-time-list
project (- stop-time start-time) time-list))))
(beginning-of-line 2) ) )))
(unless extant-timelog-buffer (kill-buffer (current-buffer))))
(setq time-list (sort time-list #'(lambda (a b) (> (cdr a) (cdr b)))))
(list first-day last-day total-days time-list)))
(defun timelog-do-summarize-range (first-day last-day) ;; date-strings
(let ((time-list (list))
(current-day "")
(total-days 0)
(extant-timelog-buffer (find-buffer-visiting timeclock-file)))
(with-current-buffer (find-file-noselect timeclock-file t)
(save-excursion
(save-restriction
(when (timelog-narrow-to-date-range first-day last-day)
(while (not (eobp))
(let ((this-day (timelog-read-date)))
(unless (string= current-day this-day)
(incf total-days)
(setq current-day this-day)))
(destructuring-bind (start-time project)
(timelog-read-time-and-project)
(beginning-of-line 2)
(let ((stop-time (timelog-read-time t)))
(setq time-list
(add-to-time-list
project (- stop-time start-time) time-list))))
(beginning-of-line 2) ) )))
(unless extant-timelog-buffer (kill-buffer (current-buffer))))
(setq time-list (sort time-list #'(lambda (a b) (> (cdr a) (cdr b)))))
(list total-days time-list)))
(defun time-list-sum (time-list)
(reduce #'+ time-list :initial-value 0 :key #'cdr))
(defun generate-time-table (time-list)
(concat
(format " Time spent Project\n")
(format " ---------- -------\n")
(reduce #'(lambda (table project-and-time)
(destructuring-bind (project . time) project-and-time
(concat
(format "%11s %s\n" (timelog-seconds-to-time time) project)
table)))
(reverse time-list)
:initial-value "\n")
(format " Total\n")
(format " ----------\n")
(format "%11s\n" (timelog-seconds-to-time (time-list-sum time-list)))))
(defun timelog-generate-day-report (date-string start-time stop-time time-list)
(concat
(format "________________________________\n")
(format "Report for time spent %s, between %s and %s.\n\n"
date-string
(timelog-seconds-to-time start-time)
(timelog-seconds-to-time stop-time))
(generate-time-table time-list)
(format "________________________________\n")))
(defun timelog-generate-month-report (first-day last-day total-days time-list)
(concat
(format "________________________________\n")
(format "Report for time spent between %s and %s, total of %d days.\n\n"
first-day last-day total-days)
(generate-time-table time-list)
(format "________________________________\n")))
(defun timelog-add-slashes-to-date (date-string)
(if (= 6 (length date-string))
(concat (substring date-string 0 4) "/" (substring date-string 4))
(concat (substring date-string 0 4) "/"
(substring date-string 4 6) "/"
(substring date-string 6)) ))
(defun timelog-summarize-day (date-string) ;; YYYYMMDD
(interactive "sDate [YYYYMMDD]: ")
(setq date-string (timelog-add-slashes-to-date date-string))
(destructuring-bind (start-time stop-time projects)
(timelog-do-summarize-day date-string)
(if (null projects)
(message "No entries for date %s in %s" date-string timeclock-file)
(insert (timelog-generate-day-report
date-string start-time stop-time projects)) )))
(defun timelog-summarize-today ()
(interactive)
(destructuring-bind (s m h day month year . ignored)
(decode-time)
(timelog-summarize-day (format "%d%02d%02d" year month day))))
(defun timelog-summarize-month (month-string) ;; YYYYMM
(interactive "sMonth [YYYYMM]: ")
(setq month-string (timelog-add-slashes-to-date month-string))
(destructuring-bind (start-date stop-date total-days projects)
(timelog-do-summarize-month month-string)
(if (null projects)
(message "No entries for month %s in %s" month-string timeclock-file)
(insert (timelog-generate-month-report
start-date stop-date total-days projects)) )))
(defun timelog-summarize-range (first-day last-day) ;; date-strings
(interactive "sFirst date [YYYYMMDD]: \nsLast date [YYYYMMDD]: ")
(setq first-day (timelog-add-slashes-to-date first-day))
(setq last-day (timelog-add-slashes-to-date last-day))
(destructuring-bind (total-days projects)
(timelog-do-summarize-range first-day last-day)
(if (null projects)
(message "No entries between dates %s and %s" first-day last-day)
(insert (timelog-generate-month-report first-day last-day total-days projects)))))
(defun timelog-summarize-each-day-in-range (first-day last-day) ;; date-strings
(interactive "sFirst date [YYYYMMDD]: \nsLast date [YYYYMMDD]: ")
(setq first-day (timelog-add-slashes-to-date first-day))
(setq last-day (timelog-add-slashes-to-date last-day))
(mapcar
#'(lambda (summary)
(destructuring-bind (date-string start stop time-list) summary
(insert
(timelog-generate-day-report date-string start stop time-list))))
(mapcar #'(lambda (date-string)
(cons date-string (timelog-do-summarize-day date-string)))
(timelog-get-dates-in-range first-day last-day))))
(defun timelog-current-project ()
(interactive)
(let ((extant-timelog-buffer (find-buffer-visiting timeclock-file)))
(with-current-buffer (find-file-noselect timeclock-file t)
(save-excursion
(save-restriction
(widen)
(end-of-buffer)
(let ((last-line (buffer-substring-no-properties
(line-beginning-position 0) (1- (point)))))
(message last-line)
)))
(unless extant-timelog-buffer (kill-buffer (current-buffer))))))
(defun timelog-workday-elapsed ()
(interactive)
(destructuring-bind (s m h day month year . ignored) (decode-time)
(let ((date-string (format "%d/%02d/%02d" year month day)))
(destructuring-bind (start-time stop-time projects)
(timelog-do-summarize-day date-string)
(if (null projects)
(message "No entries for date %s in %s" date-string timeclock-file)
(message "Total time worked today: %s" (timelog-seconds-to-time
(time-list-sum projects))) )))))
@pierre-rouleau
Copy link

Thanks for this Markus. I clone it and fixed byte compiler warnings in https://github.com/pierre-rouleau/timelog

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment