Importing/Exporting Outlook 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.
In case Timing seems to use the wrong Outlook calendar for importing/exporting, you might have multiple calendars with the same name.
In that case, try replacing [0]
at the end of calendarApp.calendars.whose({ name: calendarName })[0]
with e.g. [1]
or [2]
to select a different calendar with the same name.
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 Microsoft Outlook.
If you are using Apple Calendar or Google Calendar, please use these scripts instead.
Also, please note that technical limitations in Outlook'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("Microsoft Outlook"); 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.calendarEvents.whose({ _and: [ { startTime: { _greaterThan: startDate } }, { endTime: { _lessThan: endDate } } ] })(); var taskCount = 0; for (var event of events) { if (event.allDayFlag()) continue; var eventStartDate = event.startTime(); var eventEndDate = event.endTime(); if ((eventEndDate - eventStartDate) / 1000 >= 23 * 60 * 60) continue; var arguments = { withTitle: event.subject(), 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 Outlook Calendar
This script has been written for Microsoft Outlook.
If you are using Apple Calendar or Google Calendar, 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("Microsoft Outlook"); 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]; if (!taskStartDate || !taskEndDate) continue; var filterArguments = [ { startTime: { _equals: taskStartDate } }, { endTime: { _equals: taskEndDate } } ]; var createArguments = { startTime: taskStartDate, endTime: taskEndDate }; if (taskDescription) { filterArguments.push({ subject: { _equals: taskDescription } }); createArguments["subject"] = taskDescription; } if (currentCalendar.calendarEvents.whose({ _and: filterArguments })().length != 0) continue; var event = calendarApp.CalendarEvent(createArguments); currentCalendar.calendarEvents.push(event); } }