Edited at

RRULE文字列とEKRecurrenceRuleを変換するAppleScriptハンドラ

More than 3 years have passed since last update.


  • カレンダーやリマインダーを扱うEventKitフレームワーク


    • アプリケーションを起動しなくても操作できる

    • AppleScriptに対応していない項目を取得、設定できる



  • EverntKitフレームワークにあるイベントの繰り返しを管理するEKRecurrenceRuleクラス



    • initRecurrenceWithFrequency:interval:end:メソッドで作成



  • 一方で、一般的なカレンダーで繰り返しルールを表す文字列としてよく使われるRRULE (Recurrence Rule)


    • 例:FREQ=DAILY;INTERVAL=2は「隔日の繰り返し」を意味する


    • iCalendar RFCの一部



  • 両者を相互変換するAppleScriptハンドラを作成


    • EKRecurrenceRuleでは1日未満の繰り返しに対応していないため、FREQ=MINUTELYなどは無視

    • 週の開始曜日を決めるWKSTもEKRucurrenceRuleでは設定できない模様



use scripting additions

use framework "EventKit"
use framework "Foundation"

--Every other day
set recurrenceRule to my recurrenceRuleFromString("FREQ=DAILY;INTERVAL=2")
--> «class ocid» id «data optr00000000200393FFB97F0000»

my stringFromRecurrenceRule(recurrenceRule)
--> "FREQ=DAILY;INTERVAL=2"

on recurrenceRuleFromString(recur as text)
--require framework: EventKit, Foundation

--recurをパースしてdictionaryに変換
set RRULE to current application's NSMutableDictionary's dictionaryWithObject:1 forKey:"INTERVAL"
set scanner to current application's NSScanner's scannerWithString:(recur & ";")
repeat until scanner's atEnd as boolean
set {scanResult, |name|} to scanner's scanUpToString:"=" intoString:(reference)
set scanResult to scanner's scanString:"=" intoString:(missing value)
set {scanResult, value} to scanner's scanUpToString:";" intoString:(reference)
set scanResult to scanner's scanString:";" intoString:(missing value)
try

