Googleカレンダーみたいな繰り返し予定をRubyやRailsで扱う方法を解説します。

ice_cube gemは繰り返し予定用のRubyのライブラリです。ちなみにRailsとは直接は連携しませんが工夫することで使えます。

簡単な使い方

かなりざっくり言うと、ice_cubeは繰り返し予定に関するいろいろな条件を与えたとき、その条件に該当する日付に関する処理を行うことができるライブラリです。

ユースケースで考えたほうがわかりやすいかもしれないですね。

例えば、2018年4月1日以降の毎月1日に繰り返す予定を表すとしましょう。

1schedule = IceCube::Schedule.new(now = Date.new(2018, 4, 1))
2schedule.add_recurrence_rule IceCube::Rule.monthly

これでOK。ではschedule.all_occurrencesで全ての日付を取得してみましょう。

1schedule.all_occurrences
2ArgumentError: All recurrence rules must specify .until or .count to use `all_occurrences'

怒られました。それもそうで、終わりを決められるようなルールがない場合は無限の集合になってしまうからですね。繰り返し回数か終了日を設定するか、日付の問い合わせを期間指定にすることで日付のArrayを得ることができます。

 1schedules.occurrences(1.year.from_now)
 2=> [2018-04-01 00:00:00 +0900,
 3 2018-05-01 00:00:00 +0900,
 4 2018-06-01 00:00:00 +0900,
 5 2018-07-01 00:00:00 +0900,
 6 2018-08-01 00:00:00 +0900,
 7 2018-09-01 00:00:00 +0900,
 8 2018-10-01 00:00:00 +0900,
 9 2018-11-01 00:00:00 +0900,
10 2018-12-01 00:00:00 +0900,
11 2019-01-01 00:00:00 +0900,
12 2019-02-01 00:00:00 +0900,
13 2019-03-01 00:00:00 +0900,
14 2019-04-01 00:00:00 +0900]
1schedule.occurrences_between(Date.new(2018, 5, 1), Date.new(2018, 8, 31))
2=> [2018-05-01 00:00:00 +0900,
32018-06-01 00:00:00 +0900,
42018-07-01 00:00:00 +0900,
52018-08-01 00:00:00 +0900]
1schedule.first(4)
2=> [2018-04-01 00:00:00 +0900,
32018-05-01 00:00:00 +0900,
42018-06-01 00:00:00 +0900,
52018-07-01 00:00:00 +0900]

終わりがある繰り返し

繰り返し回数を指定してみます。

1schedule = IceCube::Schedule.new(now = Date.new(2018, 4, 1))
2schedule.add_recurrence_rule IceCube::Rule.monthly.count(4)
3schedule.all_occurrences
4=> [2018-04-01 00:00:00 +0900,
52018-05-01 00:00:00 +0900,
62018-06-01 00:00:00 +0900,
72018-07-01 00:00:00 +0900]

終了日を指定してみます。

1schedule = IceCube::Schedule.new(now = Date.new(2018, 4, 1))
2schedule.add_recurrence_rule IceCube::Rule.monthly.until(Date.new(2018, 5, 10))
3schedule.all_occurrences.count
4=> [2018-04-01 00:00:00 +0900,
52018-05-01 00:00:00 +0900]

除外日を設定する

Googleカレンダーなんかで繰り返し予定を作って、ある特定の日だけ予定を消そうとすると、このイベントだけ削除みたいなことができます。あれを実現できます。

4月1日から4回繰り返す、だけどその内の5月1日は無しにしてね、という感じでルールを指定します。

1schedule = IceCube::Schedule.new(now = Date.new(2018, 4, 1))
2schedule.add_recurrence_rule IceCube::Rule.monthly.count(4)
3schedule.add_exception_time(Date.new(2018, 5, 1))
4schedule.all_occurrences
5=> [2018-04-01 00:00:00 +0900,
62018-06-01 00:00:00 +0900,
72018-07-01 00:00:00 +0900]

繰り返しが3回しか現れないですね。

シリアライズする

これまでice_cubeのscheduleクラスに色々設定してきた繰り返しルールをyamlやical形式などでテキスト化できます。

このgemを調べるまでしらなかったのですが、繰り返しルールの記述はRFCで定義されているんですね。icalがこの記法でデータを持っているようです。

例えば先ほど除外日を使ったときのscheduleをyamlやicalにしてみます。

1schedule = IceCube::Schedule.new(now = Date.new(2018, 4, 1))
2schedule.add_recurrence_rule IceCube::Rule.monthly.count(4)
3schedule.add_exception_time(Date.new(2018, 5, 1))

yaml

 1---
 2:start_time: 2018-04-01 00:00:00.000000000 +09:00
 3:rrules:
 4- :validations: {}
 5  :rule_type: IceCube::MonthlyRule
 6  :interval: 1
 7  :count: 4
 8:rtimes: []
 9:extimes:
10- 2018-05-01 00:00:00.000000000 +09:00

ical

1DTSTART;TZID=JST:20180401T000000
2RRULE:FREQ=MONTHLY;COUNT=4
3EXDATE;TZID=JST:20180501T000000

またこれらの形式からscheduleクラスに戻すことも可能です。

1ical = "DTSTART;TZID=JST:20180401T000000\nRRULE:FREQ=MONTHLY;COUNT=4\nEXDATE;TZID=JST:20180501T000000"
2schedule = IceCube::Schedule.from_ical(ical)
3schedule.all_occurrences
4=> [2018-04-01 00:00:00 +0900,
52018-06-01 00:00:00 +0900,
62018-07-01 00:00:00 +0900]

特に編集しないアプリケーションであれば、yamlやicalの形式でDBに保存しておいて、使うときは再びscheduleに変換して使うことが可能です。

ただの紹介になっちゃったので詳しくはREADMEを見てもらうとして、今度は簡単なサンプルアプリを作って例を紹介してみようかなーと思っています。