Time Tracking Using Plain Text and Drafts - Part 4 (More Processing)

To recap: we’ve turned our plain text time slip into a list of time entries, each with a date, duration, client, project, note, billing number, and billable/non-billable flag. But now we need to clean up the data.

First, we need a function to combine entries that have the same client and project (like the entries for ABC Corp. in our example time slip).

function combineEntries(dictList) {
	let result = [];
	result[0] = dictList[0];
	for (i = 1; i < dictList.length; i++) {		let matched = false;
		for (e in result) {
			if (result[e].number == dictList[i].number && result[e].project == dictList[i].project) {
				result[e].duration = Number(result[e].duration) + Number(dictList[i].duration);
				result[e].duration = result[e].duration.toFixed(1);
				result[e].note += " " + dictList[i].note;
				matched = true;
		if (!matched) {
	return result;

(I also have a second, simpler function—called combineEntriesForEmail—that combines entries for the same client even if they don’t have the same project. I’ll include it in the final post, but won’t bother adding it here.)

To do this initial combining of entires, I just call this function on the entries list that we created in the last pose:

entries = combineEntries(entries);

Second, we’ll eliminate non-trackable entries (like lunch in the example) by filtering those out:

entries = entries.filter(x => x.number != "0");

And finally, we’ll sort the data so that billable client work comes before non-billable administrative work:

entries = entries.sort((a,b) => b.billable - a.billable);

Now that we have the data cleaned up, the next step is to make some useful strings to send elsewhere. I send my data to three primary destinations: (1) A pipe-delimited file akin to a .csv; (2) a true .csv file (so that Microsoft BI can read it without additional interference); and (3) an email to my assistant. Here’s how those are compiled:

// build pipe-delimited text file addition
var txtAddition = [];
for (e in entries) {
	txtAddition.push(entries[e].date + "|" + entries[e].duration + "|" + entries[e].client + "|" + entries[e].project + "|" + entries[e].note + "|" + entries[e].billable);

// build .csv addition
var csvAddition = [];
for (e in entries) {
	csvAddition.push(entries[e].date + ',' + entries[e].duration + ',' + entries[e].client + ',' + entries[e].project + ',"' + entries[e].note.replace(/"/g,'""') + '",' + entries[e].billable);

// consolidate entries for email and build email contents
entries = combineEntriesForEmail(entries);
var emailContents = '';
for (e in entries) {
	emailContents += entries[e].duration + "\n" + entries[e].client + " (" + entries[e].number + ")\n" + entries[e].note + "\n\n"

// add a total for the day
var dayTotal = 0;
for (e in entries) {
	dayTotal += Number(entries[e].duration);
dayTotal = dayTotal.toFixed(1);
emailContents += "----\n" + dayTotal + " total";

Finally, now that I have the strings I want, I define Drafts template tags so they can be used in subsequent action steps without all the coding.

draft.setTemplateTag('slipDate', date);
draft.setTemplateTag('emailContents', emailContents);
draft.setTemplateTag('txtAddition', txtAddition.join('\n'));
draft.setTemplateTag('csvAddition', csvAddition.join('\n'));

This lets me use an email step and get the contents for the email just by using a [[emailContents]] tag. For our sample time slip, the email contents would look like this:

ABC Corp. (1111-1)
Review lease. Review and respond to additional email question form Mr. ABC.

XYZ Co. (2222-2)
Prepare for oral argument of upcoming motion to dismiss.

Jones (3333-3)
Interview Ms. Jones regarding dispute with business partners. Prepare memo outlining potential claims.

7.6 total

Same goes for appending to cloud files—just use [[txtAddition]] or [[csvAddition]] as appropriate.

That’s really all there is to processing the time. In the next post, I’ll explain that getClient function I mentioned before. For me, getting the getClient function turned my system from somewhat fiddly and brittle, so one that feels rock solid and doesn’t require maintenance.

Ciaran Connelly @ciaran