Please also see the variant for managing start and due dates here which is based on this script.
There has been some discussion about managing due dates on tasks in TaskPaper, and this is a problem that I too need to solve. Thanks to the terrific job Jesse did on the object model and AppleScript support TaskPaper 2, I was able to write an AppleScript that provides the basic functionality for managing due dates that I need. So, I thought I would post the script for anyone who might find it useful either as is or modified to suit your needs.
I had 5 main goals for managing tasks with due dates:
My basic approach to doing this in TaskPaper is to add @due tags with a value that is either a date or a word that describes a date (e.g. "wednesday"). I then run the AppleScript and it handles adding tags to tasks that are due today (@today), tasks that are past due (@overdue), and tasks that will be due in the next 5 days (@soon). For tasks that need to be done on a regular basis, I add a @repeat tag that describes how often it needs to happen, then after the task is marked @done, running the script will remove the @done task and update the date in the @due tag. (Details on using the script are below.)
I created queries in TaskPaper that allow me to find everything due today (@due, @overdue) and upcoming items (@upcoming). I also use the Themes to add a color to items by tag. I have @overdue items appear in red, for example.
Before I get into the details of the script, I should make a couple of disclaimers. It does add and remove tags on tasks, and I can't promise it won't wreck your document either due to a bug in the script, or a bug in the AppleScript support in TaskPaper 2. I also wrote it mainly late at night, and don't have much time to support it.
All that said, this script has worked fine for me now for a few weeks. If you have questions or suggestions, feel free to post them here and I'll see what I can do. I'm relatively new to the wacky world of AppleScript, so if you have suggestions on how to do stuff better, please do share.
Anyway, here are some details on using the script, including the tags the script uses and the valid values for each.
To associate a due date with a task, you add the @due tag with a date value. The date must be in the format YYYY-MM-DD.
Example:
- This task has a due date @due(2008-05-31)
Instead of the date, you can also enter one of the following values and they will be converted to a date in the YYYY-MM-DD format when the script is run:
Examples:
- This will be due today @due(today)
- This will be due tomorrow @due(tomorrow)
- This will be due next Tuesday @due(tuesday)
- This will be due today @due
You can specify that a task should be done at a regular interval by using the @repeat tag. The following are valid values for the @repeat tag.
Any of these values can be followed by a ":" and a number that specifies a count (this will make more sense when you see the examples below). If no count is specified, 1 is assumed.
If the task has a @due tag, then all calculations are done relative to that date. Otherwise, they will be calculated relative to today.
The @repeat tag is only evaluated on items that are marked done, or items that have no @due tag. So when you finish a repeating task and mark it done, the next time you run the script, the the @done tag will be removed, and the @due tag will have its date value changed to a new date based on the @repeat tag value. When the new date is calculated, the script will always generate a new due date that is on or after the current date. So if you have a task that repeats every day, and you skip 2 days, the next due date will be today, not yesterday.
Examples:
- This will repeat every 3 days, starting 3 days from today @repeat(day:3)
- This will repeat every other monday @due(2008-05-12) @repeat(monday:2) @done(2008-05-12)
- This will repeat every month on the 15th, but when the script is run nothing will change on this item because it is not marked done @due(2008-05-15) @repeat(month)
Note that this script will not currently resurrect done items from the Archive project. That should be doable, I just didn't write the code to do it. So you should run the script before archiving done tasks.
When you run the script, first thing it does is give all @due tags with a descriptive value (e.g. @due(tomorrow)) an actual date. Then it updates @due dates on all the tasks with @repeat tags that are done, or missing a @due tag. Finally, it adds the @today, @overdue and @upcoming tags as appropriate.
The script also has a list of tags that will get removed from tasks marked @done. The main ones that get stripped are the @today, @upcoming and @overdue tags. I did this because it simplifies queries if I don't have to ignore tasks with @done tags. If you don't want that to happen then you can...
The properties at the beginning of the script allow you to customize some behaviors without digging into the code. For the most part, you are able customize all the tag names used, the number of days into to future to look for "upcoming" items (default is 5), and the tags that get stripped from @done tasks.
There is some basic error handling in the script for things like invalid date formats and unknown repeat values. If the script encounters a problem, it will add an @error tag to the task where it found the problem.
There are probably a lot of things you might want that this script doesn't currently have, but here are a couple I can think of:
To use this script, copy it and paste it into Script Editor. You may need to change the name of the TaskPaper application in the first "tell" block. You can also edit any of the properties at the beginning to suit your tastes.
Finally, compile the script and save it in the TaskPaper Scripts folder. I also suggest adding a keyboard equivalent using them method described in this tip.
(
A basic script for managing due dates.
)
property dueTag : "due"
property repeatTag : "repeat"
property todayTag : "today"
property overdueTag : "overdue"
property upcomingTag : "upcoming"
property doneTag : "done"
property inProgressTag : "inprogress"
property errorTag : "error"
property removeTags : {upcomingTag, todayTag, overdueTag, inProgressTag}
property upcomingDays : 5
property numberSet : {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
global vToday
set vToday to current date
set time of vToday to 0
tell front document of application "TaskPaper"
--Start by converting any due tags with a text description of the date
--to an actual date
repeat with tpDueTag in every tag of (every entry) whose name is dueTag
tell tpDueTag
set vDueVal to value
if vDueVal is missing value then
set vDueVal to "today"
end if
if character 1 of vDueVal is not in numberSet then
set vDateString to null
tell me to set vDate to getDateForDueValue(vDueVal)
if vDate is not null then
tell me to set vDateString to getTextFromDate(vDate)
else
tell entry of tpDueTag
make new tag with properties {name:errorTag, value:"Unknown value for due tag"}
end tell
end if
if vDateString is not null then
set value to vDateString
end if
end if
end tell
end repeat
--Next handle repeat tags. A due tag is not required on these.
repeat with tpRptTag in every tag of (every entry) whose name is repeatTag
tell entry of tpRptTag
set tagList to (every tag whose name is dueTag)
set needDue to false
if tagList is {} then
--no due tag, create one, and remember to fill in a date below.
--use today as the start date
set tpDueTag to make new tag with properties {name:dueTag}
copy vToday to vDate
set needDue to true
else
--have a due tag, use that as the start date
set tpDueTag to first item of tagList
set dateString to (get value of tpDueTag)
tell me to set vDate to getDateFromText(dateString)
end if
if not needDue then
set tagList to (every tag whose name is doneTag)
end if
--if there was no due tag, or the entry is marked done,
--the calculate and set the date of the due tag.
if needDue or tagList is not {} then
--Delete all extra tags
repeat with delTag in removeTags
delete (every tag whose name is delTag)
end repeat
--Create a date from the repeat value
set rInfo to (get value of tpRptTag)
if (vDate is not null) then
tell me to set vDate to getRepeatDate(rInfo, vDate)
end if
if vDate is not null then
--fill in the date, make sure it is not marked done
tell me to set vDateText to getTextFromDate(vDate)
set value of tpDueTag to vDateText
delete (every tag whose name is doneTag)
else
--flag unknown repeat value with an error tag
make new tag with properties {name:errorTag, value:"unknown repeat type"}
end if
end if
end tell
end repeat
--Add/update the extra tags based on the due date of items that are not done
repeat with tpDueTag in (every tag of (every entry) whose name is dueTag)
tell entry of tpDueTag
set tagList to (every tag whose name is doneTag)
set errTagList to (every tag whose name is errorTag)
if (tagList is {}) and (errTagList is {}) then
--Not marked done, see if any tags need to be added
set dateString to (get value of tpDueTag)
tell me to set vDate to getDateFromText(dateString)
if vDate is not null
set diffDays to (vDate - vToday) / days
if (diffDays < 0) then
set tagList to (every tag whose name is overdueTag)
if (tagList is {}) then
make new tag with properties {name:overdueTag}
end if
delete (every tag whose name is upcomingTag)
delete (every tag whose name is todayTag)
else if (diffDays = 0) then
set tagList to (every tag whose name is todayTag)
if (tagList is {}) then
make new tag with properties {name:todayTag}
end if
delete (every tag whose name is upcomingTag)
else if (diffDays <= upcomingDays) then
set tagList to (every tag whose name is upcomingTag)
if (tagList is {}) then
make new tag with properties {name:upcomingTag}
delete (every tag whose name is todayTag)
end if
end if
else
--invalid date format
make new tag with properties {name:"error", value:"invalid date format"}
end if
else
--Marked done, delete all extra tags
repeat with delTag in removeTags
delete (every tag whose name is delTag)
end repeat
end if
end tell
end repeat
end tell
on getDateFromText(dateText)
set vDate to null
set AppleScript's text item delimiters to {"-"}
if (count of text item of dateText) is 3 then
set vYear to text item 1 of dateText
set vMonth to text item 2 of dateText
set vDay to text item 3 of dateText
if (vYear > 1000) and (vMonth > 0 and vMonth < 13) and (vDay > 0 and vDay < 32) then
set vDate to current date
set year of vDate to (vYear as integer)
set month of vDate to vMonth as integer
set day of vDate to vDay as integer
set time of vDate to 0
end if
end if
return vDate
end getDateFromText
on getTextFromDate(vDate)
set dText to ((year of vDate) as text) & "-"
set dayText to (month of vDate as number) as text
if length of dayText is 1 then
set dayText to "0" & dayText
end if
set dText to dText & dayText & "-"
set dayText to (day of vDate as number) as text
if length of dayText is 1 then
set dayText to "0" & dayText
end if
return dText & dayText
end getTextFromDate
on getDateForDueValue(dueValue)
set vDate to null
if character 1 of dueValue is in numberSet then
set vDate to getDateFromText(dueValue)
else
--Special cases for due date not handled in getRepeatDate
if "today" is dueValue then
copy vToday to vDate
else if "tomorrow" starts with dueValue then
copy vToday to vDate
set vDate to vDate + 1 * days
else
set vDate to getRepeatDate(dueValue, vToday)
end if
end if
return vDate
end getDateForDueValue
on getRepeatDate(repeatDesc, fromDate)
copy fromDate to vDate
set AppleScript's text item delimiters to {":"}
set repeatType to text item 1 of repeatDesc
if (count of text items in repeatDesc) > 1 then
set repeatCount to text item 2 of repeatDesc
else
set repeatCount to 1
end if
if repeatType is "day" or repeatType is "week" then
if repeatType is "day" then
set vInterval to days
else
set vInterval to weeks
end if
set vDate to vDate + repeatCount * vInterval
repeat while vDate comes before vToday
set vDate to vDate + repeatCount * vInterval
end repeat
else if repeatType is "month" then
set month of vDate to (month of vDate) + repeatCount
repeat while vDate comes before vToday
set month of vDate to (month of vDate) + repeatCount
end repeat
else if repeatType is "year" then
set year of vDate to (year of vDate) + repeatCount
Log "vDate is " & vDate
repeat while vDate comes before vToday
set year of vDate to (year of vDate) + repeatCount
end repeat
else
set rDay to 0
if "sunday" starts with repeatType then
set rDay to Sunday
else if "monday" starts with repeatType then
set rDay to Monday
else if "tuesday" starts with repeatType then
set rDay to Tuesday
else if "wednesday" starts with repeatType then
set rDay to Wednesday
else if "thursday" starts with repeatType then
set rDay to Thursday
else if "friday" starts with repeatType then
set rDay to Friday
else if "saturday" starts with repeatType then
set rDay to Saturday
end if
if rDay > 0 then
--Handle case where vDate is not currect day of week.
set vOffset to rDay - (weekday of vDate)
if vOffset is not 0 then
set vDate to vDate + vOffset * days
if vOffset > 0 then
--If we move forward, count that as 1
set repeatCount to repeatCount - 1
end if
end if
--Find next date after vDate
set vOffset to 7 * repeatCount
set vDate to vDate + vOffset * days
repeat while vDate comes before vToday
set vDate to vDate + vOffset * days
end repeat
else
--Unknown type, return nothing
set vDate to null
end if
end if
return vDate
end getRepeatDate
Finally, if you want to play with the script and see what it does, here is text of the TaskPaper document I have been using to test the script. Just paste this into a new TaskPaper document and then run the script.
Assigning due dates :
Items due today will get the today tag.
Items due in the next 5 days get the upcoming tag.
Items due prior to today that are not marked done get the overdue tag.
- Enter due dates in YYYY-MM-DD format
- This due 5/01/08, should be marked overdue @due(2008-05-01)
- Use today and tomorrow as values. Script will replace them with actual dates.
- this is due today @due(today)
- this is due tomorrow @due(tomorrow)
- Use same specifiers as repeat tags. Script will replace them with actual dates.
- Do this tomorrow @due(day)
- Do this in 3 days @due(day:3)
- Do this next wednesday @due(wednesday)
- Do this next week @due(week)
- Do this in 2 weeks @due(week:2)
- Do this next month @due(month)
- Do this next year @due(year)
- Error Handling
- Invalid date @due(5/25)
- Works, generates error tag
- No date @due
- Works, defaults to today
- Invalid specifier @due(yesterday)
- Works, generates error tag.
Repeating Items :
The repeat tag is only processed if the item is marked done, or does not have a due date assigned.
- day
- With number, every x days. Example, every 4 days @due(2008-05-21) @repeat(day:4) @done(2008-05-26)
- day name
- With number, repeat every x day names. Example, every other tuesday @repeat(tuesday:2) @due(2008-05-20) @done(2008-05-26)
- Allow abbreviations
- Every 2 Mondays @repeat(mon:2) @due(2008-05-25) @done(2008-05-26)
- Repeat every Friday starting this Friday @repeat(fri)
- Add "-month" to get "of the month". Example, every 3rd thursday of month:@repeat(thursday-month:3)
- This one is not implemented yet
- week
- With number, every x weeks. Example, every other week @due(2008-05-15) @repeat(week:2) @done(2008-05-26)
- month
- With number, every x months. Example, every six months @due(2008-05-05) @repeat(month:6) @done(2008-05-26)
- year
- With number, every x years. Example, a year later @due(2008-04-15) @repeat(year:1) @done(2008-05-26)
- If no due tag present, add one, calculating from today
- Repeat every day, starting tomorrow @repeat(day)
- Error Handling
- Bad specifier with due date @due(2008-05-21) @repeat(yesterday) @done(2008-05-26)
- Works, generates error tag.
- Bad specifier @repeat(yesterday)
- Mostly works, generates error tag, but also empty due tag.
- Bad date with repeat @due(5/25) @repeat(month)
- Works, generates error tag.