iCal - AppleScript To Convert ToDos To Events

I am requesting assistance with an iCal issue.

I use a iMac G4 800MHz 15" running 10.4.11. I have used Chronos Personal Organizer 4.5.0 for quite some time. I want to transfer my approximately 21,000 CPO calendar items (all are To Dos) to iCal. This number includes approximately 5,000 items that I transferred from DateBook Pro to CPO a few years ago.

I downloaded the trial version of Chronos SOHO Organizer 6.5.4 and the accompanying converter. I converted my three CPO calendars and then imported them to SOHOO 6.5.4 successfully. Then, I exported each of them from SOHOO 6.5.4 as a “.ics” file. I imported each of these “.ics” files into iCal successfully. However, iCal does not display To Do items embedded in the calendar as does CPO and SOHOO. iCal segregates the To Do items into the To Do list window. Therefore, I wish to convert all of my 21,000 iCal calendar items from To Do items to Events, such that iCal will display them in the calendar display (day / week / month) on the original date that I assigned to each of them. During the conversion from To Do to Event, I wish to retain all text / remarks / notes.

Can anyone suggest an AppleScript that will perform this conversion? I have searched the web exhaustively without any success. Would anyone like to take a stab at writing an AppleScript that performs this conversion? I have read many postings on various forums that wish to do the same thing as me, eg. transfer data from either CPO or SOHOO to iCal but embed the To Do items in the Event calendar.

Thank you.

Kurt Todoroff

Hi, Kurt. Welcome to these forums. :slight_smile:

The script below has only been tested with 12 todos, so I don’t know how it’ll fare with 21000 of them. Let me know how it goes. It will probably take a while if it works, but is probably faster than learning how to edit the .ics files directy. :wink: It only works with iCal 2.0 or later, since todos in earlier versions didn’t have a ‘description’ property. It also assumes that all your todos have ‘due dates’ corresponding to the dates in the calendar where you want the events to appear.

tell application "iCal"
	activate
	set theCalendars to calendars
	repeat with thisCalendar in theCalendars
		set theTodos to thisCalendar's todos
		repeat with thisTodo in theTodos
			-- Using the todo's 'properties' returns unset properties as having the value 'missing value'.
			set {due date:startDate, completion date:endDate, summary:theSummary, url:theURL, description:theDescription} to properties of thisTodo
			make new event at end of thisCalendar's events with properties {summary:theSummary, start date:startDate, end date:endDate, url:theURL, description:theDescription, allday event:true}
		end repeat
	end repeat
	delete todos of every calendar
end tell

Edit: OK. The above script takes an age and seizes up with more than a few todos. Here’s another that does edit the .ics file directly, but, for safety, saves the edited text in another file (with a modified name) in the same folder. After running, move the original file out of the folder and give its name to the edited file. The script works with at least 9035 todos in one calendar and doesn’t take very long with these. Hopefully, it’ll manage the 21000…

Further edit: Second script revamped in the light of Kurt’s later comments and posted in message #4 below.

Hi Nigel,

Thank you for posting the two AppleScripts. I really appreciate the effort that you’ve expended to create them. My gratitude. Interestingly enough, after I saw your first script posting, the light bulb turned on and I thought that I could open the xxx.ics files and modify them with the simple replace command. When I did open the file, I discovered that the task required considerably more work than just simple replacements. However, we’re both on the same right track by focusing the .ics file rather than on iCal.

I spent a large chuck of yesterday using the second AppleScript. I have three CPO calendar files that I want to transfer to iCal.
The first file contains 282 ToDo items plus four events that I created yesterday for testing purposes.
The second file contains 550 ToDo items.
The third file contains 21,000 ToDo items.
I have been using your second script on only the first small file since I can review the results quickly.

By the way, I am using a spare iMac G5 that the owner is loaning to me indefinitely. I backup my iMac G4 (my primary computer) each night to a Firewire external hard drive using SuperDuper!. For my iCal endeavors, I disconnect the backup drive from the G4, connect it to the G5, then clone it to the G5, then disconnect it from the G5. In this way, I can make mistakes, or I can reestablish a baseline virgin configuration on the G5 at will, without affecting my primary Mac.

Here are my observations thus far:

1: All of my ToDo items in Chronos Personal Organizer span only a single day. This applies to all three calendar files and all ToDo items (282, 550, 21,000). However, after performing all of the conversions (CPO to CSOHOO, CSOHOO to iCal, iCal ToDo to iCal Event) iCal displays the newly created Event items as spanning two days in the monthly calendar view.

When I open the final “.ics” file (tfi.ics, 282 Event items that the script converted from 282 previous ToDo items, plus four Events that I created yesterday as an aid in troubleshooting) I notice that the newly created Event records contain an additional “DTSTART;VALUE=DATE:YYYYMMDD” entry whose value is identical to the preceding “DTSTART;VALUE=DATE:YYYYMMDD” entry. Also, each of these records contain a “DTEND;VALUE=DATE:YYYYMMDD” entry that is incremented by two days over the “DTSTART” value. This confuses me since the newly created Event items only span two days on the iCal month view calendar, and since when I click on these Event items the description contains two contiguous days for “from” and “to”, not three. For example, the tfi.ics file contains the following data for the Event “Paint guard rail”:

 DTSTART;VALUE=DATE:19941126
 DTSTART;VALUE=DATE:19941126
 DTSTART;VALUE=DATE:19941128

