This repository has been archived on 2022-05-13. You can view files and clone it, but cannot push or open issues or pull requests.
chronometrist/elisp/chronometrist-third.org

10 KiB

chronometrist-third

Program source

Library headers and commentary

;;; chronometrist-third.el --- Third Time support for Chronometrist -*- lexical-binding: t; -*-

;; Author: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Maintainer: contrapunctus <xmpp:contrapunctus@jabjab.de>
;; Keywords: calendar
;; Homepage: https://tildegit.org/contrapunctus/chronometrist
;; Package-Requires: ((emacs "25.1") (alert "1.2") (chronometrist "0.6.0"))
;; Version: 0.0.1

;; This is free and unencumbered software released into the public domain.
;;
;; Anyone is free to copy, modify, publish, use, compile, sell, or
;; distribute this software, either in source code form or as a compiled
;; binary, for any purpose, commercial or non-commercial, and by any
;; means.
;;
;; For more information, please refer to <https://unlicense.org>

;;; Commentary:
;; Add support for the Third Time system to Chronometrist.  In Third
;; Time, you work for any length of time you like, and "earn" a third
;; of the work time as break time.  For a more detailed explanation,
;; see
;; https://www.lesswrong.com/posts/RWu8eZqbwgB9zaerh/third-time-a-better-way-to-work

;; For information on usage and customization, see https://tildegit.org/contrapunctus/chronometrist-goal/src/branch/production/README.md

Dependencies

