Exporting and Importing Timing Tasks

Timing now offers a proper built-in sync solution, so in many cases that option is preferable. However, you can still use these scripts to export your tasks. For more information, see this article.

Simply copy one of the following script into a new "Script Editor" document, select "JavaScript" as the script's language, and press run (see this article for more detailed instructions).
After pasting them, consider saving the scripts to disk for future re-use. You could even save them to iCloud Drive (or e.g. Dropbox or Google Drive) to have them available on all your Macs.
Feel free to customize the scripts for your use cases.

Note: We do not take responsibility for any data loss incurred by running these scripts.
Make sure to back up your data (e.g. by copying the directories mentioned here to a different location) before running these scripts.

Exporting All Timing Tasks in the Past 30 Days to a CSV File

var endDate = new Date();
var startDate = new Date(endDate);
startDate.setDate(startDate.getDate() - 30 /* days */);

// Copyright (c) 2018 timingapp.com / Daniel Alm. All rights reserved.
// This script is licensed only to extend the functionality of Timing. Redistribution and any other uses are not allowed without prior permission from us.
var helper = Application("TimingHelper");
if (!helper.scriptingSupportAvailable()) { throw "Scripting support requires a Timing Expert license. Please contact support via https://timingapp.com/contact to upgrade."; }
var app = Application.currentApplication();
app.includeStandardAdditions = true;

var taskListPath = app.chooseFileName({ withPrompt: "Select which file to write the Tasks to.", defaultName: "TimingTasks.csv" }).toString();

$.NSFileManager.defaultManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError($(taskListPath).stringByDeletingLastPathComponent.stringByStandardizingPath, true, $(), $());

var reportSettings = helper.ReportSettings().make();
var exportSettings = helper.ExportSettings().make();

reportSettings.firstGroupingMode = "raw";
reportSettings.tasksIncluded = true;
reportSettings.appUsageIncluded = false;

exportSettings.fileFormat = "CSV";
exportSettings.durationFormat = "seconds";
exportSettings.shortEntriesIncluded = true;

var app = Application.currentApplication();
app.includeStandardAdditions = true;

helper.saveReport({ withReportSettings: reportSettings, exportSettings: exportSettings, between: startDate, and: endDate, to: Path($(taskListPath).stringByStandardizingPath.js) });

helper.delete(reportSettings);
helper.delete(exportSettings);

Importing Tasks from a File into Timing

// Copyright (c) 2018 timingapp.com / Daniel Alm. All rights reserved.
// This script is licensed only to extend the functionality of Timing. Redistribution and any other uses are not allowed without prior permission from us.
var helper = Application("TimingHelper");
if (!helper.scriptingSupportAvailable()) { throw "Scripting support requires a Timing Expert license. Please contact support via https://timingapp.com/contact to upgrade."; }
var app = Application.currentApplication();
app.includeStandardAdditions = true;

app.displayDialog("Before proceeding, please make sure to back up your Timing database.\n\nSee https://timingapp.com/help/faq#data on which folders you need to back up.");

var taskListPath = app.chooseFile({ withPrompt: "Select which CSV file to read the Tasks from.", ofType: ["public.comma-separated-values-text"] }).toString();

// Minimal ES6 CSV parser.
// Taken from: https://lowrey.me/parsing-a-csv-file-in-es6-javascript/
var Csv = null;
Csv = class {
  parseLine(text) {
    const regex =
    /(?!\s*$)\s*(?:'([^'\\]*(?:\\[\S\s][^'\\]*)*)'|"([^"\\]*(?:\\[\S\s][^"\\]*)*)"|([^,'"\s\\]*(?:\s+[^,'"\s\\]+)*))\s*(?:,|$)/g;
    let arr = [];
    text.replace(regex, (m0, m1, m2, m3) => {
      if (m1 !== undefined) {
        arr.push(m1.replace(/\\'/g, "'"));
      } else if (m2 !== undefined) {
        arr.push(m2.replace(/\\"/g, "\""));
      } else if (m3 !== undefined) {
        arr.push(m3);
      }
      return "";
    });
    if (/,\s*$/.test(text)) {
      arr.push("");
    }
    return arr;
  }

  zipObject(props, values) {
    return props.reduce((prev, prop, i) => {
      prev[prop] = values[i];
      return prev;
    }, {});
  }

  parse(csv) {
    let [properties, ...data] = csv.split("\n").map(this.parseLine);
    return data.map((line) => this.zipObject(properties, line))
  };

  serialize(obj) {
    let fields = Object.keys(obj[0]);
    let csv = obj.map(row => fields.map((fieldName) => JSON.stringify(row[fieldName] || "")));
    return [fields, ...csv].join("\n");
  }; 
}

function createProjectWithNameChainElements(projectNameChainElements) {
	var currentProject = null;
	var currentSearchSpace = helper.rootProjects();
	for (var projectName of projectNameChainElements) {
		if (!projectName) continue;
		
		var newProject = currentSearchSpace.filter(function(project) { return project.name() == projectName; })[0];
		if (!newProject) {
			var arguments = { name: projectName };
			if (currentProject) {
				arguments["parentProject"] = currentProject;
			}
			newProject = helper.createProject(arguments);
		}
		currentProject = newProject;
		currentSearchSpace = currentProject.projects();
	}
	return currentProject;
}

function createProjectWithNameChain(projectNameChain) {
	return createProjectWithNameChainElements(projectNameChain.split(" ▸ "));
}

var csvLines = $.NSString.stringWithContentsOfFileEncodingError(
	$(taskListPath).stringByStandardizingPath,
	$.NSUTF8StringEncoding,
	$()
).js.split("\n");

csvLines.shift();
var csv = new Csv();
for (var csvLine of csvLines) {
	if (!csvLine) continue;
	
	var csvFields = csv.parseLine(csvLine);
	var taskStartDate = new Date(csvFields[2]);
	var taskEndDate = new Date(csvFields[3]);
	var taskProjectNameChain = csvFields[4];
	var taskDescription = csvFields[5];
	var taskNotes = csvFields[6];
	
	if (!taskStartDate || !taskEndDate || (taskEndDate.getTime() - taskStartDate.getTime()) < 1000) continue;
	
	var arguments = { from: taskStartDate, to: taskEndDate };
	if (taskDescription) arguments["withTitle"] = taskDescription;
	if (taskProjectNameChain) {
		var taskProject = createProjectWithNameChain(taskProjectNameChain);
		if (taskProject) arguments["project"] = taskProject;
	}
	if (taskNotes) arguments["notes"] = taskNotes;
	helper.addTask(arguments);
}