Implement in-file fixing of midnight-spanning events
This commit is contained in:
parent
e6f745039b
commit
05af9d05ff
|
@ -14,6 +14,11 @@
|
|||
|
||||
This is not guaranteed to be accurate - see (info \"(elisp)Timers\").")
|
||||
|
||||
(defcustom chronometrist-day-start-time "00:00:00"
|
||||
"The time at which a day is considered to start, in \"HH:MM:SS\".
|
||||
|
||||
The default is midnight, i.e. \"00:00:00\".")
|
||||
|
||||
(provide 'chronometrist-custom)
|
||||
|
||||
;; Local Variables:
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
;;; chronometrist-events.el --- Event management and querying code for Chronometrist -*- lexical-binding: t; -*-
|
||||
|
||||
(require 'plist-pp)
|
||||
(require 'subr-x)
|
||||
|
||||
;;; Commentary:
|
||||
;;
|
||||
|
||||
(require 'subr-x)
|
||||
|
||||
;;; Code:
|
||||
|
||||
(defvar chronometrist-events (make-hash-table :test #'equal))
|
||||
|
||||
;; As we're no longer dealing with vectors, these are deprecated, and
|
||||
;; will be removed once the rest of the codebase is migrated.
|
||||
(defun chronometrist-vfirst (vector)
|
||||
"Return the first element of VECTOR."
|
||||
(elt vector 0))
|
||||
|
@ -26,38 +29,59 @@
|
|||
chronometrist-events)
|
||||
dates))
|
||||
|
||||
(defun chronometrist-events-clean ()
|
||||
"Clean `chronometrist-events' so that events can be processed accurately.
|
||||
(defun chronometrist-day-start (timestamp)
|
||||
"Get start of day (according to `chronometrist-day-start-time') for TIMESTAMP.
|
||||
|
||||
TIMESTAMP must be a time string in the ISO-8601 format.
|
||||
|
||||
Return value is a time value (see `current-time')."
|
||||
(let* ((timestamp-date (->> timestamp
|
||||
(parse-iso8601-time-string)
|
||||
(decode-time)
|
||||
(-drop 3)
|
||||
(-take 3))))
|
||||
(--> chronometrist-day-start-time
|
||||
(split-string it ":")
|
||||
(mapcar #'string-to-number it)
|
||||
(reverse it)
|
||||
(append it timestamp-date)
|
||||
(apply #'encode-time it))))
|
||||
|
||||
(defun chronometrist-file-clean ()
|
||||
"Clean `chronometrist-file' so that events can be processed accurately.
|
||||
|
||||
This function splits midnight-spanning intervals into two. It
|
||||
must be called after `chronometrist-populate'.
|
||||
must be called before running `chronometrist-populate'.
|
||||
|
||||
It returns t if the table was modified, else nil."
|
||||
;; for each key-value, see if the first event has an "o" code
|
||||
(let (prev-date modified)
|
||||
(maphash (lambda (key value)
|
||||
(when (-> value (chronometrist-vfirst) (chronometrist-vfirst) (equal "o"))
|
||||
;; Add new "o" event on previous date with 24:00:00
|
||||
;; as end time, reusing the ending reason.
|
||||
;; Add new "i" event on current date with 00:00:00
|
||||
;; as start time, with the same project.
|
||||
(let* ((reason (->> value (chronometrist-vfirst) (chronometrist-vlast)))
|
||||
(prev-events (gethash prev-date chronometrist-events))
|
||||
(prev-event (chronometrist-vlast prev-events))
|
||||
(o-event (vconcat ["o"] prev-date `[24 0 0 ,reason]))
|
||||
|
||||
(current-event (chronometrist-vfirst value))
|
||||
(project (chronometrist-vlast prev-event))
|
||||
(i-event (vconcat ["i"] key `[0 0 0 ,project])))
|
||||
(--> prev-events
|
||||
(vconcat it (vector o-event))
|
||||
(puthash prev-date it chronometrist-events))
|
||||
(--> (vconcat (vector i-event) value)
|
||||
(puthash key it chronometrist-events))
|
||||
(setq modified t)))
|
||||
(setq prev-date key)) ; this assumes that the first event of the first date doesn't
|
||||
; have an "o" code (which a correct file shouldn't)
|
||||
chronometrist-events)
|
||||
(let ((buffer (find-file-noselect chronometrist-file))
|
||||
modified
|
||||
expr)
|
||||
(with-current-buffer (find-file-noselect chronometrist-file)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (setq expr (ignore-errors (read (current-buffer))))
|
||||
(let ((split-time (chronometrist-events-midnight-spanning-p (plist-get expr :start)
|
||||
(plist-get expr :stop))))
|
||||
(when split-time
|
||||
(let ((first-start (plist-get (first split-time) :start))
|
||||
(first-stop (plist-get (first split-time) :stop))
|
||||
(second-start (plist-get (second split-time) :start))
|
||||
(second-stop (plist-get (second split-time) :stop)))
|
||||
(backward-list 1)
|
||||
(chronometrist-delete-list)
|
||||
(-> expr
|
||||
(plist-put :start first-start)
|
||||
(plist-put :stop first-stop)
|
||||
(plist-pp buffer))
|
||||
(when (looking-at-p "\n\n")
|
||||
(delete-char 2))
|
||||
(-> expr
|
||||
(plist-put :start second-start)
|
||||
(plist-put :stop second-stop)
|
||||
(plist-pp buffer))
|
||||
(setq modified t))))))
|
||||
(save-buffer))
|
||||
modified))
|
||||
|
||||
;; TODO - Maybe strip dates from values, since they're part of the key
|
||||
|
@ -88,6 +112,7 @@ This function always returns nil."
|
|||
(puthash index expression chronometrist-events)))
|
||||
nil)))
|
||||
|
||||
;; to be replaced by plist-query
|
||||
(defun chronometrist-events-subset (start-date end-date)
|
||||
"Return a subset of `chronometrist-events'.
|
||||
|
||||
|
|
|
@ -38,6 +38,35 @@ NUMBER should be an integer (0-6) - see
|
|||
(car
|
||||
(rassoc number chronometrist-report-weekday-number-alist)))
|
||||
|
||||
(defun chronometrist-unix-time->iso8601 (unix-time)
|
||||
"Return UNIX-TIME as an ISO-8601 format time string.
|
||||
|
||||
UNIX-TIME must be a time value (see `current-time') accepted by
|
||||
`format-time-string'."
|
||||
(format-time-string "%FT%T%z" unix-time))
|
||||
|
||||
;; Note - this assumes that an event never crosses >1 day. This seems
|
||||
;; sufficient for all conceivable cases.
|
||||
(defun chronometrist-events-midnight-spanning-p (start-time stop-time)
|
||||
"Return non-nil if START-TIME and STOP-TIME cross a midnight.
|
||||
|
||||
Return value is a list in the form
|
||||
\((:start START-TIME
|
||||
:stop <day-start time on initial day>)
|
||||
(:start <day start time on second day>
|
||||
:stop STOP-TIME))"
|
||||
;; FIXME - time zones are ignored; may cause issues with
|
||||
;; time-zone-spanning events
|
||||
(let* ((first-day-start (chronometrist-day-start start-time))
|
||||
(next-day-start (time-add first-day-start
|
||||
'(0 . 86400)))
|
||||
(stop-time-unix (parse-iso8601-time-string stop-time)))
|
||||
(when (time-less-p next-day-start stop-time-unix)
|
||||
(list `(:start ,start-time
|
||||
:stop ,(chronometrist-unix-time->iso8601 first-day-start))
|
||||
`(:start ,(chronometrist-unix-time->iso8601 next-day-start)
|
||||
:stop ,stop-time)))))
|
||||
|
||||
;; Local Variables:
|
||||
;; nameless-current-name: "chronometrist"
|
||||
;; End:
|
||||
|
|
|
@ -48,3 +48,14 @@ Implementation of the new format has begun.
|
|||
Of course, :production and :individual may not necessarily make sense outside the context of these particular tags. Therefore, this will require supporting arbitary plist keys.
|
||||
* Which, in turn, will complicate the UI.
|
||||
* Also, presented to the user, the keys will become "fields"...which won't be able to have spaces in them, unless we want to have keywords with escaped spaces :\
|
||||
|
||||
## Midnight-spanning events
|
||||
Not sure how to deal with these. Previously, we checked if the first event for a day had an "o" code. Some possibilities -
|
||||
1. Split them at the file level
|
||||
* Advantage - operation is performed only once for each such event (no repeated work) + benefits of both simplified data-consuming code and reduced post-parsing load.
|
||||
2. Split them at the hash-table-level (i.e. rewrite chronometrist-events-clean)
|
||||
* Advantage - simplifies data-consuming code.
|
||||
3. Split them at the data-consumer level (e.g. before calculating time for one day or getting events for one day)
|
||||
* Advantage - should reduce repetitive post-parsing load.
|
||||
|
||||
They are an issue not only in calculating time spent in $TIME_RANGE, but also acquiring the events for a day.
|
||||
|
|
Reference in New Issue