;;; Code:
(require 'chronometrist)
(require 'alert)

group   custom group

(defgroup chronometrist-third nil
  "Third Time support for Chronometrist."
  :group 'chronometrist)

TODO working-hours   custom variable

Supporting multiple ranges per day may be a little overkill - from my reading of the Third Time post, my understanding is that each day has one large working hours range which includes all breaks. That would mean you really don't want more than one range…

(defcustom chronometrist-third-working-hours nil
  "User's working hours for today.
This can be nil, a list of one or more ISO-8601 timestamp pairs,
or a function which takes no arguments and returns such a list.

Setting this has no effect at the moment."
  :type '(choice (const :tag "No defined working hours" nil)
                 (repeat :tag "Time ranges"
                         (cons :tag "Range"
                               (string :tag "Start")
                               (string :tag "Stop")))
                 function)
  :group 'chronometrist-third)

working-hours   procedure

(defun chronometrist-third-working-hours (default-hours)
  "Return the user's working hours based on DEFAULT-HOURS.
For the possible values of DEFAULT-HOURS, see the variable
`chronometrist-third-working-hours' ."
  (cond ((and (proper-list-p default-hours)
              (seq-every-p #'consp default-hours))
         default-hours)
        ((functionp default-hours)
         (chronometrist-third-working-hours (funcall default-hours)))
        (t nil)))

divisor   custom variable

(defcustom chronometrist-third-divisor 3
  "Number to determine accumulation of break time relative to work time."
  :type 'number)

duration-format   custom variable

(defcustom chronometrist-third-duration-format "%H, %M and %S%z"
  "Format string for durations, passed to `format-seconds'."
  :type 'string)

break-time   variable

(defvar chronometrist-third-break-time 0
  "Accumulated break time in seconds.")

alert-functions   custom variable

(defcustom chronometrist-third-alert-functions '(chronometrist-third-half-alert chronometrist-third-quarter-alert chronometrist-third-break-over-alert)
  "List of timed alerts for the Third Time system.

Typically, each function in this list should call
`chronometrist-third-run-at-time' to run another function, which
in turn should call `alert' to notify the user.

All functions in this list are started when the user clocks out,
and stopped when they clock in."
  :group 'chronometrist-third
  :type 'hook)

timer-list   variable

(defvar chronometrist-third-timer-list nil)

run-at-time   procedure

(defun chronometrist-third-run-at-time (time repeat function &rest args)
  "Like `run-at-time', but store timer objects in `chronometrist-third-timer-list'."
  (cl-pushnew (apply #'run-at-time time repeat function args) chronometrist-third-timer-list))

half-alert   procedure

(defun chronometrist-third-half-alert ()
  "Display an alert when half the break time is consumed."
  (let ((half-time (/ chronometrist-third-break-time 2.0)))
    (and (not (zerop chronometrist-third-break-time))
         (chronometrist-third-run-at-time
          half-time nil
          (lambda (half-time)
            (alert
             (format "%s left on your break."
                     (format-seconds chronometrist-third-duration-format half-time))))
          half-time))))

quarter-alert   procedure

(defun chronometrist-third-quarter-alert ()
  "Display an alert when 3/4ths of the break time is consumed."
  (let ((three-fourths (* chronometrist-third-break-time 7.5)))
    (and (not (zerop chronometrist-third-break-time))
         (chronometrist-third-run-at-time
          three-fourths nil
          (lambda (three-fourths)
            (alert
             (format "%s left on your break."
                     (format-seconds chronometrist-third-duration-format
                                     (- chronometrist-third-break-time three-fourths)))))
          three-fourths))))

break-over-alert   procedure

(defun chronometrist-third-break-over-alert ()
  "Display an alert when break time is over."
  (and (not (zerop chronometrist-third-break-time))
       (chronometrist-third-run-at-time
        chronometrist-third-break-time nil
        (lambda () (alert (format "Break time is over!"))))))

start-alert-timers   procedure

(defun chronometrist-third-start-alert-timers ()
  "Run functions in `chronometrist-third-alert-functions'."
  (mapc #'funcall chronometrist-third-alert-functions))

stop-alert-timers   procedure

(defun chronometrist-third-stop-alert-timers ()
  "Stop timers in `chronometrist-third-timer-list'."
  (mapc (lambda (timer) (cancel-timer timer)) chronometrist-third-timer-list))

clock-in   hook procedure

(defun chronometrist-third-clock-in (&optional _arg)
  "Stop alert timers and update break time."
  (chronometrist-third-stop-alert-timers)
  (unless (zerop chronometrist-third-break-time)
    (-let* (((&plist :stop stop) (cl-second (chronometrist-to-list (chronometrist-active-backend))))
            (used-break          (ts-diff (ts-now) (chronometrist-iso-to-ts stop)))
            (used-break-string   (format-seconds chronometrist-third-duration-format used-break))
            (new-break           (- chronometrist-third-break-time used-break))
            (old-break           chronometrist-third-break-time))
      (setq chronometrist-third-break-time (if (> new-break 0) new-break 0))
      (alert
       (if (zerop chronometrist-third-break-time)
           (format "You have used up all %s of your break time\n(%s break)"
                   (format-seconds chronometrist-third-duration-format old-break)
                   used-break-string)
         (format "You have used %s of your break time\n(%s left)"
                 used-break-string
                 (format-seconds chronometrist-third-duration-format chronometrist-third-break-time)))))))

clock-out   hook procedure

(defun chronometrist-third-clock-out (&optional _arg)
  "Update break time based on the latest work interval.
Run `chronometrist-third-alert-functions' to alert user when
break time is up."
  (let* ((latest-work-duration (chronometrist-interval (chronometrist-latest-record (chronometrist-active-backend))))
         (break-time-increment (/ latest-work-duration chronometrist-third-divisor)))
    (cl-incf chronometrist-third-break-time break-time-increment)
    (alert (format "You have gained %s of break time\n(%s total)"
                   (format-seconds chronometrist-third-duration-format break-time-increment)
                   (format-seconds chronometrist-third-duration-format chronometrist-third-break-time)))
    ;; start alert timer(s)
    (chronometrist-third-start-alert-timers)))

third-minor-mode   minor mode

;;;###autoload
(define-minor-mode chronometrist-third-minor-mode
  nil nil nil nil
  (cond (chronometrist-third-minor-mode
         (add-hook 'chronometrist-after-in-functions #'chronometrist-third-clock-in)
         (add-hook 'chronometrist-after-out-functions #'chronometrist-third-clock-out))
        (t (remove-hook 'chronometrist-after-in-functions #'chronometrist-third-clock-in)
           (remove-hook 'chronometrist-after-out-functions #'chronometrist-third-clock-out))))

Provide

(provide 'chronometrist-third)

;;; chronometrist-third.el ends here