if |name|'s isEqualToString:"FREQ" then
set freqDictionary to (current application's NSDictionary's dictionaryWithObjects:{current application's EKRecurrenceFrequencyDaily, current application's EKRecurrenceFrequencyWeekly, current application's EKRecurrenceFrequencyMonthly, current application's EKRecurrenceFrequencyYearly} forKeys:{"DAILY", "WEEKLY", "MONTHLY", "YEARLY"})
set value to (freqDictionary's objectForKey:value)

else if |name|'s isEqualToString:"UNTIL" then
if (value's |length| as integer) = 8 then
set endDate to my dateFromStringUsingFormat(value, "yyyyMMdd")
else if (value's |length| as integer) = 15 then
set endDate to my dateFromStringUsingFormat(value, "yyyyMMdd'T'HHmmss")
else if (value's |length| as integer) = 16 then
set endDate to my dateFromStringUsingFormat(value, "yyyyMMdd'T'HHmmss'Z'")
set endDate to my shiftDateFromGMT(endDate)
end if
set value to (current application's EKRecurrenceEnd's recurrenceEndWithEndDate:endDate)
set |name| to "END"

else if |name|'s isEqualToString:"COUNT" then
set value to (current application's EKRecurrenceEnd's recurrenceEndWithOccurrenceCount:(value's integerValue))
set |name| to "END"

else if |name|'s isEqualToString:"INTERVAL" then
set value to value's integerValue

else if |name|'s isEqualToString:"BYDAY" then
set weekdayDictionary to (current application's NSDictionary's dictionaryWithObjects:{current application's EKSunday, current application's EKMonday, current application's EKTuesday, current application's EKWednesday, current application's EKThursday, current application's EKFriday, current application's EKSaturday} forKeys:{"SU", "MO", "TU", "WE", "TH", "FR", "SA"})
set recurrenceDayOfWeekArray to current application's NSMutableArray's array()
repeat with weekdaynum in (value's componentsSeparatedByString:",") as list
set |weekday| to (weekdayDictionary's objectForKey:(text -2 thru -1 of weekdaynum))
if (count weekdaynum) = 2 then
set recurrenceDayOfWeek to (current application's EKRecurrenceDayOfWeek's dayOfWeek:|weekday|)
else
set recurrenceDayOfWeek to (current application's EKRecurrenceDayOfWeek's dayOfWeek:|weekday| weekNumber:(text 1 thru -3 of weekdaynum as integer))
end if
(recurrenceDayOfWeekArray's addObject:recurrenceDayOfWeek)
end repeat
set value to recurrenceDayOfWeekArray

else if (current application's NSArray's arrayWithArray:{"BYMONTHDAY", "BYYEARDAY", "BYWEEKNO", "BYMONTH", "BYSETPOS"})'s containsObject:|name| then
set value to ((value's componentsSeparatedByString:",")'s valueForKeyPath:"self.integerValue")

else
set value to |name| & "=" & value
set |name| to "UNRECOGNIZED NAME"
end if

--RRULEに追加
(RRULE's setObject:value forKey:|name|)

--エラー
on error
error "Invalid " & |name| & " in Recurrence Rule: " & |name| & "=" & value
end try
end repeat

--EKRecurrenceRule作成
set nameSet to current application's NSSet's setWithArray:(RRULE's allKeys())
if nameSet's isSubsetOfSet:(current application's NSSet's setWithArray:{"FREQ", "INTERVAL", "END", "UNRECOGNIZED NAME"}) then
return current application's EKRecurrenceRule's alloc()'s initRecurrenceWithFrequency:(RRULE's objectForKey:"FREQ") interval:(RRULE's objectForKey:"INTERVAL") |end|:(RRULE's objectForKey:"END")
else
return current application's EKRecurrenceRule's alloc()'s initRecurrenceWithFrequency:(RRULE's objectForKey:"FREQ") interval:(RRULE's objectForKey:"INTERVAL") daysOfTheWeek:(RRULE's objectForKey:"BYDAY") daysOfTheMonth:(RRULE's objectForKey:"BYMONTHDAY") monthsOfTheYear:(RRULE's objectForKey:"BYMONTH") weeksOfTheYear:(RRULE's objectForKey:"BYWEEKNO") daysOfTheYear:(RRULE's objectForKey:"BYYEARDAY") setPositions:(RRULE's objectForKey:"BYSETPOS") |end|:(RRULE's objectForKey:"END")
end if
end recurrenceRuleFromString

on stringFromRecurrenceRule(recurrenceRule)
--require framework: Foundation
return (recurrenceRule's |description|'s componentsSeparatedByString:space)'s lastObject() as text
end stringFromRecurrenceRule

on dateFromStringUsingFormat(dateString as text, dateFormat as text)
--reference: UTS #35: Unicode Locale Data Markup Language - http://www.unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns
set dateFormatter to my currentDateFormatter(dateFormat)
return (dateFormatter's dateFromString:dateString) as date
end dateFromStringUsingFormat

on shiftDateFromGMT(aDate as date)
global timeToGMT
try
timeToGMT
on error number -2753
set timeToGMT to time to GMT
end try
return aDate + timeToGMT
end shiftDateFromGMT

on currentDateFormatter(dateFormat as text)
--require framework: Foundation
set dateFormatter to current application's NSDateFormatter's alloc()'s init()
set dateFormatter's locale to current application's NSLocale's currentLocale()
set dateFormatter's timeZone to current application's NSTimeZone's localTimeZone()
set dateFormatter's dateFormat to dateFormat
return dateFormatter
end currentDateFormatter


更新履歴


  • 2016-02-03: EventKitフレームワークを使ってstringFromRecurrenceRuleハンドラを作成

  • 2016-02-05: EKRecurrenceRuleをRRULE文字列に変換するstringFromRecurrenceRuleハンドラを作成

  • 2016-03-04: recurrenceRuleFromStringハンドラでrecurのパースにNSScannerを使用