Cocoa, Applescript, Unicode & Date Formats

Sunday, 18 February 2007

I've just fixed a bug in Do It where some users were finding the 'Add deadline reminder in iCal' function wasn't working.  This took me by surprise at first, since it worked OK for me.  Then I realized that the people who had reported in were from Canada, Japan and Italy - so I figured that problem was down to local date and time formats.


The add to iCal function uses Applescript to pass the info between the two applications.  In Do It (written in Obj-C with Cocoa) the method was:




    NSDate *deadline=[toDo valueForKey:@"deadline"];




    NSDictionary *localeInfo=[[NSUserDefaults standardUserDefaults] dictionaryRepresentation];

    NSString *title=[toDo valueForKey:@"thingToDo"];

    NSCalendarDate *start=[deadline dateWithCalendarFormat:nil timeZone:nil];

    NSCalendarDate *end=[start dateByAddingYears:0 months:0 days:0 hours:1 minutes:0 seconds:0];

    NSString *startString=[start descriptionWithCalendarFormat:@"%A, %B %e, %Y %H:%M:%S"] locale:localeInfo];

    NSString *endString=[end descriptionWithCalendarFormat:@"%A, %B %e, %Y %H:%M:%S"] locale:localeInfo];

    NSString *path=[[NSBundle mainBundle] pathForResource:@"AddToICalScript" ofType:@"txt"];

    NSError *readError=nil;

    NSString *scriptTemplate=[NSString stringWithContentsOfFile:path encoding:NSUnicodeStringEncoding error:&readError];

    NSString *scriptSource=[NSString stringWithFormat:scriptTemplate, title, startString, endString];

    NSAppleScript *script=[[NSAppleScript alloc] initWithSource:scriptSource];

    NSDictionary *errorInfo=nil;

    [script executeAndReturnError:&errorInfo];

    [script release];



        NSLog(@"Applescript Error: %@", [errorInfo objectForKey:NSAppleScriptErrorBriefMessage]);

        NSLog(@"Applescript Error: %@", [errorInfo objectForKey:NSAppleScriptErrorMessage]);




The Applescript template that this method loads was:


tell application "iCal"


    set the calendar_names to the title of every calendar whose writable is true

    if the calendar_names is {} then error "iCal contains no writable calendars."

    set the calendar_name to (choose from list calendar_names with prompt "Select the calendar to create the event in") as string

    if the calendar_name is "false" then error number -128

    set this_calendar to (the first calendar whose title is the calendar_name)

    tell this_calendar

        set this_event to make new event at end of events

    end tell

    set summary of this_event to "%@"

    set start date of this_event to date "%@"

    set end date of this_event to date "%@"

end tell


With certain date formats selected in System Preference, the Applescript execution would fail with an "Invalid date and time" error.  However, changing the lines:


    NSString *startString=[start descriptionWithCalendarFormat:@"%A, %B %e, %Y %H:%M:%S"] locale:localeInfo];

    NSString *endString=[end descriptionWithCalendarFormat:@"%A, %B %e, %Y %H:%M:%S"] locale:localeInfo];


to this:


    NSString *startString=[start descriptionWithCalendarFormat:[localeInfo objectForKey:NSTimeDateFormatString] locale:localeInfo];

    NSString *endString=[end descriptionWithCalendarFormat:[localeInfo objectForKey:NSTimeDateFormatString] locale:localeInfo];



seemed to solve the problem.  I started going through all the format settings in System Preference, & it seemed to be working (including for Canada, Italy and Japan), until I hit Taiwan, and a few other Chinese based formats.  The script execution was failing with the same error as before.  I trip through all the strings in the debugger showed that the values of startString and endString were correct - for example 2007年2月18日星期日 下午06時08分28秒 and 2007年2月18日星期日 下午07時08分28秒 respectively.  I noticed that the Applescript error being returned included the message "Invalid date and time 2007îN2åé18ì˙êØä˙ì˙ â∫åfl06éû08ï™28ïb", so it seemed like there was a problem reading the unicode characters in the startString/endString values.  Looking at the value of the scriptSource string however, it included all the unicode characters, correctly formatted.


A bit of Googling revealed that unicode characters can be entered and and compiled OK in the Script Editor application, but that Applescript will parse them as Mac-Roman or UTF-8 unless explicitly told otherwise.  Applescript includes a type 'Unicode text', and strings can be parsed as unicode by casting them to this type with the command "... as Unicode text'.  Changing the lines to the add to iCal script template to the following:


    set summary of this_event to ("%@" as Unicode text)

    set start date of this_event to date ("%@" as Unicode text)

    set end date of this_event to date ("%@" as Unicode text)


forced applescript to read the unicode date strings correctly, and now the script seems to execute correctly in all the locales.