4

I'm open to suggestions relating to:

  • org-mode
  • calendar
  • calc

I recently learned that the calculator seems to have a basic way of doing it with a list of excluded days (by default Saturday and Sunday), but I don't know how to connect the pieces.

The holiday list I'd be using is the generic US holiday list.

Trevoke
  • 2,375
  • 21
  • 34
  • Doesn't appear to exist, but a function could do it (I wouldn't recommend starting with the function below). Are you thinking of Org Mode? It has enhanced calendar navigation, but eve then I don't think it has this ability (yet). – mankoff Jan 06 '15 at 01:35
  • This project requires a predefined holiday schedule (excluded from the business day calculation), which will vary depending upon the user's needs -- e.g., U.S. federal holidays; court holidays for a particular state court; and other countries have their own nationally recognized holidays. The variable `calendar-holidays` in `holidays.el` groups many holiday schedules underneath one umbrella, which can be seen by using `M-x describe-variable RET calendar-holidays RET`. The function `(calendar-cursor-to-visible-date DATE)` from `cal-move.el` is used to move the cursor to the date desired. – lawlist Jan 06 '15 at 17:38

1 Answers1

3

SHORTCUT:  Copy and paste code into the *scratch* buffer; type M-x eval-buffer RET; and, then type M-x count-business-days RET.

DOCUMENTATION:  As mentioned in the comment underneath the question, this answer is dependent upon the holiday calendar defined by the particular user -- e.g., federal holidays in U.S.; state court holidays (e.g., California, U.S.); or national holidays in another country. The variable custom-holidays defined below has common U.S. federal holidays, and the user is free to custom tailor that variable to suit his / her individual needs. The variables mark-target-date and move-cursor-to-target-date are both set to t and should be self-explanitory based on the doc-string. The date format for the initial date uses read-string because org-read-date is set up to use overlays instead of the type of default value that I prefer to see squarely placed inside the minibuffer; thereafter, org-read-date interprets that. In addition, org-read-date is set up to bury the *Calendar* buffer after a date is selected, and we don't want that behavior here. We use org-read-date; however, we only feed it a date already selected by the user. org-read-date can be picky about the format, so I have included an initial default based upon the location of the cursor position in the *Calendar* buffer as a starting point. Any format recognized by org-read-date is acceptable -- see the doc-string of org-read-date for generally recognized date formats. The mathematics generated correct results in my initial testing using the three types of possibilities -- i.e., beginning date as a holiday, or as a weekend, or as a weekday. If there is a special case I have not considered, or something seems not right, please let me know. The holidays list only goes back two (2) months in time -- for example, if the user is selecting an initial date of January 6, 2015, then the holiday list will include the months of November 2014, December 2014 and January 2015. On the todo-list is a check and error message in the event a user tries to select a number of business days beyond the outer limits of the available holiday schedule. [It is possible to have a year calendar; however, that is beyond the scope of this example.]


HISTORY

First Rough Draft (January 6, 2015):  First rough draft.


