Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A script to add due and start date support to TaskPaper, forked from the script by andyferra. See http://www.hogbaysoftware.com/wiki/StartAndDueDatesV2 for more information.
(*
Parse Start and Due Dates script
By Ryan Oldford
Based on work by andyferra (https://gist.github.com/andyferra/64842)
change_case taken from http://www.macosxautomation.com/applescript/sbrt/sbrt-06.html
This script is used with TaskPaper to provide better start and due date support, as well as support for repeating tasks.
The script searches for @due and @start tags, converts their values to standard date code values,
then adds the appropriate "diff" tag whose value indicates how far away the due or start date is (negative = past, 0 = today).
Any tasks with a @repeat tag that are done are edited to remove the done tag and have their due tags updated.
Due/Start dates allowed:
- Any dates in the standard format (YYYY-MM-DD)
- Natural language dates (i.e. June 15), can use abbreviated months (i.e. jun 15) or lower case (i.e. june 15)
- today
- tomorrow
- Any allowable repeat value (see below)
If no date is given, the date value is set to today.
Repeating tasks
Any repeating task needs an @repeat tag with a repeat value.
The repeat values are in the format (repeatType:repeatCount).
Repeat type indicates the type of repeat, and can be any of the following:
- day
- week
- month
- year
- any weekday (can be abbreviated, i.e. tues or tue work the same as tuesday)
Repeat count determines how many of the repeat type have to pass before a new repeat is due.
Examples:
- day:1 = every day
- day:3 = every 3rd day
- week:1 = every week
- month:2 = every 2 months
- sunday:2 = every 2nd Sunday
These are calculated for done tasks based on the due date. If none is given, today will be used (as stated above).
Customization
You can use different tag names for the tags by editing the properties below.
*)
-- Edit these if you want to use different tags.
property dueTag : "due"
property startTag : "start"
property asapTag : "asap"
property dateTag : {dueTag, startTag, asapTag}
property repeatTag : "repeat"
property diffStartTag : "diffstart"
property diffDueTag : "diffdue"
property diffAsapTag : "diffasap"
property doneTag : "done"
property errorTag : "error"
property removeTags : {diffStartTag, diffDueTag, diffAsapTag}
property numberSet : {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
property monthSet : {January, February, March, April, May, June, July, August, September, October, November, December}
global vToday
set vToday to current date
set time of vToday to 0
global startingASTIDs
set startingASTIDs to AppleScript's text item delimiters
my clearErrors(errorTag)
--Start by converting any due tags with a text description of the date
--to an actual date
my convertDate(dueTag)
my convertDate(startTag)
tell front document of application "TaskPaper"
--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 dueWhen tag 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
set dateString to (get value of tpDueTag)
tell me to set vDate to getDateFromText(dateString)
if vDate is not null then
set diffDays to ((round ((vDate - vToday) / days)) as rich text)
set tagList to (every tag whose name is diffDueTag)
if (tagList is {}) then
make new tag with properties {name:diffDueTag, value:diffDays}
end if
repeat with thisTag in tagList
set value of thisTag to diffDays
end repeat
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
--Add/update the startWhen tag based on the start date of items that are not done
repeat with tpStartTag in (every tag of (every entry) whose name is startTag)
tell entry of tpStartTag
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
set dateString to (get value of tpStartTag)
tell me to set vDate to getDateFromText(dateString)
if vDate is not null then
set diffDays to ((round ((vDate - vToday) / days)) as rich text)
set tagList to (every tag whose name is diffStartTag)
if (tagList is {}) then
make new tag with properties {name:diffStartTag, value:diffDays}
end if
repeat with thisTag in tagList
set value of thisTag to diffDays
end repeat
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 clearErrors(tagLabel)
tell application "TaskPaper"
tell front document
repeat with myTask in entries
tell myTask
repeat while (exists tag named tagLabel)
if (exists tag named tagLabel) then
delete tag named tagLabel
end if
end repeat
end tell
end repeat
end tell
end tell
end clearErrors
on convertDate(tagLabel)
tell front document of application "TaskPaper"
repeat with myTag in (every tag of (every entry) whose name is tagLabel)
set vDueVal to (value of myTag)
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 myTag
make new tag with properties {name:errorTag, value:"Unknown value for " & tagLabel & " tag"}
end tell
end if
if vDateString is not null then
set value of myTag to vDateString
end if
end if
end repeat
end tell
end convertDate
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
set AppleScript's text item delimiters to {" "}
if (count of text items in dueValue) = 2 then
-- check for months, do natural language date processing
copy vToday to vDate
set firstWord to (first text item of dueValue)
set secondWord to (second text item of dueValue)
if (whichMonth(firstWord) is not null) and ((first character of secondWord) is in numberSet) then
set month of vDate to whichMonth(firstWord)
set day of vDate to (secondWord as integer)
set year of vDate to (year of vToday)
repeat while vDate comes before vToday
set year of vDate to (year of vDate) + 1
end repeat
else
set vDate to null
end if
else if (count of text items in dueValue) = 1 then
--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
end if
set AppleScript's text item delimiters to startingASTIDs
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
set AppleScript's text item delimiters to startingASTIDs
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
on whichMonth(theText)
set theText to change_case(theText, 0)
if length of theText is less than 3 then return null
set theMonth to null
repeat with i from 1 to 12
set aMonth to item i of monthSet
if (aMonth as text) starts with theText then
copy aMonth to theMonth
end if
end repeat
return theMonth
end whichMonth
on change_case(this_text, this_case)
if this_case is 0 then
set the comparison_string to "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
set the source_string to "abcdefghijklmnopqrstuvwxyz"
else
set the comparison_string to "abcdefghijklmnopqrstuvwxyz"
set the source_string to "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
end if
set the new_text to ""
repeat with this_char in this_text
set x to the offset of this_char in the comparison_string
if x is not 0 then
set the new_text to (the new_text & character x of the source_string) as string
else
set the new_text to (the new_text & this_char) as string
end if
end repeat
return the new_text
end change_case
@alienlebarge

This comment has been minimized.

Copy link

@alienlebarge alienlebarge commented Aug 3, 2013

Really nice !
Thanks for this script 👏

@alienlebarge

This comment has been minimized.

Copy link

@alienlebarge alienlebarge commented Aug 5, 2013

It would be nice to kepp the @today, @overdue and @upcoming tags. A lot of themes use these tags to color items.

@jackbrannen

This comment has been minimized.

Copy link

@jackbrannen jackbrannen commented Feb 13, 2014

Hi Ryan, I'm researching recurring start dates in TaskPaper. Seems like there's currently no script to do it. Is that right? I see on line 85 you have a note about refreshing a start date, but I can't get this work when I fire the script.

Thanks,
Jack

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment