Plug-In: Task Date Controls
This plug-in provides the following actions for changing defer and/or due dates on selected tasks.
(AUTHOR: Christian Y. · Omni Support Human)- Clear Defer & Due
- Set New Dates
- Clear Defer Date
- Defer Tomorrow
- Defer +1 Hour
- Defer +1 Day
- Defer +1 Week
- Defer +1 Month
- Defer +1 Year
- Clear Due Date
- Due Today
- Due +1 Hour
- Due +1 Day
- Due +1 Week
- Due +1 Month
- Due +1 Year
Several of the +1 actions will appear to overlap with functionality already provide in the Dates inspector, but they function differently. Even when using these +1 date actions with multiple actions selected, the date for each task will be adjusted relative to its existing date. Additionally, when using Defer Tomorrow or Due Today on tasks that have an existing defer or due date, the time of day for the existing date will be preserved.
The Set New Dates action then provides the ability to apply the same defer and/or due to a selection of multiple tasks. This too will seem similar to just using the Date inspector, but again there are some key differences. One is that you can choose to only update the date on tasks, while preserving the time of day. The other is it allows you the option to update the defer date on a task, and automatically push out the due date to keep the same relative gap between the defer date and due date.
Installation
You can download the plug-in to disk (Download Plug-In), or begin the OmniFocus plug-in onboarding process (Install Plug-In)
Using the Plug-In
There are four different ways to use the actions provided by this plug-in (available when at least one task is selected):
- macOS & iOS: Assigning keyboard shortcuts to the individual actions.
- macOS: Customize your toolbar, and add buttons for the actions you want quick access to. Each plug-in action has its own icon so they can all be distinguished.
- macOS: Using the Date Controls sub-menu that will be accessible from the Automation menu in the menu bar.
- iOS: Use the Share sheet.
Return to: OmniFocus Plug-In Collection
Plug-In Scripts
Shown here are three of the scripts contained in this plug-in.
First, is the bundle plug-in library file that is shared by all the other scripts for the individual actions.
Plug-In's DateLib Library
(() => {
let DateLib = new PlugIn.Library(new Version("1.0"));
DateLib.getSelection = () => document.windows[0].selection
DateLib.today = Calendar.current.startOfDay(new Date())
DateLib.getTimeComponentsFromString = function(timeString) {
let placeholderDate = new Date("1/1/1 " + timeString)
let defaultTimeComponents = new DateComponents()
defaultTimeComponents.hour = placeholderDate.getHours()
defaultTimeComponents.minute = placeholderDate.getMinutes()
return defaultTimeComponents
}
DateLib.getDefaultDeferTimeComponents = function() {
return this.getTimeComponentsFromString(settings.objectForKey("DefaultStartTime"))
}
DateLib.getDefaultDueTimeComponents = function() {
return this.getTimeComponentsFromString(settings.objectForKey("DefaultDueTime"))
}
DateLib.incrementDate = function(dateType, dateIncrementer) {
let sel = this.getSelection()
let cal = Calendar.current
let defaultTimeComponents
sel.databaseObjects.forEach(obj => {
if (obj instanceof Task || obj instanceof Project){
let oldDate
let newDate
if (dateType == "due") {
oldDate = obj.dueDate
defaultTimeComponents = this.getDefaultDueTimeComponents()
}
else if (dateType == "defer") {
oldDate = obj.deferDate
defaultTimeComponents = this.getDefaultDeferTimeComponents()
}
// Apply a new date to items that don't have an existing date
if (!oldDate) {
if (dateIncrementer.hour) {
oldDate = new Date()
}
else {
oldDate = cal.dateByAddingDateComponents(new Date(this.today), defaultTimeComponents)
}
}
// Set due date to today or defer date to tomorrow
if (dateIncrementer.day === 0) {
let oldHour = oldDate.getHours()
let oldMinutes = oldDate.getMinutes()
// Reset old date to today, but preserve the time
oldDate = new Date(this.today)
oldDate.setHours(oldHour)
oldDate.setMinutes(oldMinutes)
if (dateType == "defer") {dateIncrementer.day = 1}
}
newDate = cal.dateByAddingDateComponents(oldDate, dateIncrementer)
if (dateType == "due") obj.dueDate = newDate
if (dateType == "defer") obj.deferDate = newDate
}
})
}
DateLib.clearDate = function(dateField){
let sel = this.getSelection()
sel.databaseObjects.forEach(obj => {
if (obj instanceof Task || obj instanceof Project){
if (obj.task != undefined) {obj = obj.task} // workaround for bug setting dueDate to null on Project objects
if (dateField.includes("due")) {obj.dueDate = null}
if (dateField.includes("defer")) {obj.deferDate = null}
}
})
}
return DateLib;
})();
(() => {
let action = new PlugIn.Action(function(selection, sender){
let dc = new DateComponents(); dc.day = 1
this.DateLib.incrementDate("defer", dc)
})
action.validate = function(selection, sender){
return (selection.projects.length > 0 || selection.tasks.length > 0)
};
return action;
})();
Then, there is the Due +1 Day script, which shows the basic structure used by all the other action scripts, except for Set New Dates, which is the last script shown. The Set New Dates script is largely self-contained though it does use the plug-ins library file in a couple of spots.
Due +1 Day
(() => {
let action = new PlugIn.Action(function(selection, sender){
let dc = new DateComponents(); dc.day = 1
this.DateLib.incrementDate("due", dc)
})
action.validate = function(selection, sender){
return (selection.projects.length > 0 || selection.tasks.length > 0)
};
return action;
})();
Set New Dates
(() => {
let action = new PlugIn.Action(function(selection) {
// Add code to run when the action is invoked
let cal = Calendar.current
let DateLib = this.DateLib
let optionInputField = new Form.Field.Option(
"dateOption",
"Dates to update",
[0,1,2],
["Defer Only", "Due Only", "Defer & Due"],
1
)
let deferInputField = function() {
let form = new Form.Field.Date(
"deferInput",
"Defer",
new Date(),
Formatter.Date.withFormat("MM/dd/yyyy")
)
return form
}
let deferInputWithTimeField = function() {
let form = new Form.Field.Date(
"deferInputWithTime",
"Defer",
new Date(),
null
)
return form
}
let dueInputField = function() {
let form = new Form.Field.Date(
"dueInput",
"Due",
new Date(),
Formatter.Date.withFormat("MM/dd/yyyy")
)
return form
}
let dueInputWithTimeField = function() {
let form = new Form.Field.Date(
"dueInputWithTime",
"Due",
new Date(),
null
)
return form
}
let pushDueCheckbox = function() {
let form = new Form.Field.Checkbox(
"pushDue",
"Push out due date relative to new defer date"
)
return form
}
let includeTimeCheckbox = new Form.Field.Checkbox(
"includeTime",
"Adjust time on dates"
)
let inputForm = new Form()
let formPrompt = "Batch Update Dates:"
let buttonTitle = "Continue"
inputForm.addField(optionInputField)
inputForm.addField(dueInputField())
inputForm.addField(includeTimeCheckbox)
let formPromise = inputForm.show(formPrompt, buttonTitle)
inputForm.validate = function(formObject) {
let dateOption = formObject.values["dateOption"]
let includeTime = formObject.values["includeTime"]
let deferInput = formObject.values["deferInput"]
let deferInputWithTime = formObject.values["deferInputWithTime"]
let dueInput = formObject.values["dueInput"]
let dueInputWithTime = formObject.values["dueInputWithTime"]
let dateFilled
// Set up the form for "Defer Only"
if (dateOption == 0) {
// Remove the second date field when switching from "Defer & Due"
if (inputForm.fields[2]["key"].includes("due")) {
inputForm.removeField(inputForm.fields[2])
}
if (inputForm.fields[2]["key"] != "pushDue") {
inputForm.addField(pushDueCheckbox(), 2)
}
if (!includeTime) {
if (inputForm.fields[1]["key"] != "deferInput") {
inputForm.removeField(inputForm.fields[1])
inputForm.addField(deferInputField(), 1)
}
dateFilled = deferInput
}
else if (includeTime) {
if (inputForm.fields[1]["key"] != "deferInputWithTime") {
inputForm.removeField(inputForm.fields[1])
inputForm.addField(deferInputWithTimeField(), 1)
}
dateFilled = deferInputWithTime
}
}
// Set up the form for "Due Only"
else if (dateOption == 1) {
if (inputForm.fields.length == 4) {
// Remove the extra form field when switching from the other dateOptions
inputForm.removeField(inputForm.fields[2])
}
if (!includeTime) {
if (inputForm.fields[1]["key"] != "dueInput") {
inputForm.removeField(inputForm.fields[1])
inputForm.addField(dueInputField(), 1)
}
dateFilled = dueInput
}
else if (includeTime) {
if (inputForm.fields[1]["key"] != "dueInputWithTime") {
inputForm.removeField(inputForm.fields[1])
inputForm.addField(dueInputWithTimeField(), 1)
}
dateFilled = dueInputWithTime
}
}
// Set up the form for "Defer & Due"
else if (dateOption == 2) {
if (inputForm.fields.length == 3) {
inputForm.addField(deferInputField(), 1)
}
if (!includeTime) {
if (inputForm.fields[1]["key"] != "deferInput"
|| inputForm.fields[2]["key"] != "dueInput") {
inputForm.removeField(inputForm.fields[1])
inputForm.addField(deferInputField(), 1)
inputForm.removeField(inputForm.fields[2])
inputForm.addField(dueInputField(), 2)
}
dateFilled = (deferInput && dueInput)
}
else if (includeTime) {
if (inputForm.fields[1]["key"] != "deferInputWithTime"
|| inputForm.fields[2]["key"] != "dueInputWithTime") {
inputForm.removeField(inputForm.fields[1])
inputForm.addField(deferInputWithTimeField(), 1)
inputForm.removeField(inputForm.fields[2])
inputForm.addField(dueInputWithTimeField(), 2)
}
dateFilled = (deferInputWithTime && dueInputWithTime)
}
}
// Make sure all date fields (per the selected dateOption) are filled in
if (dateFilled){return true}
}
formPromise.then(function(formObject) {
let dateOption = formObject.values["dateOption"]
let updateDateOnly = !formObject.values["includeTime"]
let pushDue = formObject.values["pushDue"]
let newDeferDate
let newDueDate
// sel = document.windows[0].selection
selection.databaseObjects.forEach(function(obj) {
if (obj instanceof Task || obj instanceof Project) {
let oldDeferDate = obj.deferDate
let oldDueDate = obj.dueDate
// Defer Only
if (dateOption == 0 || dateOption == 2) {
if (updateDateOnly) {
newDeferDate = formObject.values["deferInput"]
if (oldDeferDate) {
newDeferDate.setHours(oldDeferDate.getHours())
newDeferDate.setMinutes(oldDeferDate.getMinutes())
}
else {
let defaultDeferDate = DateLib.getDefaultDeferTimeComponents()
newDeferDate.setHours(defaultDeferDate.hour)
newDeferDate.setMinutes(defaultDeferDate.minute)
}
}
else {
newDeferDate = formObject.values["deferInputWithTime"]
}
obj.deferDate = newDeferDate
// Push out due date relative to new defer date
if (pushDue) {
let dateDiff = cal.dateComponentsBetweenDates(oldDeferDate, oldDueDate)
newDueDate = cal.dateByAddingDateComponents(newDeferDate, dateDiff)
obj.dueDate = newDueDate
}
}
// Due Only
if (dateOption == 1 || dateOption == 2) {
if (updateDateOnly) {
newDueDate = formObject.values["dueInput"]
if (oldDueDate) {
newDueDate.setHours(oldDueDate.getHours())
newDueDate.setMinutes(oldDueDate.getMinutes())
}
else {
let defaultDueDate = DateLib.getDefaultDueTimeComponents()
newDueDate.setHours(defaultDueDate.hour)
newDueDate.setMinutes(defaultDueDate.minute)
}
}
else {
newDueDate = formObject.values["dueInputWithTime"]
}
obj.dueDate = newDueDate
}
}
})
})
});
action.validate = function(selection, sender) {
return (selection.projects.length > 0 || selection.tasks.length > 0)
};
return action;
})();