duty.el

Sprache || Language

Emacs-/Linux-Inhalte werden auf Englisch veröffentlicht.

Emacs-/Linux related content will be published in English.

Dealing with working days

I have x vacation days, by contract with my employer. How many did I take already? How many are left for the current year?

How many business days until deadline y, of course excluding holidays and leave days? Can I finish this project on time?

How many sick days did I have in 2020?

For my amusement (or for doing my taxes) I might need to know how many days I worked in 2021 - not counting public holidays, sick days or vacation days.

I want to schedule daily standups, jour fixes, reoccuring maintenance tasks - they should not clutter my agenda view when I'm on leave though.

Some org-mode magic, some of elisp, and all of this becomes easy.

Let me introduce to you: duty.el (an Emacs package)!

Disclaimer

This sounded way more consequential than it should. This is basically my first published piece of elisp - it is way worse written than it could be, while doing way less than it should.

Consider it a WIP, because I do.

I stumbled across this (Counting working days again) blog post by Marcin Borkowski while reading the 2022-11-07 Emacs news by Sacha Chua. This teased me to write about and publish my own version of accomplishing similar things.

Please do not hesitate to contact me to suggest improvements, but also to try out if this packages works for you.

Examples of what I do using duty.el

  • counting days

    • counting workdays in any given time frame
    • counting non-workdays in any given time frame
    • counting vacation days in any given time frame
  • scheduling tasks

    • only on work days
    • only on non-work days
    • only on days before a work day
  • marking in org-mode

    • work days
    • vacation days
    • sick days
  • having an overview over

    • how many vacation days I already took that year
    • how many are left
  • planning vacations by

    • selecting time frame in M-x calendar
    • automatically creating/scheduling tasks for

      • requesting that time frame as vacation with my employer
      • awaiting acceptance/decline
      • generating the elisp for the date range, which a lot of the magic here is built on
      • setting up an absence note on the last workday before my absence
      • inform colleagues about loose strings on the last workday before my absence

How I do these things using duty.el

Counting days

In M-x calendar, pressing M-= counts days between point and mark (Basically, what non emacs users know as a selection. In this case a selection of dates).

Pressing C-u M-= counts workdays in that same region.

Pressing C-u C-u M-= counts non workdays in that same region.

Pressing C-u C-u C-u M-= counts vacation days in that same region.

This may be updated to select type of days to count via list selection instead of stacking universal arguments C-u.

Scheduling tasks and marking in org-mode

