Forked from theconektd/Parse Start and Due Dates.applescript
Last active
August 18, 2024 13:02
-
-
Save ROldford/5210432 to your computer and use it in GitHub Desktop.
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(* | |
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 |
It would be nice to kepp the @today
, @overdue
and @upcoming
tags. A lot of themes use these tags to color items.
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
Really nice !
Thanks for this script 👏