Compute the duration of appointment in an org-mode table - emacs

I have a list of appointments in the style of:
| Appointment | Dur. |
|------------------------------+-------|
| <2014-02-20 Thu 09:30-18:30> | ??? |
| <2014-02-22 Sat 09:00-10:00> | ??? |
| | |
How can I make orgmode calculate the durations of my appointments?

Org mode allows you to specify formulas for calculating values for a given column. You are allowed to execute elisp code in those formulas. We can use feature to achieve what you want first, we will need to define some functions which find difference between times. You can add the following to your org document
#+begin_src emacs-lisp
(defun my-get-number-of-minutes (time)
(+ (* (nth 2 time) 60) (nth 1 time)))
(defun my-get-time-diff (t1 t2)
(- (my-get-number-of-minutes t1) (my-get-number-of-minutes t2)))
(defun my-get-duration (time-string)
(let* ((times (split-string (substring time-string 16 -1) "-"))
(minute-diff (my-get-time-diff (parse-time-string (nth 1 times))
(parse-time-string (nth 0 times)))))
(format "%dhrs %dmins" (/ minute-diff 60) (% minute-diff 60))))
#+end_src
Then do C-cC-c anywhere between the #+begin-src emacs-lisp and #+end-src tags, this will evaluate the elisp.
OR
Simply copy the elisp code (between #+begin_src emacs-lisp #+end-src) to *scratch* buffer and do M-xeval-bufferRET.
Now that we have the functions defined we can tell org-mode the formula to calculate the second column from first column, to do this simply paste the following line below the org-table
#+TBLFM: $2='(my-get-duration $1)
This tells org that value of column 2 ($2) is the function my-get-duration applied 'value of column 1' ($2). Then with point on the line do C-cC-c, if you have done everything well, the second column should be filled with the duration.
NOTE: The code assumes that the datetime will be of the same format as given in your example.
I recommend you read this short article about org-mode's capabilities as a spreadsheet.
UPDATE
If you can reformat your table as follows, you will be able to simplify the formula
| Appointment | Start | End | Duration |
|------------------+-------+-------+----------|
| <2014-02-20 Thu> | 09:30 | 18:30 | ??? |
| <2014-02-22 Sat> | 09:00 | 10:00 | ??? |
The formula for calculating the duration now would simply be
#+TBLFM: $4=$3-$2;T

Related

How to access data from many tables in src blocks

In my org document, I have several tables named (with #+name:) t1, t2, etc. I want to pass all of the tables to some lisp code. This is what I have so far:
#+name: process-tables
#+header: :var t1=t1 t2=t2 t3=t3 t4=t4 t5=t5 t6=t6 t7=t7 t8=t8 t9=t9 t10=t10 t11=t11 t12=t12 t13=t13 t14=t14
#+BEGIN_SRC emacs-lisp
(process (append t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 t11 t12 t13 t14))
#+END_SRC
This seems very clumsy. Is there a better way? I do not want to merge the tables in the org document.
You can try the org-table-map-tables and org-table-to-lisp functions to create a list of all tables in the buffer. This avoids having to invoke table names individually.
(defun org-tables-to-list ()
(let (tbls)
(org-table-map-tables
(lambda ()
(push (org-table-to-lisp) tbls))
t)
(apply #'append (nreverse tbls))))
For example:
#+name: t1
| 0 | 1 |
#+name: t2
| 2 | 3 |
#+name: process-tables
#+BEGIN_SRC emacs-lisp :results value verbatim
(org-tables-to-list)
#+END_SRC
#+RESULTS: process-tables
: (("0" "1") ("2" "3"))

how to use TBLFM combined to results from org-evaluate-time-range

From a previous post (Calculate time range in org-mode table), I was able to use the org-evaluate-time-range function in a table.
I'd like to sum the obtained durations. However, searching for how to do that, I noticed the following behavior of TBLFM mode.
| <2015-12-09 01:29>--<2015-12-09 08:43> | 7 hours 14 minutes | 98 hours minutes |
| <2015-12-09 08:29>--<2015-12-09 08:43> | 14 minutes | 14 minutes |
#+TBLFM: $2='(org-evaluate-time-range)::$3=$2
Here in the third column, it seems that it is the product that is calculated. Any reason for that?
What I would like to is to sum the duration with a macro like that
#+TBLFM: $2='(org-evaluate-time-range)::#3$2=vsum(#1$2..#2$2)
Thanks.
The right-hand side of formulas like $3=$2 is evaluated by calc, and calc knows nothing about the 7 hour 14 minutes syntax. In fact, calc can do symbolic calculations, and it reads 7 hour 14 minutes as the product 7*hour*14*minutes where hour and minutes are variables.
The time syntax that calc understands is HH:MM[:SS]. This is illustrated by the org-mode manual.
TBLFM: has a flag ;T to format numeric values as time this way, but that does not work for timestamp ranges. So if you really want to start from a timestamp range, maybe one way is to write your own function to convert ranges to seconds:
Evaluate this first:
#+BEGIN_SRC emacs-lisp
(defun convert-time-range-to-seconds (range)
(if (string-match org-tr-regexp-both range)
(let ((start (match-string 1 range))
(end (match-string 2 range)))
(round (- (org-time-string-to-seconds end)
(org-time-string-to-seconds start))))
""))
#+END_SRC
| time stamp range | as seconds | as time |
|------------------------------------------------+------------+-----------|
| <2015-12-09 Wed 01:29>--<2015-12-10 Thu 08:42> | 112380 | 31:13:00 |
| <2015-12-28 Mon>--<2015-12-31 Thu> | 259200 | 72:00:00 |
| <2015-12-11 Fri>--<2015-12-13 Sun> | 172800 | 48:00:00 |
|------------------------------------------------+------------+-----------|
| | | 151:13:00 |
#+TBLFM: $2='(convert-time-range-to-seconds $1)::$3=$2;T::#>$3=vsum(#2..#-1);T

Calculate time range in org-mode table

Given a table that has a column of time ranges e.g.:
| <2015-10-02>--<2015-10-24> |
| <2015-10-05>--<2015-10-20> |
....
how can I create a column showing the results of org-evalute-time-range?
If I attempt something like:
#+TBLFM: $2='(org-evaluate-time-range $1)
the 2nd column is populated with
Time difference inserted
in every row.
It would also be nice to generate the same result from two different columns with, say, start date and end date instead of creating one column of time ranges out of those two.
If you have your date range split into 2 columns, a simple subtraction works and returns number of days:
| <2015-10-05> | <2015-10-20> | 15 |
| <2013-10-02 08:30> | <2015-10-24> | 751.64583 |
#+TBLFM: $3=$2-$1
Using org-evaluate-time-range is also possible, and you get a nice formatted output:
| <2015-10-02>--<2015-10-24> | 22 days |
| <2015-10-05>--<2015-10-20> | 15 days |
| <2015-10-22 Thu 21:08>--<2015-08-01> | 82 days 21 hours 8 minutes |
#+TBLFM: $2='(org-evaluate-time-range)
Note that the only optional argument that org-evaluate-time-range accepts is a flag to indicate insertion of the result in the current buffer, which you don't want.
Now, how does this function (without arguments) get the correct time range when evaluated is a complete mystery to me; pure magic(!)

Org mode spreadsheet programmatic remote references

I keep my budget in org-mode and have been pleased with how simple it is. The simplicity fails, however, as I am performing formulas on many cells; for instance, my year summary table that performs the same grab-and-calculate formulas for each month. I end up with a massive line in my +TBLFM. This would be dramatically shorter if I could programmatically pass arguments to the formula. I'm looking for something like this, but working:
| SEPT |
| #ERROR |
#+TBLFM: #2$1=remote(#1,$tf)
Elsewhere I have a table named SEPT and it has field named "tf". This function works if I replace "#1" with "SEPT" but this would cause me to need a new entry in the formula for every column.
Is there a way to get this working, where the table itself can specify what remote table to call (such as the SEPT in my example)?
Yes, you can't do this with built-in remote and you need to use org-table-get-remote-range. Hopefully this better suits your needs than the answer given by artscan (I used his/her example):
| testname1 | testname2 |
|-----------+-----------|
| 1 | 2 |
#+TBLFM: #2='(org-table-get-remote-range #<$0 (string ?# ?1 ?$ ?1))
#+TBLNAME: testname1
| 1 |
#+TBLNAME: testname2
| 2 |
Note the (string ?# ?1 ?$ ?1): this is necessary because before evaluating table formulae, all substitutions will be done first. If you use "#1$1" directly, it would have triggered the substitution mechanism and be substituted by the contents of the first cell in this table.
There is some ugly hack for same effect without using remote:
1) it needs named variable for remote address
(setq eab/test-remote "#1$1")
2) it uses elisp expression (from org-table.el) instead remote(tablename,#1$1)
(defun eab/test-remote (x)
`(car (read
(org-table-make-reference
(org-table-get-remote-range ,x eab/test-remote)
't 't nil))))
3) worked example
| testname1 | testname2 |
|-----------+-----------|
| | |
#+TBLFM: #2='(eval (eab/test-remote #1))
#+TBLNAME: testname1
| 1 |
#+TBLNAME: testname2
| 2 |
4) result
| testname1 | testname2 |
|-----------+-----------|
| 1 | 2 |

Emacs org-mode totals table

I have an org file, describing a project:
* task1
** task1-1
:PROPERTIES:
:price: 10
:given: <2012-11-08 Thu>
:END:
** task1-2
:PROPERTIES:
:price: 11
:given: <2012-11-08 Thu>
:END:
* task2
** task2-1
:PROPERTIES:
:price: 20
:given: <2012-11-08 Thu>
:END:
** task2-2
:PROPERTIES:
:price: 21
:given: <2012-11-08 Thu>
:END:
I used org-collector to produce a totals table:
#+BEGIN: propview :id global :conds ((not (= price 0))) :cols (ITEM price)
| ITEM | price |
|-----------+-------|
| "task1-1" | 10 |
| "task1-2" | 11 |
| "task2-1" | 20 |
| "task2-2" | 21 |
|-----------+-------|
| | 62 |
#+TBLFM: #6$2=vsum(#2$2..#5$2)
#+END:
But I want to have something like this:
| ITEM | price |
|-----------+-------|
| "task1-1" | 10 |
| "task1-2" | 11 |
| "task2-1" | 20 |
| "task2-2" | 21 |
|-----------+-------|
| Total | 62 |
How to do it?
For the “Total” line, you can add a line |Total| |, press C-u C-c = in the empty cell (to define a formula for it), and enter the formula vsum(#1$2..#4$2). (If you wanna recalc, that's C-u C-c C-c for all.)
I don't know about org-collector, so can't help you with this part. Run it on the entire document (is :id global working?), or shift everything by one level to the right for it to be inside a single tree, maybe.
I just figured this one out this morning. The answer you're looking for with Org-Collector is straightforward. Just put the table formulae two blank lines beneath the beginning of the property view and it'll be calculated automatically when the property view is evaluated (with C-c C-c).
#+BEGIN: propview :scope tree :cols (ITEM CLOCKSUM EFFORT) :match "TODO=\"TODO\"" :noquote ALL
#+TBLFM: #>$1=Totals::#>$2=vsum(#I..#II);t::#>$3=vsum(#I..#II);t
#+END:
If you want to turn this into a generic yasnippet, you need to escape the $ and the \:
#+BEGIN: propview :scope tree :cols (ITEM CLOCKSUM EFFORT) :match "TODO=\\"TODO\\"" :noquote ALL
#+TBLFM: #>\$1=Totals::#>\$2=vsum(#I..#II);t::#>\$3=vsum(#I..#II);t
#+END:
Now, you can add a generic property view that calculates the time so far and effort remaining for each step of a project, as well as the total time overall.