(defvar mark-target-date t
"When `t`, highlight the target date.
When `nil`, do not highlight the target date.")

(defvar move-cursor-to-target-date t
"When `t`, move the calendar cursor to the target date.
When `nil`, do not move the cursor to the target date.")

(defcustom custom-holidays
  (mapcar 'purecopy
  '((holiday-fixed 1 1 "New Year's Day")
    (holiday-float 1 1 3 "Martin Luther King Day")
    (holiday-float 2 1 3 "President's Day")
    (holiday-float 5 1 -1 "Memorial Day")
    (holiday-fixed 7 4 "Independence Day")
    (holiday-float 9 1 1 "Labor Day")
    (holiday-float 10 1 2 "Columbus Day")
    (holiday-fixed 11 11 "Veteran's Day")
    (holiday-float 11 4 4 "Thanksgiving")
    (holiday-fixed 12 25 "Christmas")) )
  "Custom holidays defined by the user."
  :type 'sexp
  :group 'holidays)

(defun count-business-days ()
"Count x number of business days backwards -- excluding holidays defined by the
user in the variable `custom-holidays`; and, excluding Saturdays and Sundays."
(interactive)
  (require 'holidays)
  (require 'org)
  (require 'calendar)
  (unless (get-buffer-window calendar-buffer (selected-frame))
    (calendar))
  (let* (
      holidays
      (counter 1)
      (cursor-date (when (get-buffer calendar-buffer) ;; format is:  '(month day year)
        (with-current-buffer calendar-buffer (calendar-cursor-to-nearest-date))))
      (proposed-month (when cursor-date
        (if (< (nth 0 cursor-date) 10)
          (format "%02d" (nth 0 cursor-date))
          (number-to-string (nth 0 cursor-date)))))
      (proposed-day (when cursor-date
        (if (< (nth 1 cursor-date) 10)
          (format "%02d" (nth 1 cursor-date))
          (number-to-string (nth 1 cursor-date)))))
      (proposed-year (when cursor-date (number-to-string (nth 2 cursor-date))))
      ;; `org-read-date` likes the format of:  year-month-day
      (proposed-date
        (when cursor-date (concat proposed-year "-" proposed-month "-" proposed-day)))
      (initial-date (read-string
        "year-month-day of initial date:  "
          proposed-date nil proposed-date nil))
      (days (read-number "Number of Days (before):  "))
      (day-variable (org-read-date nil t initial-date nil)) )
    (let* (
        (displayed-month (nth 4 (decode-time day-variable)))
        (displayed-year (nth 5 (decode-time day-variable))) )
      (calendar-increment-month displayed-month displayed-year -1)
      (dolist (holiday
          (let (res h)
            (dolist (p custom-holidays res)
              (when (setq h (eval p))
                (setq res (append h res))))))
        (setq holidays (append holidays (list holiday)))) )
    (catch 'done
      (while t
        (setq day-variable (time-subtract day-variable (days-to-time 1)))
        (let* (
            (day (nth 3 (decode-time day-variable)))
            (month (nth 4 (decode-time day-variable)))
            (year (nth 5 (decode-time day-variable)))
            (day-of-week (nth 6 (decode-time day-variable)))
            (gregorian (list month day year))
            holiday-p)
          (mapcar (lambda (x)
            (if (equal gregorian (car x))
              (setq holiday-p t))) holidays)
          ;; DEBUGGING
          ;; (message "holidays:  %s" holidays)
          ;; (message "greg: %s | count:  %s | day:  %s | day-variable: %s"
          ;;   gregorian counter day-of-week (decode-time day-variable))
          (if
              (and
                (= counter days)
                (memq day-of-week '(1 2 3 4 5))
                (not holiday-p))
            (throw 'done nil)
            (when
                (and
                  (memq day-of-week '(1 2 3 4 5))
                  (not holiday-p))
              (setq counter (1+ counter)))))))
    (when (get-buffer calendar-buffer)
      (let* (
          (decoded-target (decode-time day-variable))
          (month-target (nth 4 decoded-target))
          (day-target (nth 3 decoded-target))
          (year-target (nth 5 decoded-target))
          (target (list month-target day-target year-target)) )
        (unless
            (with-current-buffer calendar-buffer (calendar-date-is-visible-p target))
          (with-current-buffer calendar-buffer
            (calendar-generate month-target year-target)))
        (when mark-target-date
          (calendar-mark-visible-date target 'holiday)) ;; `'holiday` is the face.
        (when move-cursor-to-target-date
          (calendar-cursor-to-visible-date target))))
    (message "Initial Date: %s | Target Date: %s | Business Days: %s"
      initial-date (format-time-string "%Y-%m-%d" day-variable) days)))
lawlist
  • 18,826
  • 5
  • 37
  • 118