org-mode supports diary-style expression entries. The following entry would display the task "Important task" on 10. November 2022 (at least with calendar-date-style configured to 'iso).

*​ Important task
<%%(diary-date 2022 11 10)>

These expressions can get arbitrarily complex. And since these are simply elisp functions, you can hide them behind your own function names:

​* Work days
<%%(duty-work-days)>

​* Vacation days
<%%(duty-vacation-days)>

​* Sick days
<%%(duty-sick-days)>

​* 9:00-9:15 Daily Standup
<%%(duty-work-days)>

​* 20:00 Go to bed early
<%%(duty-days-where-next-day-is-a-work-day)>

Planning new vacation days

Pressing i v ("insert vacation" mnemonic) in M-x calendar to create a bunch of tasks for arranging to get time off in the selected time range and things to work through for when I'll actually be able to leave.

Overview over vacation days

Pressing C-c C-x C-c (M-x org-columns) at the location of the created tasks mentioned in the previous section displays the duration of each leave while also adding them up for the info on how many one took in total.

What's required to be able to do this

Package installation

I recommend the following.

Clone duty.el repo from github or download duty.el directly.

(add-to-list 'load-path "~/path/to/duty.el")
(require 'duty)
(keymap-set calendar-mode-map "M-=" 'duty-calendar-count-days-region)
(keymap-set calendar-mode-map "i v" 'duty-calendar-new-holidays)

I had issues with the package loading too late with use-package and getting a buch of errors (bad s-exps) in my org files. If you know how to circumvent this issue I'd be glad to hear from you.

General

It is mostly about maintaining the functions duty-work-days, duty-vacation-days and duty-sick-days, as well as an one time effort to define duty-holiday-days.

You can define arbitraty types of days next to work-days, vacation-days and sick-days to keep track of.

duty-holiday-days uses the calendar-holidays format, so you could in theory (setq duty-holiday-days 'calendar-holidays). In practice this is not a good idea, since it, by default, contains a lot of days you'll most probably still have to work on.

The elisp to add into duty-vacation-days is automatically generated and added to a task when calling duty-calendar-new-holidays.

But you could also write it yourself - or by pressing i d or i b in M-x calendar to let emacs create a diary-date or diary-block expression for you anytime.

This is also the easiest way to create expressions to add to duty-sick-days.

Examples

duty-work-days
(defun duty-work-days ()
  (and
   (not (duty-is-weekend-p date))
   (not (duty-is-official-holiday-p date))
   (not (duty-vacation-days))
   (not (duty-sick-days))
   (diary-block 2022 01 01 2025 12 31) ; time of employment
   ))
duty-vacation-days
(defun duty-vacation-days ()
  (and
   (not (duty-is-weekend-p date))
   (not (duty-is-official-holiday-p date))
   (or (diary-date 2022 02 14)
       (diary-block 2022 04 15 2022 04 22))))
duty-sick-days
(defun duty-sick-days ()
  (diary-block 2022 02 01 2022 02 08) ; Covid
  (diary-date 2022 03 01) ; my finger hurt
  )

Planning new vacation days

Create a new file like ~/vacation-planning.org and set duty-org-refile-target accordingly.

After that pressing i v (M-x duty-calendar-new-holidays) in M-x calendar does the trick for the most part.

It will append a set of tasks in duty-org-refile-target.

You'll notice that these tasks are german, as well as specific to my use case by default. As of writing this there is no good customization workflow available, so you'll need to get your hands dirty and change/overwrite the duty-work-new-holidays-org-project-for function.

The part at the end could look something like this for you:

(insert
 "\n* TODO " days-string "\n"
 ":PROPERTIES:\n"
 ":DURATION: " (number-to-string day-count) "\n"
 ":END:\n"
 "** NEXT request this period\n"
 ":PROPERTIES:\n"
 ":TRIGGER:  " days-string-id "-reply(WAIT)\n"
 ":END:\n"
 "** TODO await acceptance/decline\n"
 ":PROPERTIES:\n"
 ":ID:       " days-string "-reply\n"
 ":TRIGGER:  chain-siblings(NEXT)\n"
 ":END:\n"
 "** TODO add to duty-vacation-days (when accepted)\n"
 diary-date-string "\n"
 "** TODO check responsibilities\n"
 "SCHEDULED: <" work-day-before-string " 8:30>\n"
 "Inform colleagues:\n"
 "- something that did not get done?\n"
 "- appointments I cannot attend?\n"
 "** TODO enable absence notice\n"
 "SCHEDULED: <" work-day-before-string " 15:30>\n")

Adapt to your needs.

Overview over vacation days

I add :COLUMNS: %7TODO(State) %65ITEM(When) %DURATION(Dur in d.){+} to a heading I refile my tasks (created by M-x calendar i v (duty-calendar-new-holidays)) to. This uses the DURATION property thats automatically calculated at task generation time.

​* Vacation planning
:PROPERTIES:
:COLUMNS:  %7TODO(State) %65ITEM(When) %DURATION(Dur in d.){+}
:CATEGORY: vacation
:END:
​** 2022
​*** left from 2021
:PROPERTIES:
:DURATION: -2
:END:
​*** approved
entries created by duty-calendar-new-holidays, refiled again once approved
​*** pending
entries created by duty-calendar-new-holidays

When now using org-columns, you will have an overview about how many actual days a vacation period takes, as well as a running total for that year. You could add another heading with the negative amount of your total vacation days like

​*** Vacation days by contract
:PROPERTIES:
:DURATION: -30
:END:

to spare youself from the burden of calculating the days you have left yourself.

Kategorien
Emacs