Help us understand the problem. What is going on with this article?

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を使用
szk-3
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away