What's wrong with this script for setting up Today's due date?


#1

Note: I’m not an author. I just removed the notifications. Yes, it wasn’t working before removing as well.

Thanks for figuring this out

--The following setting changes script mode. Options: "start" or "due" (quotes needed)
property mode : "start"


property startTime : 6 --Start hour for items not previously assigned a start time (24 hr clock)
property dueTime : 17 --Due hour for items not previously assigned a due time (24 hr clock)

-- Don't change these
property alertItemNum : ""
property alertDayNum : ""
property growlAppName : "Dan's Scripts"
property iconApplication : "OmniFocus.app"

on main()
	tell application "OmniFocus"
		tell content of first document window of front document
			--Get selection
			set totalMinutes to 0
			set validSelectedItemsList to value of (selected trees where class of its value is not item and class of its value is not folder)
			set totalItems to count of validSelectedItemsList
			if totalItems is 0 then
				set alertName to "Error"
				set alertTitle to "Script failure"
				set alertText to "No valid task(s) selected"
				return
			end if
			
			--Perform action
			set successTot to 0
			set autosave to false
			set currDate to (current date) - (time of (current date))
			if mode is "start" then
				repeat with thisItem in validSelectedItemsList
					set succeeded to my startToday(thisItem, currDate)
					if succeeded then set successTot to successTot + 1
				end repeat
			else if mode is "due" then
				repeat with thisItem in validSelectedItemsList
					set succeeded to my dueToday(thisItem, currDate)
					if succeeded then set successTot to successTot + 1
				end repeat
			else
				set alertName to "Error"
				set alertTitle to "Script failure"
				set alertText to "Improper mode setting"
			end if
			set autosave to true
		end tell
	end tell
	
end main

on startToday(selectedItem, currDate)
	set success to false
	tell application "OmniFocus"
		try
			set originalStartDateTime to defer date of selectedItem
			if (originalStartDateTime is not missing value) then
				--Set new start date with original start time
				set defer date of selectedItem to (currDate + (time of originalStartDateTime))
				set success to true
			else
				set defer date of selectedItem to (currDate + (startTime * hours))
				set success to true
			end if
		end try
	end tell
	return success
end startToday

on dueToday(selectedItem, currDate)
	set success to false
	tell application "OmniFocus"
		try
			set originalDueDateTime to due date of selectedItem
			if (originalDueDateTime is not missing value) then
				--Set new due date with original due time
				set originalDueStartDate to originalDueDateTime - (time of originalDueDateTime)
				set theDelta to (currDate - originalDueStartDate) / 86400
				set newDueDateTime to (originalDueDateTime + (theDelta * days))
				set due date of selectedItem to newDueDateTime
				set originalStartDateTime to defer date of selectedItem
				if (originalStartDateTime is not missing value) then
					set newStartDateTime to (originalStartDateTime + (theDelta * days))
					set defer date of selectedItem to newStartDateTime
				end if
				set success to true
			else
				set due date of selectedItem to (currDate + (dueTime * hours))
				set success to true
			end if
		end try
	end tell
	return success
end dueToday


main()

#2

I’d recommend this script instead. Some of Curt Clifton’s have been deprecated with OF1. Chris Suave built upon it and gives you the ability to flag tasks operated on by the script, the ability to select either start or due for the selected task, and provides helpful dialogs that walk you through. I am sure you could speed things up by removing some of the dialogs if you have a default path that you want the script to follow.

To accomplish what you desire I simply select a task and choose the DUE path and enter a time today like 3p.

