Importing/Exporting Calendar Events as Time Entries into Timing

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; if you need details on how a particular method works, please refer to our AppleScript reference.

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.

Table of Contents

Importing all Events from a Specific Calendar in the Past 7 Days as Timing Time Entries in a Specific Project

Timing now natively supports showing events from your Mac's calendar on the timeline. We strongly suggest using that functionality instead of these scripts.

This script has been written for Apple's Calendar.app.
If you are using Google Calendar, simply subscribe to your Google Calendar with Calendar.app.
If you are using Microsoft Outlook, please use these scripts instead.

Also, please note that technical limitations in Calendar's AppleScript interface might make it impossible to import recurring events with this script.

// Copyright (c) 2024 Timing Software GmbH. 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.advancedScriptingSupportAvailable()) { throw "This script requires a Timing Connect subscription. Please contact support via https://timingapp.com/contact to upgrade."; }
var app = Application.currentApplication();
app.includeStandardAdditions = true;
var calendarApp = Application("Calendar");

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 endDate = new Date();
var startDate = new Date(endDate);
startDate.setDate(startDate.getDate() - 7 /* days */);

// Please replace "CalendarName-CHANGEME" with the name of your calendar in quotation marks, e.g. "Work".
// Please replace "ProjectName-CHANGEME" with the name of the corresponding Timing project in quotation marks, e.g. "Client XYZ".
addEventsFromCalendarToProject("CalendarName-CHANGEME", "ProjectName-CHANGEME");

function addEventsFromCalendarToProject(calendarName, projectName) {
	var project = helper.projects.whose({ name: projectName })()[0];
	if (!project) {
		app.displayAlert('Could not find a project with name "' + projectName + '".\n\nPlease make sure a project with that name existis in Timing, or change the project name in the script.');
		return;
	}
	
	var currentCalendar = calendarApp.calendars.whose({ name: calendarName })[0];
	try {
		currentCalendar.get();
	} catch (e) {
		app.displayAlert('Could not find a calendar with name "' + calendarName + '".\n\nPlease make sure a calendar with that name existis in Calendar.app, or change the calendar name in the script.');
		return;
	}
	
	var events = currentCalendar.events.whose({
		_and: [
			{ startDate: { _greaterThan: startDate } },
			{ endDate: { _lessThan: endDate } }
		]
	})({ timeout: 600 });

	var taskCount = 0;
	for (var event of events) {
		if (event.alldayEvent()) continue;

		var eventStartDate = event.startDate();
		var eventEndDate = event.endDate();
		if ((eventEndDate - eventStartDate) / 1000 >= 23 * 60 * 60) continue;
		if (eventEndDate - eventStartDate < 1000) continue; 
		var arguments = { withTitle: event.summary(), from: eventStartDate, to: eventEndDate, project: project };
		helper.addTimeEntry(arguments);
		taskCount += 1;
	}
	"Imported " + taskCount + " calendar events from calendar '" + calendarName + "' to project '" + projectName + "'.";
}

Exporting all Time Entries from a Specific Project in the Past 7 Days to a Specific Calendar

This script has been written for Apple's Calendar.app.
If you are using Google Calendar, simply subscribe to your Google Calendar with Calendar.app.
If you are using Microsoft Outlook, please use these scripts instead.

We take no responsibility for any damages this script might cause to your calendar!

// Copyright (c) 2024 Timing Software GmbH. 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.advancedScriptingSupportAvailable()) { throw "This script requires a Timing Connect subscription. Please contact support via https://timingapp.com/contact to upgrade."; }
var app = Application.currentApplication();
app.includeStandardAdditions = true;
var calendarApp = Application("Calendar");

app.displayDialog("Before proceeding, please note that we take no responsibility for any damage caused to your calendars.\n\nAt worst, the script should only create a lot of unexpected calendar events, but again - no guarantees.");

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

// Please replace "CalendarName-CHANGEME" with the name of your calendar in quotation marks, e.g. "Work".
// Please replace "ProjectName-CHANGEME" with the name of the corresponding Timing project in quotation marks, e.g. "Client XYZ".
exportTasksFromProjectToCalendar("ProjectName-CHANGEME", "CalendarName-CHANGEME");


function exportTasksFromProjectToCalendar(projectName, calendarName) {
	var project = helper.projects.whose({ name: projectName })()[0];
	if (!project) {
		app.displayAlert('Could not find a project with name "' + projectName + '".\n\nPlease make sure a project with that name existis in Timing, or change the project name in the script.');
		return;
	}
	
	var currentCalendar = calendarApp.calendars.whose({ name: calendarName })[0];
	try {
		currentCalendar.get();
	} catch (e) {
		app.displayAlert('Could not find a calendar with name "' + calendarName + '".\n\nPlease make sure a calendar with that name existis in Calendar.app, or change the calendar name in the script.');
		return;
	}
	
	var taskListPath = $.NSTemporaryDirectory().js + "TimingTasks.csv";
	
$.NSFileManager.defaultManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError($(taskListPath).stringByDeletingLastPathComponent.stringByStandardizingPath, true, $(), $());

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

reportSettings.firstGroupingMode = "raw";
reportSettings.timeEntriesIncluded = 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), forProjects: helper.projects.whose({ name: projectName }) });

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


// 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");
  }; 
}
	
	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 taskDescription = csvFields[5];
		var taskNotes = csvFields[6];
	
		if (!taskStartDate || !taskEndDate) continue;
	
		var filterArguments = [
			{ startDate: { _equals: taskStartDate } },
			{ endDate: { _equals: taskEndDate } }
		];
		var createArguments = { startDate: taskStartDate, endDate: taskEndDate };
		if (taskDescription) {
			filterArguments.push({ summary: { _equals: taskDescription } });
			createArguments["summary"] = taskDescription;
		}
		if (taskNotes) {
			createArguments["description"] = taskNotes;
		}
	
		if (currentCalendar.events.whose({ _and: filterArguments })().length != 0) continue;
	
		var event = calendarApp.Event(createArguments);
		currentCalendar.events.push(event);
	}
}

Take our free 5-day course to get started with Timing.