this was taken from this Github repo with the author's approval:
https://github.com/jwiegley/org2tc
this is very useful to convert org-mode clock entries into the more
easily parseable timeclock.el format, a fundamental step in automating
billing with org-mode.
---
contrib/scripts/org2tc | 150 +
1 file changed, 150 insertions(+)
create mode 100755 contrib/scripts/org2tc
diff --git a/contrib/scripts/org2tc b/contrib/scripts/org2tc
new file mode 100755
index 0..9ff6422d7
--- /dev/null
+++ b/contrib/scripts/org2tc
@@ -0,0 +1,150 @@
+#!/usr/bin/python
+
+'''Take an org-mode file as input and print a timeclock file as
+output. This can then be read directly by Ledger for fancy time
+reporting and querying. Fields :BILLCODE: and :TASKCODE: are parsed to
+generate lines compatible with the format expected by ledger
+("billcode taskcode").
+
+See also
http://ledger-cli.org/2.6/ledger.html#Using-timeclock-to-record-billable-time
+
+© 2011-2016 John Wiegly
+© 2016-2017 Antoine Beaupré
+'''
+
+
+from __future__ import print_function
+
+import argparse
+import locale
+locale.setlocale(locale.LC_ALL, '')
+import sys
+import re
+import time
+
+iso_date_fmt = "%Y-%m-%d %H:%M:%S"
+
+def parse_org_time(s):
+return time.strptime(s, "%Y-%m-%d %a %H:%M")
+
+def parse_timestamp(s):
+return time.strptime(s, iso_date_fmt)
+
+events = []
+last_heading = None
+clocks = []
+
+parser = argparse.ArgumentParser(description='convert org clocks into
timeclock',
+ epilog=__doc__ +
+ '''Note that TIME is provided in the
following format: %s'''
+ % iso_date_fmt)
+parser.add_argument('orgfile', help='Org file to process')
+parser.add_argument('-s', '--start', metavar='TIME', help='process only
entries from this date')
+parser.add_argument('-e', '--end', metavar='TIME', help='process only entries
to this date')
+parser.add_argument('-r', '--regex', help='process only entries matching this
regex')
+parser.add_argument('-o', '--output', help='output file (default: stdout)',
+type=argparse.FileType('w'), default=sys.stdout)
+args = parser.parse_args()
+
+data = args.orgfile
+range_start = parse_timestamp(args.start) if args.start else None
+range_end= parse_timestamp(args.end) if args.end else None
+regex= args.regex
+fd = open(data, "r")
+headings = [None] * 9
+acct = ""
+
+(billcode, taskcode) = ("", None)
+
+def add_events():
+# XXX: those globals should really be cleaned up, maybe through a clock
object or named tuple
+global acct, clocks, billcode, taskcode, events, todo_keyword, last_heading
+if clocks:
+for (clock_in, clock_out, billcode, taskcode) in clocks:
+if billcode and ":" not in billcode and taskcode:
+acct = "%s:%s" % (billcode, taskcode)
+events.append((clock_in, clock_out, todo_keyword,
+ ("%s %s" % (acct, last_heading))
+ if acct else last_heading))
+clocks = []
+
+for line in fd:
+match = re.search("^(\*+)\s*(.+)", line)
+if match:
+depth = len(match.group(1))
+headings[depth] = match.group(2)
+
+depth = 0
+match = re.search("^(\*+)\s+(TODO|DONE)?(\s+\[#[ABC]\])?\s*(.+)", line)
+if match:
+add_events()
+
+depth = len(match.group(1))
+todo_keyword = match.group(2)
+last_heading = match.group(4)
+match = re.search("(.+?)\s+:\S+:$", last_heading)
+if match:
+last_heading = match.group(1)
+match = re.search("\[\[.*\]\]\s+(.+?)$", last_heading)
+if match:
+last_heading = match.group(1)
+
+headings[depth] = last_heading
+
+i = 0
+prefix = ""
+while i < depth:
+if prefix:
+prefix += ":" + headings[i]
+else:
+prefix = headings[i]
+i += 1
+
+if prefix:
+#last_heading = prefix + " " + last_heading
+last_heading = prefix + ":" + last_heading
+
+if regex and not (prefix and re.search(regex, prefix)):
+last_heading = None
+
+if last_heading:
+match = re.search("CLOCK:\s+\[(.+?)\](--\[(.+?)\])?", line)
+if match:
+clock_in = parse_org_time(match.group(1))
+clock_out = match.group(3) # optional
+if clock_out:
+clock_out = parse_org_time(clock_out)
+else:
+#clock_out = time.localtime()
+clock_out = None
+if (not range_start or clock_in >= range_start) and \
+ (not range_end or clock_in < range_end):
+ clocks.append((clock_in, clock_out, billcode, taskcode))
+elif clock_in < range_start and clock_out > range_start:
+ clocks.app