(*
	LATER.SCPT
	By Chris Sauve of [pxldot](http://pxldot.com).
	See README for details.
*)


property usesFlagsForScheduling : true -- true or false, true flags any tasks that the script runs on
property methodForScheduling : "Start" -- Options are "Start" or "Due"
property useGrowlForAlerts : false -- true or false
property promptForUserOptions : true -- will change after first run
property timesUsedSinceError : 0

-- Get the selection FIRST and then exit if nothing is selected.
tell application "OmniFocus"
	tell content of first document window of front document
		set tasksSelected to value of (selected trees where (class of its value is not item) and (class of its value is not folder))
		if length of tasksSelected is 0 then
			display alert "You didn't select any OmniFocus tasks."
			return
		end if
	end tell
end tell

if promptForUserOptions then
	display dialog ¬
		"Would you like to automatically flag the tasks on which you run this script?" buttons {"No, Don't Flag", "Yes, Use Flags"} default button 1
	set decisionOnFlags to button returned of result
	if decisionOnFlags is "No, Don't Flag" then set usesFlagsForScheduling to false
	
	display dialog "Would you like to use Due or Start dates for scheduling?" buttons {"Due", "Start"} default button 2
	set methodForScheduling to button returned of result
end if

try
	set inputDialog to "To when would you like to defer this task?"
	if timesUsedSinceError = 0 then set inputDialog to (inputDialog & " Note: you can use relative days (i.e., \"1w 4d 2pm\"), absolute dates (i.e., \"Jan 19 14:00\") or weekdays (i.e., \"Sat 5pm\"), just  as in OmniFocus.")
	display dialog ¬
		inputDialog default answer "1d 12am"
	set timeDeferred to text returned of result
on error errorText number errorNumber
	if errorNumber is -128 then
		return
	end if
end try

-- Escape to manually choose start and due dates, regardless of the default (start with the input with that word)
set escapeToStart to (timeDeferred starts with "start")
set escapeToDue to (timeDeferred starts with "due")
set escapeToBoth to (timeDeferred contains "start") and (timeDeferred contains "due")
set startFirst to true

if escapeToBoth then
	set startFirst to (offset of "start" in timeDeferred) < (offset of "due" in timeDeferred)
	set my text item delimiters to {"start ", "start", "due ", "due"}
	set adjustedInput to every text item of timeDeferred
	set fixedInput to {}
	repeat with i from 1 to (length of adjustedInput)
		if not ((item i of adjustedInput is "") or (item i of adjustedInput is in "                ")) then
			set the end of fixedInput to item i of adjustedInput
		end if
	end repeat
	if length of fixedInput is not 2 then return
	set desiredDate1 to getDate(item 1 of fixedInput)
	set desiredDate2 to getDate(item 2 of fixedInput)
	if (desiredDate2 is -1) or (desiredDate1 is -1) then return
else
	set desiredDate1 to getDate(timeDeferred)
	if desiredDate1 is -1 then return
end if

-- Do the actual work of setting the date and the flag, if necessary
tell application "OmniFocus"
	repeat with i from 1 to (length of tasksSelected)
		tell item i of tasksSelected
			if escapeToBoth then
				if startFirst then
					set defer date of it to desiredDate1
					set due date of it to desiredDate2
				else
					set due date of it to desiredDate1
					set defer date of it to desiredDate2
				end if
			else if ((methodForScheduling is "Start") and not escapeToDue) or escapeToStart then
				set defer date of it to desiredDate1
			else
				set due date of it to desiredDate1
			end if
			if usesFlagsForScheduling then set its flagged to true
		end tell
	end repeat
end tell


--//////// Understanding the date and time given in plain english ////////--

on englishTime(dateDesired)
	if dateDesired is "0" then return 0
	
	set monthFound to 0
	set weekdayFound to 0
	-- Solves an issue with the treatment of leading zeros for the minutes (i.e., 12:01am)
	set minuteLeadingZero to false
	
	-- Figures out if the user excluded any of the components
	set timeMissing to false
	set daysMissing to false
	set weeksMissing to false
	
	-- Sets up the delimiters for different items
	set timeDelimiters to {"am", "pm", "a", "p", ":"}
	set dayDelimiters to {"days", "day", "d"}
	set weekDelimiters to {"weeks", "week", "w"}
	set monthDelimiters to {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
	set weekdayDelimiters to {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
	set specialRelativeDayDelimiters to {"Today", "Tomorrow", "at"}
	set otherDelimiters to {" ", "th", "st", "rd", "nd", "start", "due", "both"}
	
	set inThe to "unknown"
	set howManyNumbersInputted to 0
	set numList to {}
	
	-- See if they included AM/PM
	if isNumberIdentifier("a", dateDesired) then set inThe to "AM"
	if isNumberIdentifier("p", dateDesired) then set inThe to "PM"
	
	-- See if they gave an absolute date formatted in YY.MM.DD or some other similar format
	set my text item delimiters to specialRelativeDayDelimiters & otherDelimiters & timeDelimiters
	set checkInput to every text item of dateDesired
	set checkInputCleaned to {}
	repeat with i from 1 to (length of checkInput)
		if item i of checkInput is not "" then
			set the end of checkInputCleaned to item i of checkInput
		end if
	end repeat
	set theDateCheck to item 1 of checkInputCleaned
	if (theDateCheck contains ".") or (theDateCheck contains "-") or (theDateCheck contains "/") then
		set todaysDate to (current date)
		set time of todaysDate to 0
		set targetDate to my understandAbsoluteDate(theDateCheck)
		if targetDate = -1 then return -1
		set my text item delimiters to ""
		if length of checkInputCleaned is 1 then
			return (targetDate - todaysDate) as number
		else
			set theTime to items 2 thru -1 of checkInputCleaned
			set numList to {}
			
			set timeStoreLocation to length of theTime
			repeat while timeStoreLocation > 0
				try
					-- If the minutes have a leading zero, just combine them with the hours
					if (numList = {}) and ((item timeStoreLocation of theTime) starts with "0") then
						set the end of numList to ((item (timeStoreLocation - 1) of theTime) & (item timeStoreLocation of theTime)) as number
						set minuteLeadingZero to true
						set timeStoreLocation to timeStoreLocation - 2
					else
						-- Otherwise, get the numbers only
						set tempNum to (item timeStoreLocation of theTime) as number
						if tempNum ≠ 0 then set the end of numList to tempNum
						set timeStoreLocation to timeStoreLocation - 1
					end if
				end try
			end repeat
			
			set theTime to figureOutTheTime(numList, false, true, true, minuteLeadingZero)
			set theTime to understandTheTime(theTime, inThe, false)
			return (targetDate + theTime - todaysDate) as number
		end if
	end if
	
	-- See if they gave an absolute date, a relative one, or a day of the week
	repeat with i from 1 to (length of monthDelimiters)
		if dateDesired contains (item i of monthDelimiters) then
			set monthFound to i
			exit repeat
		end if
		if i ≤ (length of weekdayDelimiters) then
			if dateDesired contains (item i of weekdayDelimiters) then
				set weekdayFound to i
			end if
		end if
	end repeat
	
	-- Getting rid of all the bits I could imagine being around the numbers
	set text item delimiters to (specialRelativeDayDelimiters & monthDelimiters & weekDelimiters & dayDelimiters & timeDelimiters & otherDelimiters)
	set inputList to every text item of dateDesired
	-- Resetting delimiters
	set text item delimiters to {""}
	
	repeat with i from 1 to (length of inputList)
		if item i of inputList is "-" and (character 1 of item (i + 1) of inputList is in "123456789") then
			set item (i + 1) of inputList to item i of inputList & item (i + 1) of inputList
		end if
	end repeat
	
	-- Count how many numbers were given
	repeat with i from 1 to (length of inputList)
		if (item i of inputList) is not "" then
			try
				set tempItem to (item i of inputList) as integer
				if class of tempItem is integer then set howManyNumbersInputted to howManyNumbersInputted + 1
			end try
		end if
		set tempItem to ""
	end repeat
	
	-- Get the numbers of the input — start from the back to get the minutes first
	set timeStoreLocation to length of inputList
	repeat while timeStoreLocation > 0
		try
			-- If the minutes have a leading zero, just combine them with the hours
			if (numList = {}) and ((item timeStoreLocation of inputList) starts with "0") then
				set the end of numList to ((item (timeStoreLocation - 1) of inputList) & (item timeStoreLocation of inputList)) as number
				set minuteLeadingZero to true
				set timeStoreLocation to timeStoreLocation - 2
			else
				-- Otherwise, get the numbers only
				try
					set tempNum to (item timeStoreLocation of inputList) as number
					if tempNum ≠ 0 then set the end of numList to tempNum
				end try
				set timeStoreLocation to timeStoreLocation - 1
			end if
		end try
	end repeat
	
	-- Reverse it so the order is from biggest to smallest time increment
	set numList to reverse of numList
	
	if (monthFound is 0) and (weekdayFound is 0) then
		-- If the user gave a relative date...
		tell dateDesired
			set daysMissing to not my isNumberIdentifier("d", it)
			set weeksMissing to not my isNumberIdentifier("w", it)
			if (howManyNumbersInputted - ((not daysMissing) as integer) - ((not weeksMissing) as integer)) = 0 then set timeMissing to true
		end tell
		
		-- Figure out how many weeks
		if not weeksMissing then
			set weeksDeferred to item 1 of numList
		else
			set weeksDeferred to 0
		end if
		
		-- Figure out how many days
		if not daysMissing then
			set daysDeferred to howManyDays(numList, weeksMissing)
		else
			if dateDesired contains "Tomorrow" then
				-- Special case where they put "tomorrow"
				set daysDeferred to 1
			else
				-- If they exclude it entirely or put "Today"
				set daysDeferred to 0
			end if
		end if
		
		-- Figure out the time
		set timeDeferredTemp to figureOutTheTime(numList, timeMissing, daysMissing, weeksMissing, minuteLeadingZero)
		-- Understand the meaning of the time component
		set timeDeferred to understandTheTime(timeDeferredTemp, inThe, timeMissing)
		
		-- Creating the time deferred based on minutes and hours calculated
		if timeDeferred ≥ 0 then
			set totalTimeDeferred to timeDeferred + daysDeferred * days + weeksDeferred * weeks
		else
			set totalTimeDeferred to timeDeferred
		end if
		-- end of relative date-only code
		
	else if (weekdayFound > 0) and (monthFound is 0) then
		if length of numList < 1 then set timeMissing to true
		-- Same as if the day and the week were missing on a relative date
		set timeDeferredTemp to figureOutTheTime(numList, timeMissing, true, true, minuteLeadingZero)
		set timeDeferred to understandTheTime(timeDeferredTemp, inThe, timeMissing)
		set daysDeferred to daysFromTodayToWeekday(weekdayFound)
		if timeDeferred ≥ 0 then
			set totalTimeDeferred to daysDeferred * days + timeDeferred
		else
			set totalTimeDeferred to timeDeferred
		end if
	else
		-- If the user gave an absolute date...
		if length of numList < 2 then set timeMissing to true
		-- Same as if the day were there but week wasn't on a relative date
		set timeDeferredTemp to figureOutTheTime(numList, timeMissing, false, true, minuteLeadingZero)
		set timeDeferred to understandTheTime(timeDeferredTemp, inThe, timeMissing)
		set timeFromTodayUntilDesired to figuringTimeToDesiredDay(monthFound, (item 1 of numList))
		if timeDeferred ≥ 0 then
			set totalTimeDeferred to timeFromTodayUntilDesired + timeDeferred
		else
			set totalTimeDeferred to timeDeferred
		end if
	end if
	
	return totalTimeDeferred
	
end englishTime


on isNumberIdentifier(possibleIdentifier, containerString)
	set numberIdentifier to true
	set identifierIsInContainer to false
	set positionOfLastIdentifier to 0
	set charList to every character of containerString
	
	repeat with i from 1 to (length of charList)
		if (item i of charList) = possibleIdentifier then
			set identifierIsInContainer to true
			set positionOfLastIdentifier to i
		end if
	end repeat
	
	if (positionOfLastIdentifier is 0) or (positionOfLastIdentifier is 1) then
		set numberIdentifier to false
	else
		set characterBefore to character (positionOfLastIdentifier - 1) of containerString
		set numBefore to 0
		try
			set numBefore to characterBefore as integer
		end try
		if (characterBefore is not " ") and (class of numBefore is not integer) then set numberIdentifier to false
	end if
	return numberIdentifier
end isNumberIdentifier


on howManyDays(numList, weeksMissing)
	if not weeksMissing then
		set daysDeferred to item 2 of numList
	else
		set daysDeferred to item 1 of numList
	end if
	return daysDeferred
end howManyDays


on figureOutTheTime(numList, timeMissing, daysMissing, weeksMissing, minuteLeadingZero)
	if not timeMissing then
		if minuteLeadingZero then
			set timeDeferredTemp to item -1 of numList
		else
			set text item delimiters to ""
			set timeDeferredTemp to ((items -1 thru (1 + ((not daysMissing) as integer) + ¬
				((not weeksMissing) as integer)) of numList) as text) as integer
		end if
	else
		set timeDeferredTemp to 0
	end if
	return timeDeferredTemp
end figureOutTheTime


to understandTheTime(timeDeferredTemp, inThe, timeMissing)
	if timeMissing then
		set timeDeferred to 0
	else
		if timeDeferredTemp > 2400 then
			-- If the time is greater than the 24 hour clock...
			display alert "Please try again: the time you entered was not a valid time of day."
			set timeDeferred to -1
			
		else if timeDeferredTemp = 2400 then
			-- If the time is equal to 2400...
			set timeDeferred to days
			
		else if timeDeferredTemp ≥ 100 then
			-- if they entered the time as a full hour:minute pair (with or without AM/PM and with or without the colon)
			set minutesDeferred to (((characters -2 thru -1 of (timeDeferredTemp as text)) as text) as integer)
			set hoursDeferred to (((characters 1 thru -3 of (timeDeferredTemp as text)) as text) as integer)
			-- Figuring out the minutes and hours in the time given (minutes are last two numbers)
			
			if inThe = "PM" then
				-- For any number specifically designated as PM
				set timeDeferred to ((hoursDeferred + 12) * hours + minutesDeferred * minutes)
			else if hoursDeferred = 12 and inThe = "AM" then
				-- For 12:00AM exactly
				set timeDeferred to minutesDeferred * minutes
			else
				-- For times in the AM (implicit or explicit) and explicit times in the PM (i.e., 16:00)
				set timeDeferred to (hoursDeferred * hours + minutesDeferred * minutes)
			end if
			
		else if timeDeferredTemp > 24 then
			-- If they entered the time as a single number above 24
			display alert "Please try again: the time you entered was not a valid time of day."
			set timeDeferred to -1
			
		else if timeDeferredTemp ≤ 24 then
			-- If the entered the time as a single number (with or without AM/PM)	
			if timeDeferredTemp = 24 then
				-- If they entered 24 hours exactly (treat as a full extra delay)
				set timeDeferred to days
			else if (timeDeferredTemp = 12) and (inThe ≠ "AM") then
				-- If they entered "12" (treat it as 12PM)
				set timeDeferred to 12 * hours
			else if (timeDeferredTemp ≥ 12) or (inThe ≠ "PM") then
				-- For implicit and explicit AM entries and for implicit PM entries
				set timeDeferred to timeDeferredTemp * hours
			else
				-- For explicit PM entries
				set timeDeferred to (timeDeferredTemp + 12) * hours
			end if
		end if
	end if
	return timeDeferred
end understandTheTime


to figuringTimeToDesiredDay(monthDesired, dayDesired)
	set todaysDate to (current date)
	set time of todaysDate to 0
	-- Creating an intial date object
	copy todaysDate to exactDesiredDate
	set (day of exactDesiredDate) to dayDesired
	set (month of exactDesiredDate) to monthDesired
	if exactDesiredDate < (current date) then
		set (year of exactDesiredDate) to ((year of todaysDate) + 1)
	end if
	return (exactDesiredDate - todaysDate)
end figuringTimeToDesiredDay


on daysFromTodayToWeekday(weekdayDesired)
	set currentWeekday to (weekday of (current date)) as integer
	if currentWeekday = weekdayDesired then
		set daysDeferred to 0
	else if currentWeekday < weekdayDesired then
		set daysDeferred to weekdayDesired - currentWeekday
	else
		set daysDeferred to 7 + weekdayDesired - currentWeekday
	end if
	return daysDeferred
end daysFromTodayToWeekday

on understandAbsoluteDate(theText)
	set theDate to (current date)
	set the day of theDate to 1
	set the month of theDate to 2
	set theDate to (theDate - 1 * days)
	set theDate to short date string of theDate
	
	set text item delimiters to {".", "-", "/", "–", "—", "|", "\\"}
	set theDate to every text item of theDate
	set thePositions to {theDay:0, theMonth:0, theYear:0}
	
	-- Checks the positions of the date components based on January 31 of this year
	repeat with i from 1 to (length of theDate)
		tell item i of theDate
			if it is in "01" then
				set (theMonth in thePositions) to i
			else if it is in "31" then
				set (theDay in thePositions) to i
			else
				set (theYear in thePositions) to i
			end if
		end tell
	end repeat
	
	set theText to every text item of theText
	
	set targetDate to (current date)
	set time of targetDate to 0
	if (length of theText is not 2) and (length of theText is not 3) then
		-- If they don't input at 2-3 numbers, return the error
		return -1
	else
		if length of theText is 3 then
			-- If the input has three numbers
			set the year of targetDate to solveTheYear((item (theYear of thePositions) of theText) as number)
		else
			-- If the input has two numbers (left out the year)
			set thePositions to adjustPositionsForNoYear(thePositions)
		end if
		set the month of targetDate to (item (theMonth of thePositions) of theText) as number
		set the day of targetDate to (item (theDay of thePositions) of theText) as number
		if targetDate is less than (current date) then
			set the year of targetDate to (the year of (current date)) + 1
		end if
	end if
	return targetDate
end understandAbsoluteDate

to adjustPositionsForNoYear(thePositions)
	if (theYear in thePositions) is 1 then
		set (theMonth in thePositions) to (theMonth in thePositions) - 1
		set (theDay in thePositions) to (theDay in thePositions) - 1
	else if yearPosition is 2 then
		if (theDay in thePositions) < (theMonth in thePositions) then
			set (theMonth in thePositions) to (theMonth in thePositions) - 1
		else
			set (theDay in thePositions) to (theDay in thePositions) - 1
		end if
	end if
	return thePositions
end adjustPositionsForNoYear

to solveTheYear(num)
	if num ≥ 1000 then
		return num
	else
		return (2000 + num)
	end if
end solveTheYear

to getDate(theInput)
	-- Setting the desired date based on input
	set desiredDate to (current date)
	set time of desiredDate to 0
	set secondsDeferred to englishTime(theInput)
	if secondsDeferred = -1 then
		set timesUsedSinceError to 0
		return -1
	else
		set timesUsedSinceError to timesUsedSinceError + 1
	end if
	return desiredDate + secondsDeferred
end getDate

#3

@TheWart Thanks for sharing Curt Clifton’s script! By chance, have you done any work to clean it u, so that it works more like OmniFocus?

For example, it seems to work quite well except for the following two cases.

First, it seems to have some problems with the tomorrow parameter. It gives an error, if used without more information. Also, if you try to set the time (e.g., tomorrow 5pm), it gives some really odd results. It ought to operate just like if someone had input 1d (i.e., tomorrow and its variations ought to be the equivalent of 1d, so that the previous example would operate like 1d 5pm. which works just fine in the script).

Second, it also has some problems with years. For example, if you leave the year off, it gives an error (e.g., 12/15). And, if you try it with a year that has already passed, it automatically behaves as if you left off the year, by assigning the next year (e.g., 11/15/2011 = 12/11/2019, or 11/15/11 = 12/11/2019) (i.e., where today’s actual date is December 10, 2018). While the later is not such a big deal - because users are not likely to set due dates that have already passed - but former is a big problem.

Lastly - and this is not so much of a problem, as wishful thinking - it’d be nice if its day references behaved more like OmniFocus by adopting the user’s default time for defer or due dates (i.e., from OmniFocus’ preferences). The default of midnight is a quite odd (e.g., 1d = the next day at midnight, Tuesday = the next Tuesday at midnight, etc.). While I’m sure it’s difficult to reference OmniFocus’ preferences for the time, do you know if there’s an easy place in the code to make this alteration (e.g., say by setting the default hours to * 8 or 8AM, etc.)?

Thanks again for sharing this script, and for any help you can lend with the issues above.

All the best!


#4

@jasondm007 It’s been a while since I had time to play with this and run your scenarios. You’ve definitely spotted inconsistencies.

I am not sure what is going on with the tomorrow parameter. It is defined in the script as specialRelativeDayDelimiters but doesn’t work as such.

The other two issues I haven’t encountered (although I can reproduce them) frankly because I don’t use them. I am using this script exclusively for 2d 4pm kind of stuff, to move tasks a couple of days out.

Perhaps somebody else has ideas? I know @unlocked2412 has provided extremely valuable help on this forum before. Perhaps we can lure him here as well ;)


#5

I think you hit an edge case. Probably, this script wasn’t prepared to handle that. I see the problem but it would need a major rewrite.

I made a very rough sketch (JavaScript For Automation) with functionality that resembles Chris Suave’s script.

There is only one drawback. It creates a task in the inbox (and then deletes it automatically). I can leverage OmniFocus date parsing mechanism that way (and respect user date/time preferences). For the time being, it only modifies defer date property.

(() => {
    'use strict';

    // main :: IO ()
    const main = () => {
        const setDateAndFlag = (blnFlag, strDateType, date, task) => {
            return (
                task[strDateType] = date,
                task.flagged = blnFlag,
                task
            )
        }

        const of = Application('OmniFocus');
        const doc = of .defaultDocument;

        const lrTasks = bindLR(
            // Selection ?
            ofSelectionLR(),

            // Dialog Accepted ?
            seln => {
                const flagButton = dialogChoiceLR(
                    'Automatically flag Tasks?', 'Select an option', Left(''), ['Cancel', 'No, Don\'t flag', 'Yes, Use Flags'], 'No, Don\'t flag',
                    'Cancel', 30, '/Applications/OmniFocus.app/Contents/Resources/AppIcon.icns'
                ).Right.buttonReturned
                const dateButton = dialogChoiceLR(
                    'Would you like to use Due or Defer date for scheduling?', 'Select an option', Left(''), ['Cancel', 'Defer Date', 'Due Date'], 'Defer Date',
                    'Cancel', 30, '/Applications/OmniFocus.app/Contents/Resources/AppIcon.icns'
                ).Right.buttonReturned
                const lrDate = dialogChoiceLR(
                     'When would you like to ', dateButton + ' these tasks?', Right('tomorrow'), ['Cancel', 'OK'], 'OK',
                     'Cancel', 30, '/Applications/OmniFocus.app/Contents/Resources/AppIcon.icns'
                )

                return isLeft(lrDate) ? (
                    lrDate
                ) : (() => {
                    const strDateType = dateButton === 'Defer Date' ?
                        'deferDate' : 'dueDate'
                    const blnFlag = flagButton === 'No, Don\'t flag' ? false : true
                    // Leverage OmniFocus Date Parsing
                    const oTasks = doc.parseTasksInto({
                        'withTransportText': '#' + lrDate.Right.textReturned
                    });
                    const oDate = oTasks[0].dueDate();
                    // of.delete(oTasks[0])
                    return Right(
                        map(curry(setDateAndFlag, blnFlag, strDateType, oDate), seln)
                    )
                })()
            }
        )
        return standardAdditions()
            .displayNotification(
                isLeft(lrTasks) ? (
                    lrTasks.Left
                ) : 'Changed date on: ' + lrTasks.Right.length + ' tasks'
            )
    };

    // GENERIC -----------------------------------------------------------------
    // https://github.com/RobTrew/prelude-jxa
    // JS - Apps

    // ofSelectionLR :: () -> Either [OF Task]
    const ofSelectionLR = () => {
        const
            appOF = Application('OmniFocus'),
            ds = appOF.documents;

        return bindLR(
            bindLR(

                // Documents ?
                ds.length === 0 ? (
                    Left('No documents open')
                ) : Right(ds[0].documentWindows),

                // Windows ?
                ws => ws.length === 0 ? (
                    Left('No windows open')
                ) : Right(ws[0].content.selectedTrees)
            ),

            // Selection ?
            seln => seln.length === 0 ? (
                Left('No selection')
            ) : Right(seln.value())
        )
    }

    // JS - Automation

    // dialogChoiceLR :: String -> String -> Either String String ->
    //      [String] -> String -> String -> Int -> FilePath
    //          -> Either Dict String
    const dialogChoiceLR = (
        strTitle, strMsg, lrDefault, lstButtons, strDefaultButton,
        strCancelButton, intMaxSeconds, strIconPath
    ) => {
        const
            sa = standardAdditions();
        try {
            sa.activate;
            return (() => {
                // sa :: standardAdditions
                const dct = sa.displayDialog(strMsg, Object.assign({
                    buttons: lstButtons || ['Cancel', 'OK'],
                    defaultButton: strDefaultButton || 'OK',
                    cancelButton: strCancelButton || 'Cancel',
                    withTitle: strTitle,
                    givingUpAfter: intMaxSeconds || 120
                }, isRight(lrDefault) ? {
                    defaultAnswer: lrDefault.Right
                } : {}, typeof strIconPath === 'string' ? {
                    withIcon: Path(strIconPath)
                } : {}));
                return dct.gaveUp ? (
                    Left(dct)
                ) : Right(dct);
            })();
        } catch (e) {
            return Left(e);
        }
    };

    // standardAdditions :: () -> Application
    const standardAdditions = () =>
        Object.assign(Application.currentApplication(), {
            includeStandardAdditions: true
        });

    // JS - Prelude

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
    const bindLR = (m, mf) =>
        m.Right !== undefined ? (
            mf(m.Right)
        ) : m;

    // Flexibly handles two or more arguments, applying
    // the function directly if the argument array is complete,
    // or recursing with a concatenation of any existing and
    // newly supplied arguments, if gaps remain.
    // curry :: ((a, b) -> c) -> a -> b -> c
    const curry = (f, ...args) => {
        const go = xs => xs.length >= f.length ? (
            f.apply(null, xs)
        ) : function() {
            return go(xs.concat(Array.from(arguments)));
        };
        return go(args);
    };

    // isLeft :: Either a b -> Bool
    const isLeft = lr =>
        lr.type === 'Either' && lr.Left !== undefined;

    // isRight :: Either a b -> Bool
    const isRight = lr =>
        lr.type === 'Either' && lr.Right !== undefined;

    // map :: (a -> b) -> [a] -> [b]
    const map = (f, xs) => xs.map(f);

    // e.g. sortBy(on(compare,length), xs)
    // on :: (b ->ja b -> c) -> (a -> b) -> a -> a -> c
    const on = (f, g) => (a, b) => f(g(a), g(b));

    // or :: [Bool] -> Bool
    const or = xs => {
        let i = xs.length;
        while (i--)
            if (xs[i]) return true;
        return false;
    };

    // JXA MAIN ----------------------------------------------------------------
    return main();
})();

#6

Update: I added an option to set defer or due date and removed task deletion of inbox task (it was unnecessary). Text in dialogs need more polishing.


#7

Another update: the script now gives the option to flag tasks and dialogs are a bit more polished.


#8

This is a fantastic update, @unlocked2412. Seeing a difference if I put in ‘Mon’ versus ‘Monday.’ Also, for some reason, ‘Wednesday’ and ‘Wed’ both give the following error:

The operation couldn’t be completed. /Users/ben.crist/Library/Application Scripts/com.omnigroup.OmniFocus3/Later.scpt:6513:6518: execution error: Error on line 193: Error: A privilege violation occurred. (-10004)

Do I need to specify a numerical date instead of a day name?


#9

Glad you like it.

Just tested with this data and not seeing any errors. What OS and OF version are you running ? How are you executing the script ?

No, because it uses OmniFocus date parsing mechanism. There isn’t a restriction.


#10

@TheWart and @unlocked2412 Thanks for taking a crack at this script, and sorry for the slow response (mentally, I’m still on holiday - Ha).

Because of the complexity of the script, I wound up integrating it into Alfred. Then, I essentially used a bunch of filters/regex keys to catch the problematic inputs outlined above, so that I could either (1) convert the input into something that worked with your script, or (2) feed the input into an altogether different set of scripts.

The new scripts add some functionality, like delete and flag, but mostly just tinker with some of the scenarios outlined previously (tomorrow, days, weeks, months, years, etc.). Also, if a task’s due date isn’t empty, the scripts add the time to the existing day (e.g., if the due date is 1/1/2019 at 10AM and the user’s input is 1d, the new due date will be 1/2/2019 at 10AM) or if it was blank it will act in a similar manner but set the default time to 8AM (this is the time that I use in OmniFocus’s preferences).

If either of you guys are Alfred users, I’m happy to share the workflow. As you can tell from my posts, however, I’m a novice (i.e., this approach is not very efficient, and will probably make your skin crawl - but it works).

Thanks again!


#11

You’re welcome, @jasondm007. Good that you found something that works for you. I do use Alfred everyday. By request, I did an Alfred Workflow that offers some flexibility inputting tasks. Example:

Task Name Flag! @Context ::Project (#Start #Due) $Duration //Note


#12

@unlocked2412 Thanks for letting me know about your Alfred workflow. I can’t wait to check it out. It looks amazing!