However the iCal Event summary contains the following data:

 all-day  checked
 from  1994/11/26
 to    1994/11/27

I checked the first tfi.ics file that had not been modified by your script. It contained the following data:

 DTSTART;VALUE=DATE:19941126
 DUE;VALUE=DATE:19941126
 COMPLETED:19941126T050000Z

2: I examined the records in the initial unmodified tfi.ics file and the final script-modified tfi.ics file. As I said, I created four Events yesterday in this fourteen year old calendar file to help me to troubleshoot, if necessary. It came in handy. Here is what I found:

Comparison of an “Event” item before and after modification by the script.

 <<Original "Event" in the unmodified tfi.ics file.>>
 BEGIN:VEVENT
 DTSTAMP:20080525T133157Z
 UID:B2168911-98B2-4E92-86EC-EFBB1F9DB62D\:PGCalendarEvent
 SEQUENCE:1
 SUMMARY:This is a test of EVENT on 1994-05-29
 DTSTART;VALUE=DATE:19941129
 DTEND;VALUE=DATE:19941130
 PRIORITY:1
 END:VEVENT

 <<Original "Event" in the script-modified tfi.ics file.>>
 BEGIN:VEVENT
 DTSTAMP:20080525T133157Z
 UID:B2168911-98B2-4E92-86EC-EFBB1F9DB62D\:PGCalendarEvent
 SEQUENCE:1
 SUMMARY:This is a test of EVENT on 1994-05-29
 DTSTART;VALUE=DATE:19941129
 DTEND;VALUE=DATE:19941130
 PRIORITY:1
 END:VEVENT
 <<The script correctly did not modify this Event record.>>

Comparison of a “ToDo” item before and after modification by the script.

 <<Original "ToDo" in the unmodified tfi.ics file.>>
 BEGIN:VTODO
 DTSTAMP:20080525T133157Z
 UID:973D57D1-6B1F-4C96-9558-6B0F7F4D53F4\:PGCalendarTask
 SEQUENCE:1
 SUMMARY:Dig footings yesterday
 DESCRIPTION:To-Do
 DTSTART;VALUE=DATE:19940401
 DUE;VALUE=DATE:19940401
 COMPLETED:19940401T050000Z
 STATUS:COMPLETED
 PRIORITY:1
 END:VTODO

 <<Original "ToDo" in the script-modified tfi.ics file.>>
 BEGIN:VEVENT
 DTSTAMP:20080525T133157Z
 UID:973D57D1-6B1F-4C96-9558-6B0F7F4D53F4\:PGCalendarTask
 SEQUENCE:1
 SUMMARY:Dig footings yesterday
 DESCRIPTION:To-Do
 DTSTART;VALUE=DATE:19940401
 PRIORITY:1
 DTSTART;VALUE=DATE:19940401
 DTEND;VALUE=DATE:19940403
 END:VEVENT
 <<Notice that the "Priority" entry occupies a different
 position within the second record.>>
 <<"Status:Completed" is not present in the second file.>>
 <<"Completed:YYYYMMDDTHHMMSSZ" is replaced with 
 "DTEND;VALUE=DATE:YYYYMMDD" in the second record.>>
 <<"DTSTART;VALUE=DATE:YYYYMMDD" appears twice in 
 the record in the second file.>>
 <<By the way, is the entry "DESCRIPTION:" synonymous
 with "Notes"?  The word "To-Do" appears in the notes
 section of CSOHOO, and also in what appears to be a
 notes section of iCal.>>

Finally, in the script-modified file tfi.ics, the VEVENT records that the script created from the previous VTODO records do not follow the protocol of the VEVENT records that the script did not touch. Should they?

I have reviewed your code in the second script, but alas, I am about as lost as a ball in tall grass. I hope that the observations that I have provided to you will help. Please feel free to contact me with any questions or issues.

Best wishes.

Regards,

Kurt R. Todoroff

Hi, Kurt.

Sorry the script wasn’t quite right. Thanks very much for your detailed feedback, which led me almost immediately to a possible solution. Unfortunately, I couldn’t post it yesterday because the site server was unreachable all day. I’m hopeful from what you’ve told me that this version will be more successful and perhaps a little faster than the original:

-- Get the possibly more than 4000 paragraphs from a text.
-- (Gets round a limit on pre-Tiger systems and turns out to be faster than a single command in Tiger anyway.)
on getParas(txt)
	set theParas to {}
	set len to (count txt's paragraphs)
	
	repeat with i from 1 to len by 3900
		set j to i + 3899
		if (j > len) then set j to len
		set theParas to theParas & paragraphs i thru j of txt
	end repeat
	
	return theParas
end getParas

-- Get the possibly more than 4000 texts from a list containing both texts and other items.
on getTextsFromList(lst)
	set theTexts to {}
	set len to (count lst's text)
	
	repeat with i from 1 to len by 3900
		set j to i + 3899
		if (j > len) then set j to len
		set theTexts to theTexts & text i thru j of lst
	end repeat
	
	return theTexts
end getTextsFromList


on main()
	-- Best to quit iCal while editing a calendar file.
	tell application "System Events" to set iCalRunning to (application process "iCal" exists)
	if (iCalRunning) then
		tell application "iCal" to quit
		tell application "System Events"
			repeat while (application process "iCal" exists)
				delay 0.2
			end repeat
		end tell
	end if
	
	script o
		property monthLengths : {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
		property paras : missing value
	end script
	
	-- Choose the calendar file.
	set icsFile to (choose file with prompt "Choose an .ics file")
	set inputPath to icsFile as text
	-- The edited version will be saved as a separate file to preserve the original.
	-- To use it, move the original out of the folder and edit the new file's name.
	set outputPath to text 1 thru -5 of inputPath & " (edited).ics"
	
	-- Read the text from the original file and get a list of the individual lines (paragraphs).
	set icsTxt to (read icsFile as string)
	set o's paras to getParas(icsTxt)
	
	set astid to AppleScript's text item delimiters
	
	set AppleScript's text item delimiters to ":"
	set handlingTodo to false
	considering case
		-- Check each line in turn.
		repeat with i from 1 to (count o's paras)
			set thisPara to item i of o's paras
			
			if (thisPara begins with "BEGIN:VTODO") then
				-- Replace a todo header with an event header. Flag that we're handling a todo.
				set item i of o's paras to "BEGIN:VEVENT"
				set handlingTodo to true
			else if (handlingTodo) then
				-- Only do the following checks when handling a todo.
				if (thisPara begins with "END:VTODO") then
					-- Replace a todo footer with an event footer. Cancel the "handling todo" flag.
					set item i of o's paras to "END:VEVENT"
					set handlingTodo to false
				else if (thisPara begins with "DUE;") then
					-- Replace a todo's DUE line with a DTSTART line indicating the same date 
					-- and a DTEND line indicating the following day. The dates not having times,
					-- the result will be an all-day event in iCal, with the original due date.
					set startISOT to text 1 thru 8 of text item 2 of thisPara
					set DTSTARTline to "DTSTART;VALUE=DATE:" & startISOT
					-- Add one day to the yyyymmdd start date, adjusting month and year as necessary.
					set endISOT to startISOT + 1
					set y to endISOT div 10000
					set m to endISOT mod 10000 div 100
					if (m is 2) and (y mod 4 is 0) and ((y mod 100 > 0) or (y mod 400 is 0)) then
						set mLen to 29
					else
						set mLen to item m of o's monthLengths
					end if
					if (endISOT mod 100 > mLen) then set endISOT to endISOT - mLen + 100
					if (endISOT mod 10000 > 1231) then set endISOT to endISOT + 8800
					set DTENDline to "DTEND;VALUE=DATE:" & endISOT
					
					set item i of o's paras to DTSTARTline & return & DTENDline
				else if (thisPara begins with "STATUS:") or (thisPara begins with "DTSTART") or (thisPara begins with "COMPLETED:") or (thisPara begins with "PRIORITY:") then
					-- Substitute 'missing value' for any todo lines beginning with these terms.
					set item i of o's paras to missing value
				end if
			end if
		end repeat
		
		-- Rejoin the edited lines, omitting any that were replaced with 'missing value'.
		set AppleScript's text item delimiters to return
		set icsTxt to getTextsFromList(o's paras) as text
	end considering
	
	set AppleScript's text item delimiters to astid
	
	-- Save the edited text to a new file.
	set fref to (open for access file outputPath with write permission)
	try
		set eof fref to 0
		write icsTxt as string to fref
	end try
	close access fref
end main

main()
display dialog "Done!" with icon note

Hi Nigel,

Great work on the changes! I wanted the script to retain the “PRIORITY:X” line in the .ics file, so while still being lost as a ball in tall grass, I took a chance and replaced it with “DESCRIPTION:To-Do” since I didn’t want “DESCRIPTION:To-Do” in the .ics file. It worked.

I have a couple of questions for your regarding the .ics file structure and commands, which I am not familiar with.

1: I have noticed many occurrences of the character string " : " in text fields in the context that I am confident should only be " : ". Do vcal compliant calendars store the colon as " : " and then display it onscreen as a proper colon? I usually see this in the DESCRIPTION and SUMMARY lines. I am not referring to the " : " characters in the UID strings.

2: I have noticed many occurrences of the following line:

      DESCRIPTION:TEXT TEXT TEXT To-Do
      or
      DESCRIPTION:TEXT TEXT TEXTTo-Do
      or
      DESCRIPTION:TEXT TEXT TEXT. To-Do
      or
      DESCRIPTION:TEXT TEXT TEXT.To-Do

      or

      DESCRIPTION:TEXT TEXT TEXT Event
      or
      DESCRIPTION:TEXT TEXT TEXTEvent
      or
      DESCRIPTION:TEXT TEXT TEXT. Event
      or
      DESCRIPTION:TEXT TEXT TEXT.Event

As I have modified your script to remove “DESCRIPTION:To-Do”, I wish to remove “To-Do” and “Event” from the end of the lines that begin with “DESCRIPTION:TEXT TEXT TEXT”. I wish to retain the descriptive TEXT, but not the “To-Do” and “Event” suffixes that the original Chronos conversion software has obviously appended to these lines. Can AppleScript do this?

Your revised script is wonderful. I am hopeful that these changes are do-able.

Best wishes.

Regards,

Kurt Todoroff

By the way,

Can you help me to understand a component of your AppleScript? My question reflects my curiosity. I am not critical of your code. Keep in mind my exceedingly limited AppleScript knowledge.

I’m curious about the following line in your script:

 else if (thisPara begins with "STATUS:") or 
 (thisPara begins with "DTSTART") or 
 (thisPara begins with "COMPLETED:") or 
 (thisPara begins with "PRIORITY:") then
 -- Substitute 'missing value' for any todo lines beginning with these terms.

In this line, what is the purpose of the parenthetical:

 or (thisPara begins with "DTSTART") ?

I am confused that your script produces a resultant file that contains the lines:

 DTSTARTXXXXX

but does not contain the lines:

 STATUSXXXXX

or

 COMPLETEDXXXXX

or

 PRIORITYXXXXX

Why doesn’t the file remove the “DTSTART” lines from the file? Obviously my file requires “DTSTART”. So, this is not a complaint. I am just in learning mode.

Thank you, Nigel.

Best wishes.

Regards,

Kurt Todoroff

I presume you mean you replaced “DESCRIPTION:To-Do” with “PRIORITY:X”. :confused:

Events (in iCal, at least) don’t have “priorities”, but todos do. I made the script remove the “PRIORITY” lines to avoid possible confusion. With both events and todos, a “DESCRIPTION” is equivalent to the “Notes” in iCal’s user interface. I don’t really understand what you’ve done, but if it’s worked for you, I imagine it’s OK. :slight_smile:

I’m afraid I don’t know anything about the vcalendar standard and most of what I know about the icalendar standard comes from looking at .ics files created by iCal! A quick test shows that “:” in an .ics file appears as “:” in iCal’s text fields, so you probably don’t want it. According to iCal’s Help, iCal can import vcalendar (.vcs) files, so if your files are in that format, it might be worth importing them into iCal first and seeing if iCal creates equivalent .ics files for its own use. Then you can run my script on those .ics files. Otherwise, AppleScript can easily convert “:” to “:”, but I’d want to see an example of how it appears in one of the todos in your files.

It might slow things down a little, but yes. I’ve assumed below that “To-Do” and “Event” are at the very ends of their lines (not followed by spaces). The process also deals with your “DESCRIPTION:To-Do” lines.

The script creates its own “DTSTART” lines from the file’s “DUE” lines and removes the "DTSTART"s that were there already. :slight_smile: When I was researching the script, I wrote an AppleScript that told iCal to create hundreds of todos in a calendar, so that I could see what they looked like in the file. The file contained lines beginning with “DTSTART;TZID=”, which didn’t all have the right dates, and lines beginning with “DUE”, which did. The original version of the script therefore derived the necessary “DTSTART;VALUE=” lines from the “DUE” lines and removed lines beginning with “DTSTART;TZID=”. However, you found you were then getting two “DTSTART;VALUE=” lines in each todo/event conversion (message #3 above), so your todos obviously contained “DTSTART;VALUE=” lines already. Since I didn’t know how consistently their dates matched the “DUE” dates, I decided to continue with the “DUE” conversions and to remove all pre-existing lines beginning with “DTSTART”.

I’ve now also modified the script so that it only removes “STATUS:COMPLETED” lines, rather than all “STATUS” lines, just in case that helps. :slight_smile:

(Further modified a few times to address the “backslashed colons” issue and to read and write ‘as «class utf8»’ rather than ‘as string’.)

-- Get the possibly more than 4000 paragraphs from a text.
-- (Gets round a limit on pre-Tiger systems and turns out to be faster than a single command in Tiger anyway.)
on getParas(txt)
	set theParas to {}
	set len to (count txt's paragraphs)
	
	repeat with i from 1 to len by 3900
		set j to i + 3899
		if (j > len) then set j to len
		set theParas to theParas & paragraphs i thru j of txt
	end repeat
	
	return theParas
end getParas

-- Get the possibly more than 4000 Unicode texts from a list containing both Unicode texts and other items.
on getUnicodeFromList(lst)
	set theTexts to {}
	set len to (count lst each Unicode text)
	
	repeat with i from 1 to len by 3900
		set j to i + 3899
		if (j > len) then set j to len
		set theTexts to theTexts & Unicode text i thru j of lst
	end repeat
	
	return theTexts
end getUnicodeFromList


on main()
	-- Best to quit iCal while editing a calendar file.
	tell application "System Events" to set iCalRunning to (application process "iCal" exists)
	if (iCalRunning) then
		tell application "iCal" to quit
		tell application "System Events"
			repeat while (application process "iCal" exists)
				delay 0.2
			end repeat
		end tell
	end if
	
	script o
		property monthLengths : {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
		property paras : missing value
	end script
	
	-- Choose the calendar file.
	set icsFile to (choose file with prompt "Choose an .ics file.")
	set inputPath to icsFile as Unicode text
	-- The edited version will be saved as a separate file to preserve the original.
	-- To use it, move the original out of the folder and edit the new file's name.
	set outputPath to text 1 thru -5 of inputPath & " (edited).ics"
	
	-- Read the text from the original file and get a list of the individual lines (paragraphs).
	set icsTxt to (read icsFile as «class utf8»)
	set o's paras to getParas(icsTxt)
	
	-- Predefine these texts to save thousands of background text-to-Unicode coercions during the repeat.
	-- Not necessary on Leopard systems, but the use of variables may save time even there.
	set BEGINVTODO to "BEGIN:VTODO" as Unicode text
	set BEGINVEVENT to "BEGIN:VEVENT" as Unicode text
	set ENDVTODO to "END:VTODO" as Unicode text
	set ENDVEVENT to "END:VEVENT" as Unicode text
	set DUE to "DUE;" as Unicode text
	set DTSTARTVALUEDATE to "DTSTART;VALUE=DATE:" as Unicode text
	set DTENDVALUEDATE to "DTEND;VALUE=DATE:" as Unicode text
	set STATUS to "STATUS:" as Unicode text
	set DTSTART to "DTSTART" as Unicode text
	set COMPLETED to "COMPLETED:" as Unicode text
	set PRIORITY to "PRIORITY:" as Unicode text
	set backslashColon to "\\:" as Unicode text
	set colon to ":" as Unicode text
	set CR to return as Unicode text
	
	set astid to AppleScript's text item delimiters
	
	set AppleScript's text item delimiters to colon
	set handlingTodo to false
	considering case
		-- Check each line in turn.
		repeat with i from 1 to (count o's paras)
			set thisPara to item i of o's paras
			
			if (thisPara begins with BEGINVTODO) then
				-- Replace a todo header with an event header. Flag that we're handling a todo.
				set item i of o's paras to BEGINVEVENT
				set handlingTodo to true
			else if (handlingTodo) then
				-- Only do the following checks when handling a todo.
				if (thisPara begins with ENDVTODO) then
					-- Replace a todo footer with an event footer. Cancel the "handling todo" flag.
					set item i of o's paras to ENDVEVENT
					set handlingTodo to false
				else if (thisPara begins with DUE) then
					-- Replace a todo's DUE line with a DTSTART line indicating the same date 
					-- and a DTEND line indicating the following day. The dates not having times,
					-- the result will be an all-day event in iCal with the original due date.
					set startISOT to text 1 thru 8 of text item 2 of thisPara
					set DTSTARTline to DTSTARTVALUEDATE & startISOT
					-- Add one day to the yyyymmdd start date, adjusting month and year as necessary.
					set endISOT to startISOT + 1
					set y to endISOT div 10000
					set m to endISOT mod 10000 div 100
					if (m is 2) and (y mod 4 is 0) and ((y mod 100 > 0) or (y mod 400 is 0)) then
						set mLen to 29
					else
						set mLen to item m of o's monthLengths
					end if
					if (endISOT mod 100 > mLen) then set endISOT to endISOT - mLen + 100
					if (endISOT mod 10000 > 1231) then set endISOT to endISOT + 8800
					set DTENDline to DTENDVALUEDATE & endISOT
					
					set item i of o's paras to DTSTARTline & return & DTENDline
				else if (thisPara begins with STATUS) or (thisPara begins with DTSTART) or (thisPara begins with COMPLETED) or (thisPara begins with PRIORITY) then
					-- Substitute 'missing value' for any todo lines beginning with these terms.
					set item i of o's paras to missing value
				else if (thisPara contains backslashColon) then
					-- Remove any backslashes before colons in a (presumed) SUMMARY or DESCRIPTION text.
					set AppleScript's text item delimiters to backslashColon
					set thisPara to thisPara's text items
					set AppleScript's text item delimiters to colon
					set item i of o's paras to thisPara as Unicode text
				end if
			end if
		end repeat
		
		-- Rejoin the edited lines, omitting any that were replaced with 'missing value'.
		set AppleScript's text item delimiters to CR
		set icsTxt to getUnicodeFromList(o's paras) as Unicode text
	end considering
	
	set AppleScript's text item delimiters to astid
	
	-- Save the edited text to a new file.
	set fref to (open for access file outputPath with write permission)
	try
		set eof fref to 0
		write icsTxt as «class utf8» to fref
	end try
	close access fref
end main

main()
display dialog "Done!" with icon note

Hi Nigel,

Your latest script works well. You have done a wonderful job on it, and you have solved a very large problem for me, saving me a lot of work.

I wish to express my gratitude to you. I really wish that I could do a Mac favor for you. This means a lot to me.

I have an AppleScript question to ask you. I pasted your code into an AppleScript file, saved the file, then saved it as an application. I ran the script application on my .ics files. When I double-clicked on the AppleScript file that contained your code, the AppleScript editor launched, and then opened the file, as i expected. However, when I double-click on one of the AppleScript code files that I have create in the past, the AppleScript editor launches, and the script runs. I don’t want the script to run. How do I prevent the script from running when double-click a script file to edit it?

Very best wishes to you.

Warm regards,

Kurt Todoroff

Hi, Kurt.

I’m glad the script does what you want. :slight_smile:

If you save a script as an application, double-clicking its file icon should run the application. If you save the script as a script or as text, double-clicking its icon should open it in the editor that created it: ie. Script Editor, in this case.

To open a script that’s been saved as an application, either use “Open.” (or perhaps “Open Recent”) in Script Editor’s “File” menu or drag the script’s file icon onto Script Editor’s file icon.

It’s not possible to open a script that’s been saved with the “Run Only” option checked.

Hi Nigel,

My interpretation of script operation is consistent with your description. However, one of my scripts does run when I attempt to open it by double-clicking on it.

I have created four scripts. After I saved each of the scripts as a script, I performed a Save As to save each one as an application. I double-click on the script application when I want to run it. (Usually Cron runs them for me.) I double-click on the script file when I want to edit its contents. (Then I Save As again with the same file name, overwriting the previous version.) However, double-clicking on one of these script files opens the script editor, then opens the script file, and then runs the script. C’est la vie.

By the way, I had previously shared with you that after I used the Chronos Organizer Converter to convert the Chronos Personal Organizer 4.5.0 data to Chronos SOHO Organizer 6.5.4 format, I noticed that the colons ( : ) that I had typed into the Summary or Description fields over the years, had been converted to backslash colons ( : ). After I used your wonderful script to perform the final conversion on the data, and then imported this file into iCal, I discovered that the " : " remained in the file but that iCal did not display them on the screen. That was yesterday. This morning, I launched iCal and sure enough it is now displaying the " : " again. I have published this calendar so that I can use it at work on my Windoze machine. The " : " appear there as well. I’m stumped. I’ve decided to try my hand at AppleScript, using what I’ve learned from your script, and create a script that will strip out the backslashes in only the Summary and Description lines.

iCal runs sluggish now that I’ve imported these 21,000 calendar items. My wife and I had hoped to replace our two iMac G4s with a pair of new Intel models this spring (I want a 24-inch iMac, she wants a MBP 17-inch). I don’t think that will happen this year. Our iMacs won’t support a Leopard upgrade. We’re stuck with sluggish.

Nigel, again, thank you for the script.

Best wishes.

Kurt Todoroff

Hi, Kurt.

I’ve now edited the script in message #7 above so that it also replaces any backslash-colons in SUMMARY and DESCRIPTION lines with plain colons.

For your convenience below is a reduced script that only does that, just in case you want to use it with the files produced by the earlier version of the full script.

-- Get the possibly more than 4000 paragraphs from a text.
-- (Gets round a limit on pre-Tiger systems and turns out to be faster than a single command in Tiger anyway.)
on getParas(txt)
	set theParas to {}
	set len to (count txt's paragraphs)
	
	repeat with i from 1 to len by 3900
		set j to i + 3899
		if (j > len) then set j to len
		set theParas to theParas & paragraphs i thru j of txt
	end repeat
	
	return theParas
end getParas

on main()
	-- Best to quit iCal while editing a calendar file.
	tell application "System Events" to set iCalRunning to (application process "iCal" exists)
	if (iCalRunning) then
		tell application "iCal" to quit
		tell application "System Events"
			repeat while (application process "iCal" exists)
				delay 0.2
			end repeat
		end tell
	end if
	
	script o
		property paras : missing value
	end script
	
	-- Choose the calendar file.
	set icsFile to (choose file with prompt "Choose an .ics file.")
	set inputPath to icsFile as text
	-- The edited version will be saved as a separate file to preserve the original.
	-- To use it, move the original out of the folder and edit the new file's name.
	set outputPath to text 1 thru -5 of inputPath & " (edited).ics"
	
	-- Read the text from the original file and get a list of the individual lines (paragraphs).
	set icsTxt to (read icsFile as «class utf8»)
	set o's paras to getParas(icsTxt)
	
	set astid to AppleScript's text item delimiters
	
	set AppleScript's text item delimiters to ":"
	considering case
		-- Check each line in turn.
		repeat with i from 1 to (count o's paras)
			set thisPara to item i of o's paras
			if (thisPara contains "\\:") and ((thisPara begins with "SUMMARY:") or (thisPara begins with "DESCRIPTION:")) then
				-- Remove any backslashes before colons in a SUMMARY or DESCRIPTION line.
				-- If the file's already been opened in iCal 1.5.5, the backslashes
				-- in the line will themselves have been escaped with backslashes.
				-- Each backslash in the group also has to be represented by an escaped backslash in AppleScript!
				set AppleScript's text item delimiters to "\\\\:"
				set thisPara to thisPara's text items
				set AppleScript's text item delimiters to "\\:"
				set thisPara to text items of (thisPara as text)
				set AppleScript's text item delimiters to ":"
				set item i of o's paras to thisPara as text
			end if
		end repeat
		
		-- Rejoin the edited lines.
		set AppleScript's text item delimiters to return
		set icsTxt to o's paras as text
	end considering
	
	set AppleScript's text item delimiters to astid
	
	-- Save the edited text to a new file.
	set fref to (open for access file outputPath with write permission)
	try
		set eof fref to 0
		write icsTxt as «class utf8» to fref
	end try
	close access fref
end main

main()
display dialog "Done!" with icon note

Thank you, Nigel!

Since I have already converted my data using your script, and then imported the resultant file into iCal, and have made several days of changes to my calendar (with daily backups), should I export my “Kurt” calendar and then run it through this new script and then re-import it into iCal, replacing my existing “Kurt” calendar?

Best wishes.

Regards,

Kurt Todoroff

Hi, Kurt.

I suppose you could. That’s probably the safest way. I’ve been messing around directly with iCal’s own copy of the .ics file. (That’s why the script makes sure iCal quits first!) With iCal 2.0.5, the .ics file’s in a folder that has a coded name with a “.calendar” extension in the user’s ~/Library/Application Support/iCal/Sources/ folder. There’s one such “.calendar” folder for each of the calendars you have in iCal. Inside are three files, including “corestorage.ics”, which is the .ics file for that calendar. The folder with the largest “corestorage.ics” is probably “Kurt”. :wink: Actually, if you’re using Tiger or later, this should identify the .ics file for you:

tell application "iCal" to set KurtID to uid of calendar "Kurt" -- Your calendar name here.

set iCalAppSupportPath to (path to application support from user domain as unicode text) & "iCal:"

tell application "System Events"
	repeat with thisPLitem in property list items of property list item "List" of property list file (iCalAppSupportPath & "nodes.plist")
		if (value of thisPLitem's property list item "Key" is KurtID) then
			set sourceID to value of thisPLitem's property list item "SourceKey"
			exit repeat
		end if
	end repeat
end tell

set calendarFile to (iCalAppSupportPath & "Sources:" & sourceID & ".calendar:corestorage.ics") as alias

tell application "Finder"
	activate
	reveal calendarFile
end tell

return calendarFile

You can check it’s the right file by opening it in a text editor and seeing if it contains the right events. The editing script creates a file called “corestorage (edited).ics” in the same folder. Move the original “corestorage.ics” file out of the folder, edit the " (edited)" out of the new file’s name, and reopen iCal. Hopefully, all will be well… :slight_smile:

It turns out that iCal treats the text in .ics files as utf8 Unicode rather than as ASCII text. I don’t think Kurt will be affected by this, but I’ve modified the scripts again in messages #7 and #11 to read and write ‘as «class utf8»’.

Hi Nigel,

I’ve opened and read the .ics file in TextEdit, and I’ve read your script, and I’ve reviewed both the original Personal Organizer data in calendar mode and iCal data in calendar mode, and I have concluded that:

 \,
 \"
 etcetera

are legitimate strings in the .ics file, but that:

 \\:

is not since iCal has not displayed the " , " string in text fields, but iCal has displayed the " : " string in text fields.

Is my conclusion correct?

Regards,

Kurt Todoroff

By the way, I just ran your latest standalone script that removes the backslashes and discovered that 75 occurrences of \: remained in the file (out of at least a thousand). As I checked closer, I discovered that each of the remaining \: strings reside on a second wrapped line. For example,

the script properly modified the entry:

 John\\: aced his test.

the script properly modified the entry:

 John\\: aced his test that he took at school on Wednesday at 3\\:30 PM.

the script properly modified the entry:

 John\\: aced his test that he took at school on Wedne
 sday after classes.  [intentional break in Wednesday, like in the file]

the script properly modified the first line in this entry, but not the second line:

 John\\: aced his test that he took at school on Wedne
 sday after classes at 3\\:30 PM.  [intentional break in Wednesday, like in the file]

the script properly modified the first line in this entry, but not the second line:

 John\\: aced his test that he took at school on Wedne
 sday after classes from 3\\:30 PM to 5\\:30 PM.  [intentional break in Wednesday, like in the file]

Also, I am unsure of the purpose of the new script that you added in #13.

Regards,

Kurt Todoroff

Hi, Kurt.

Yes, I think so. On my machines, it seems that if a comma, a double-quote, a semicolon, or a backslash is typed into an iCal text field, iCal “escapes” it in the .ics file by inserting a backslash before it. It does not escape a colon, even though this has significance in the grammar of the .ics structure.

\,
\"
\;
\\

but :

iCal understands the first four characters whether or not they’re escaped in the file and correctly displays them in its text fields. But if a colon is preceded by a backslash in the file, iCal displays the backslash as a separate character.

Additionally, if the backslash is omitted before an escapable character in the file, iCal 1.5.5 inserts the backslash into the file when it quits (but iCal 2.0.5 doesn’t).

Ah. For safety, the script only modifies lines beginning with “SUMMARY:” or “DESCRIPTION:”. If there’s a deliberate break in the text, the bit after the break will be another line that doesn’t begin with either of these! (iCal itself uses the sequence \n rather than an actual break, so the script wouldn’t divide its split lines into separate entities.)

But I’m fairly confident now that your backslash-colon sequences will only occur in text fields, so we could perhaps ignore lines altogether:

-- Get the possibly more than 4000 text items from a text, using the current delimiter.
-- (Gets round a limit on pre-Tiger systems and turns out to be faster than a single command in Tiger anyway.)
on getTextItems(txt)
	set theTIs to {}
	set len to (count txt's text items)
	
	repeat with i from 1 to len by 3900
		set j to i + 3899
		if (j > len) then set j to len
		set theTIs to theTIs & text items i thru j of txt
	end repeat
	
	return theTIs
end getTextItems

on main()
	-- Best to quit iCal while editing a calendar file.
	tell application "System Events" to set iCalRunning to (application process "iCal" exists)
	if (iCalRunning) then
		tell application "iCal" to quit
		tell application "System Events"
			repeat while (application process "iCal" exists)
				delay 0.2
			end repeat
		end tell
	end if
	
	-- Choose the calendar file.
	set icsFile to (choose file with prompt "Choose an .ics file.")
	set inputPath to icsFile as Unicode text
	-- The edited version will be saved as a separate file to preserve the original.
	-- To use it, move the original out of the folder and edit the new file's name.
	set outputPath to text 1 thru -5 of inputPath & " (edited).ics"
	
	set icsTxt to (read icsFile as «class utf8»)
	
	set astid to AppleScript's text item delimiters
	
	considering case
		-- Replace any instances of \\: or \: in the text with :
		set AppleScript's text item delimiters to "\\\\:"
		set textItems to getTextItems(icsTxt)
		set AppleScript's text item delimiters to "\\:"
		set textItems to getTextItems(textItems as Unicode text)
		set AppleScript's text item delimiters to ":"
		set icsTxt to textItems as Unicode text
	end considering
	
	set AppleScript's text item delimiters to astid
	
	-- Save the edited text to a new file.
	set fref to (open for access file outputPath with write permission)
	try
		set eof fref to 0
		write icsTxt as «class utf8» to fref
	end try
	close access fref
end main

main()
display dialog "Done!" with icon note

It just reveals the .ics file associated with your “Kurt” calendar in iCal, in case you wanted to use the backslash removing script with it directly, to save you having to export.

Hi Nigel,

I used your AppleScript that searches for " \: " strings only in lines that begin with “SUMMARY:” or “DESCRIPTION:”, not your more recent script that searches for " \: " strings in all lines, because you had not updated it at that time. Then I used TextEdit’s “find and replace” to remove the remaining 72 " \: " strings. I launched iCal and then checked all three calendars (282 items, 555 items, 21,000 items) which span 15 years. Everything looks great! Thank you, Nigel.

I decided to not save your newer code that searches all lines for the " \: " string. As silly as it may seem, I would prefer this security in the EXTREMELY unlikely event that a legitimate " \: " string needs to be retained. That being said, I’ll probably not have a need to use these scripts again. Pity. You’ve written great scripts. I recommend that you add them to VersionTracker. Remember, I used Chronos Organizer Converter 6.5.2 to convert my older Chronos Personal Organizer 4.5.0 calendar file to Chronos SOHO Organizer 6.5.4 file format. Then I exported it from CSOHOO654 to filename.ics format. That’s where your scripts came in. Your scripts helped me where Chronos technical support was unable to do so.

A few curiosity questions:

Did you test your latest code on .ics files in the Sources folder?

Did you incorporate these latest changes into the original longer script?

Vis-a-vis UTF-8, was it okay that I made the replacement changes to my .ics file using TextEdit?

Here is a confusing issue for me: After I ran your script last weekend on my spare iMac G5, iCal ran slower. I mentioned this in one of my postings. I used Google to search for other people who had experienced this problem. I found several suggestions to solve the sluggishness problem. Most of the suggestion seemed cumbersome. One of them seemed very simple. The poster stated that he had quit iCal, moved the Sources folder from ~Library/Application Support/iCal into the trash, and then launched iCal. He suggested waiting several minutes patiently while iCal rebuilt the calendars, then evaluate the new speed results. I performed his suggested procedure. Sure enough, iCal rebuilt the calendars correctly. I examined all three calendars for errors and omissions. I found none. I’ve repeated this procedure several times on the spare iMac G5. Every time, iCal rebuilt the calendars properly. If I am deleting the calendars that iCal needs to load at launch, then how does it rebuild them?

Regards,

Kurt Todoroff

Hi, Kurt.

I’m glad everything’s as you want it now. :slight_smile:

That’s OK. I often write one-off scripts to do things that would take me too long to do otherwise. (Usually, it takes longer to write the script than to perform the task, but it turns a chore into something interesting. ;)) In this particular case, I’ve learned a few things about iCal’s .ics files along the way.

Yes. There was no danger of irreversible fatal errors, since the script(s) don’t write to the original files. The main difficulty with testing any of the scripts is that I’ve been working with files created by iCal itself, so I’ve had to guess to a certain extent what might be in your Chronos conversions.

Yes. That still works on a line-by-line basis, but doesn’t insist on a line beginning with anything in particular if it detects a backslash-colon combination. (It does assume though, that there won’t be any backslash-backslash-colon sequences in the original file.) However, since the backslash-colon test is the last of a series of else ifs, it isn’t applied to any lines that have been identified and handled by the tests above it.

I think so. The original ‘string’ versions of the scripts were probably OK too. I just changed them for safety and, of course, correctness. :wink: The edits only occur in text that’s the same in both UTF-8 and string formats, and software that thinks ‘string’ throughout will see the more exotic UTF-8 characters (representing diacriticals, non-Latin characters, etc.) merely as sequences of other, admittedly strange, characters. So these characters are unlikely to have been affected and will only be in the file in the first place if you’ve used any in your summaries or descriptions.

Wow! I’ve noticed that if I remove an .ics file from its folder, iCal generates an empty replacement calendar when it starts up. It also creates a new calendar if I forget to rename the .ics file produced by the scripts. But I haven’t tried removing the Sources folder altogether. iCal must also keep the information in quick-access database form somewhere else on the disk. That’s a very interesting tip. Thanks for passing it on. Did it give the speed improvement you wanted?

Hi Nigel,

I don’t think that I can discern a speed improvement after moving the Sources folder to the Trash. I’ve tried this on both the iMac G4 and the iMac G5.

By the way, after reviewing my most recent post to you, I think that I may have used poor language in describing the speed situation. My posting could be interpreted to suggest that your script caused the sluggish iCal performance. This is NOT the case. I did not mean to suggest this. Poor language on my part. My apologies. The slowdown did indeed occur after I used your script, however, that would only be correlation. The causation is the fact that I added 21,000 calendar items to iCal.

While perusing the .ics files, I’ve noticed that the calendar items, both Events and To-Dos, do not occur in chronological order within the .ics file. I’m unsure if this was how Chronos Personal Organizer stored the data, or if the Chronos Organizer Converter did this, or if iCal stores the data this way. If these items were sorted in chronological order, do you think that my calendars would load faster at iCal launch, and do you think that iCal would be more responsive?

Thank you again for the great work.

Best wishes to you, Nigel.

Warm regards,

Kurt Todoroff