カレンダー アプリケーションで定期的なイベントをモデル化する最良の方法は何ですか?
-
01-07-2019 - |
質問
定期的なイベントをサポートする必要があるグループ カレンダー アプリケーションを構築していますが、これらのイベントを処理するために私が考え出したソリューションはすべてハックのように思えます。どこまで先を見られるかを制限し、すべてのイベントを一度に生成できます。または、イベントを繰り返しとして保存し、カレンダーで先を見ているときに動的に表示することもできますが、誰かがイベントの特定のインスタンスの詳細を変更したい場合は、イベントを通常のイベントに変換する必要があります。
もっと良い方法があると思いますが、まだ見つかりません。特定のイベント インスタンスの詳細を変更または削除できる、定期的なイベントをモデル化する最良の方法は何ですか?
(私は Ruby を使用していますが、回答を制約しないでください。ただし、Ruby 固有のライブラリか何かがある場合は、知っておくと良いでしょう)。
解決
今後のすべての定期的なイベントには「リンク」の概念を使用します。これらはカレンダーに動的に表示され、単一の参照オブジェクトにリンクされます。イベントが発生するとリンクが切断され、イベントはスタンドアロン インスタンスになります。定期的なイベントを編集しようとすると、将来のすべての項目を変更するように求めるプロンプトが表示されます(つまり、単一のリンクされた参照を変更する)、またはそのインスタンスのみを変更する (この場合、これをスタンドアロン インスタンスに変換してから変更を加えます)。後者のケースでは、単一インスタンスに変換された将来のすべてのイベントを定期的なリストで追跡する必要があるため、少し問題があります。しかし、これは完全に実行可能です。
したがって、本質的には、単一インスタンスと定期的なイベントという 2 つのクラスのイベントがあります。
他のヒント
Martin Fowler - カレンダーの定期的なイベント いくつかの興味深い洞察とパターンが含まれています。
ラント gem はこのパターンを実装します。
定期的なイベントには多くの問題が発生する可能性があります。私が知っているいくつかの問題を紹介します。
解決策 1 - インスタンスがない
元の予定と定期的なデータを保存します。すべてのインスタンスを保存するのではありません。
問題点:
- 必要なときに日付ウィンドウ内のすべてのインスタンスを計算する必要があり、コストがかかります
- 例外を処理できません (例:インスタンスの 1 つを削除するか、移動します。むしろ、このソリューションではこれを行うことはできません)
解決策 2 - インスタンスをストアする
1 からすべてを保存しますが、すべてのインスタンスも元の予定にリンクして保存します。
問題点:
- 多くのスペースを必要とします(ただし、スペースは安価なので、それほど重要ではありません)
- 特に例外を作成した後に元の予定に戻って編集する場合、例外は適切に処理される必要があります。たとえば、3 番目のインスタンスを 1 日先に移動した場合、戻って元の予定の時間を編集し、元の日に別の予定を再挿入し、移動した予定をそのままにしておく場合はどうなるでしょうか。移動したもののリンクを解除しますか?移動したものを適当に変更してみてはいかがでしょうか?
もちろん、例外を行わない場合は、どちらのソリューションも問題なく、基本的には時間と空間のトレードオフ シナリオから選択します。
iCalendar ソフトウェアの実装または標準自体を確認するとよいでしょう (RFC 2445 RFC 5545)。すぐに思い浮かぶのは Mozilla プロジェクトです。 http://www.mozilla.org/projects/calendar/ 簡単な検索で明らかになります http://icalendar.rubyforge.org/ 同じように。
イベントを保存する方法に応じて、他のオプションを検討できます。独自のデータベース スキーマを構築していますか?iCalendar ベースのものなどを使用していますか?
私は次のように取り組んでいます。
- http://github.com/elevation/event_calendar - カレンダーのモデルとヘルパー
- http://github.com/seejohnrun/ice_cube - 素晴らしい定期的な宝石
- http://github.com/justinfrench/formtastic - 簡単なフォーム
そして、入力タイプ:quirting (form.schedule :as => :recurring
)、iCal のようなインターフェイスと before_filter
ビューをシリアル化して IceCube
再び反対します、ゲットー的に。
私のアイデアは、繰り返しの属性をモデルに追加し、ビュー内で簡単に接続できるようにすることです。すべては数行で書かれています。
それで、これは私に何をもたらしますか?インデックス付き、編集可能、繰り返しの属性。
events
1日インスタンスを保存し、カレンダービュー/ヘルパーで使用されています task.schedule
yaml で指定されたものを保存します IceCube
オブジェクトなので、次のような呼び出しを行うことができます。 task.schedule.next_suggestion
.
要約:私は 2 つのモデルを使用しており、1 つはカレンダー表示用にフラットで、もう 1 つは機能用に属性付きです。
私は複数のカレンダーベースのアプリケーションを開発し、繰り返しをサポートする再利用可能な JavaScript カレンダー コンポーネントのセットも作成しました。概要を書きました 再発をどう設計するか それは誰かの役に立つかもしれません。私が作成したライブラリに固有の部分もいくつかありますが、提供されるアドバイスの大部分は、カレンダーの実装に一般的なものです。
重要なポイントのいくつか:
- を使用して繰り返しを保存します。 iCal RRULE 形式 -- それは本当に再発明したくない車輪の 1 つです
- 個別の定期的なイベントを保存しないでください インスタンス データベースの行として!常に繰り返しパターンを保存します。
- イベント/例外スキーマを設計するにはさまざまな方法がありますが、基本的な開始点の例が提供されています。
- すべての日付/時刻値は UTC で保存され、表示のためにローカルに変換される必要があります。
- 定期的なイベントに対して保存される終了日は、常に 繰り返し範囲の終了日 (または、「永久に」繰り返される場合はプラットフォームの「最大日付」) とイベント期間は別々に保存する必要があります。これは、後でイベントをクエリするための適切な方法を確保するためです。
- イベント インスタンスの生成と繰り返しの編集戦略に関するいくつかの説明が含まれています。
これは、実装するための有効なアプローチが数多く存在する非常に複雑なトピックです。私は実際に再発の実装に何度か成功してきたと言っておきますが、実際に再発を行ったことのない人からこの件に関するアドバイスを受けることには慎重です。
以下で説明するデータベース スキーマを使用して、繰り返しパラメータを保存しています。
http://github.com/bakineggs/quirting_events_for
次に、runt を使用して動的に日付を計算します。
- 繰り返しルールを追跡します (@ ごとに、おそらく iCalendar に基づいています)クリス・K)。これにはパターンと範囲が含まれます (毎月第 3 火曜日、10 回発生)。
- 特定の出来事を編集/削除する場合に備えて、上記の繰り返しルールの例外日 (イベントが発生した日付) を追跡してください。 しません ルールの指定どおりに発生します)。
- 削除した場合は、それだけで済みます。編集した場合は、別のイベントを作成し、メイン イベントに設定された親 ID をそれに与えます。メイン イベントのすべての情報をこのレコードに含めるか、変更のみを保持し、変更されないものはすべて継承するかを選択できます。
終わりのない繰り返しルールを許可する場合は、無限になった情報を表示する方法を考える必要があることに注意してください。
お役に立てば幸いです!
日付ライブラリの力と Ruby の range モジュールのセマンティクスを使用することをお勧めします。定期的なイベントは、実際には時間、日付範囲 (開始と終了)、そして通常は単一の曜日です。日付と範囲を使用すると、あらゆる質問に答えることができます。
#!/usr/bin/ruby
require 'date'
start_date = Date.parse('2008-01-01')
end_date = Date.parse('2008-04-01')
wday = 5 # friday
(start_date..end_date).select{|d| d.wday == wday}.map{|d| d.to_s}.inspect
イベントの全日程をプロデュースし、 含む うるう年!
# =>"[\"2008-01-04\", \"2008-01-11\", \"2008-01-18\", \"2008-01-25\", \"2008-02-01\", \"2008-02-08\", \"2008-02-15\", \"2008-02-22\", \"2008-02-29\", \"2008-03-07\", \"2008-03-14\", \"2008-03-21\", \"2008-03-28\"]"
これらの回答から、私は解決策をある程度選び出しました。リンクのコンセプトがとても気に入っています。繰り返し発生するイベントは、その繰り返しルールを知っている末尾を持つリンクされたリストにすることができます。リンクはそのまま残るため、1 つのイベントを変更するのは簡単です。また、イベントの削除も簡単です。イベントのリンクを解除して削除し、その前後のイベントを再リンクするだけです。誰かがカレンダー上でこれまでに見たことのない新しい期間を確認するたびに、定期的なイベントをクエリする必要がありますが、それ以外の点では、これは非常に簡単です。
イベントを繰り返しとして保存し、特定のインスタンスが編集された場合は、同じイベント ID を持つ新しいイベントを作成できます。次に、イベントを検索するときに、同じイベント ID を持つすべてのイベントを検索して、すべての情報を取得します。独自のイベント ライブラリをロールしたのか、それとも既存のイベント ライブラリを使用しているため不可能なのかはわかりません。
3 つの優れた Ruby 日付/時刻ライブラリについては、以下の記事を確認してください。特に、ice_cube は、イベント カレンダーに必要な繰り返しルールやその他のものにとって、確実な選択肢であると思われます。http://www.rubyinside.com/3-new-date-and-time-libraries-for-rubyists-3238.html
JavaScript の場合:
定期的なスケジュールの処理:http://bunkat.github.io/later/
複雑なイベントとそれらのスケジュール間の依存関係の処理:http://bunkat.github.io/schedule/
基本的に、ルールを作成してから、(日付範囲を指定するかどうかを指定して) 次の N 個の繰り返しイベントを計算するようにライブラリに依頼します。ルールはモデルに保存するために解析/シリアル化できます。
定期的なイベントがあり、1 つの繰り返しのみを変更したい場合は、 を除外する() 特定の日を却下し、このエントリに新しい変更されたイベントを追加する関数。
このライブラリは、非常に複雑なパターン、タイムゾーン、さらにはクローニング イベントをサポートします。
イベントを繰り返しとして保存し、動的に表示しますが、定期的なイベントには、特定の日のデフォルト情報を上書きできる特定のイベントのリストを含めることができます。
定期的なイベントをクエリすると、その日の特定の上書きがチェックされます。
ユーザーが変更を行った場合は、すべてのインスタンス (デフォルトの詳細) を更新するか、その日だけ (新しい特定のイベントを作成してリストに追加する) かを尋ねることができます。
ユーザーがこのイベントの繰り返しをすべて削除するように要求した場合でも、詳細のリストが手元にあるので、それらを簡単に削除できます。
唯一問題となるのは、ユーザーがこのイベントと将来のすべてのイベントを更新したい場合です。この場合、定期的なイベントを 2 つに分割する必要があります。この時点で、定期的なイベントをすべて削除できるように、何らかの方法で定期的なイベントをリンクすることを検討することをお勧めします。
多少のライセンス料を支払う用意がある .NET プログラマーの場合は、次のことがわかるかもしれません。 Aspose.Network 役に立つ...定期的な予定のための iCalendar 互換ライブラリが含まれています。
イベントを iCalendar 形式で直接保存すると、無制限の繰り返しやタイムゾーンのローカリゼーションなどが可能になります。
これらを CalDAV サーバーに保存しておくと、イベントを表示するときに、CalDAV で定義されたレポートのオプションを使用して、表示されている期間にわたって繰り返し発生するイベントを展開するようにサーバーに要求できます。
または、それらを自分でデータベースに保存し、バックエンド CalDAV サーバーと通信するために PUT/GET/REPORT を必要とせずに、ある種の iCalendar 解析ライブラリを使用して拡張を行うこともできます。これはおそらくさらに作業が必要です。CalDAV サーバーはどこかに複雑さを隠していると思います。
イベントを iCalendar 形式にすると、人々はいずれにせよ他のソフトウェアに入れるためにイベントをエクスポートしたいと考えるので、長期的にはおそらく物事が簡単になるでしょう。
この機能を簡単に実装しました。ロジックは次のとおりです。まず、2 つのテーブルが必要です。RuleTable は、一般的なイベントまたは父親のイベントをリサイクルします。ItemTable はサイクル イベントを格納します。たとえば、周期イベントを作成すると、開始時刻が 2015 年 11 月 6 日、終了時刻が 12 月 6 日 (または永久) となり、1 週間周期で繰り返されます。RuleTable にデータを挿入します。フィールドは次のとおりです。
TableID: 1 Name: cycleA
StartTime: 6 November 2014 (I kept thenumber of milliseconds),
EndTime: 6 November 2015 (if it is repeated forever, and you can keep the value -1)
Cycletype: WeekLy.
ここで、11 月 20 日から 12 月 20 日までのデータをクエリしたいとします。関数 RecurringEventBE (長い開始時間、長い終了時間) を作成し、開始時間と終了時間に基づいて WeekLy を作成し、必要なコレクション < サイクル A11.20、サイクル A 11.27、サイクル A 12.4 ......> を計算できます。11月6日と残りの日をバーチャルイベントと呼びました。ユーザーが仮想イベントの名前を変更した後 (cycleA11.27 など)、ItemTable にデータを挿入します。フィールドは次のとおりです。
TableID: 1
Name, cycleB
StartTime, 27 November 2014
EndTime,November 6 2015
Cycletype, WeekLy
Foreignkey, 1 (pointingto the table recycle paternal events).
関数RecurringEventbe(Long Start、Long End)では、仮想イベント(CycleB11.27)をカバーするこのデータを使用して、私の英語について申し訳ありません。
これは私の RecurringEventBE です:
public static List<Map<String, Object>> recurringData(Context context,
long start, long end) { // 重复事件的模板处理,生成虚拟事件(根据日期段)
long a = System.currentTimeMillis();
List<Map<String, Object>> finalDataList = new ArrayList<Map<String, Object>>();
List<Map<String, Object>> tDataList = BillsDao.selectTemplateBillRuleByBE(context); //RuleTable,just select recurringEvent
for (Map<String, Object> iMap : tDataList) {
int _id = (Integer) iMap.get("_id");
long bk_billDuedate = (Long) iMap.get("ep_billDueDate"); // 相当于事件的开始日期 Start
long bk_billEndDate = (Long) iMap.get("ep_billEndDate"); // 重复事件的截止日期 End
int bk_billRepeatType = (Integer) iMap.get("ep_recurringType"); // recurring Type
long startDate = 0; // 进一步精确判断日记起止点,保证了该段时间断获取的数据不未空,减少不必要的处理
long endDate = 0;
if (bk_billEndDate == -1) { // 永远重复事件的处理
if (end >= bk_billDuedate) {
endDate = end;
startDate = (bk_billDuedate <= start) ? start : bk_billDuedate; // 进一步判断日记起止点,这样就保证了该段时间断获取的数据不未空
}
} else {
if (start <= bk_billEndDate && end >= bk_billDuedate) { // 首先判断起止时间是否落在重复区间,表示该段时间有重复事件
endDate = (bk_billEndDate >= end) ? end : bk_billEndDate;
startDate = (bk_billDuedate <= start) ? start : bk_billDuedate; // 进一步判断日记起止点,这样就保证了该段时间断获取的数据不未空
}
}
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(bk_billDuedate); // 设置重复的开始日期
long virtualLong = bk_billDuedate; // 虚拟时间,后面根据规则累加计算
List<Map<String, Object>> virtualDataList = new ArrayList<Map<String, Object>>();// 虚拟事件
if (virtualLong == startDate) { // 所要求的时间,小于等于父本时间,说明这个是父事件数据,即第一条父本数据
Map<String, Object> bMap = new HashMap<String, Object>();
bMap.putAll(iMap);
bMap.put("indexflag", 1); // 1表示父本事件
virtualDataList.add(bMap);
}
long before_times = 0; // 计算从要求时间start到重复开始时间的次数,用于定位第一次发生在请求时间段落的时间点
long remainder = -1;
if (bk_billRepeatType == 1) {
before_times = (startDate - bk_billDuedate) / (7 * DAYMILLIS);
remainder = (startDate - bk_billDuedate) % (7 * DAYMILLIS);
} else if (bk_billRepeatType == 2) {
before_times = (startDate - bk_billDuedate) / (14 * DAYMILLIS);
remainder = (startDate - bk_billDuedate) % (14 * DAYMILLIS);
} else if (bk_billRepeatType == 3) {
before_times = (startDate - bk_billDuedate) / (28 * DAYMILLIS);
remainder = (startDate - bk_billDuedate) % (28 * DAYMILLIS);
} else if (bk_billRepeatType == 4) {
before_times = (startDate - bk_billDuedate) / (15 * DAYMILLIS);
remainder = (startDate - bk_billDuedate) % (15 * DAYMILLIS);
} else if (bk_billRepeatType == 5) {
do { // 该段代码根据日历处理每天重复事件,当事件比较多的时候效率比较低
Calendar calendarCloneCalendar = (Calendar) calendar
.clone();
int currentMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
calendarCloneCalendar.add(Calendar.MONTH, 1);
int nextMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
if (currentMonthDay > nextMonthDay) {
calendar.add(Calendar.MONTH, 1 + 1);
virtualLong = calendar.getTimeInMillis();
} else {
calendar.add(Calendar.MONTH, 1);
virtualLong = calendar.getTimeInMillis();
}
} while (virtualLong < startDate);
} else if (bk_billRepeatType == 6) {
do { // 该段代码根据日历处理每天重复事件,当事件比较多的时候效率比较低
Calendar calendarCloneCalendar = (Calendar) calendar
.clone();
int currentMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
calendarCloneCalendar.add(Calendar.MONTH, 2);
int nextMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
if (currentMonthDay > nextMonthDay) {
calendar.add(Calendar.MONTH, 2 + 2);
virtualLong = calendar.getTimeInMillis();
} else {
calendar.add(Calendar.MONTH, 2);
virtualLong = calendar.getTimeInMillis();
}
} while (virtualLong < startDate);
} else if (bk_billRepeatType == 7) {
do { // 该段代码根据日历处理每天重复事件,当事件比较多的时候效率比较低
Calendar calendarCloneCalendar = (Calendar) calendar
.clone();
int currentMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
calendarCloneCalendar.add(Calendar.MONTH, 3);
int nextMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
if (currentMonthDay > nextMonthDay) {
calendar.add(Calendar.MONTH, 3 + 3);
virtualLong = calendar.getTimeInMillis();
} else {
calendar.add(Calendar.MONTH, 3);
virtualLong = calendar.getTimeInMillis();
}
} while (virtualLong < startDate);
} else if (bk_billRepeatType == 8) {
do {
calendar.add(Calendar.YEAR, 1);
virtualLong = calendar.getTimeInMillis();
} while (virtualLong < startDate);
}
if (remainder == 0 && virtualLong != startDate) { // 当整除的时候,说明当月的第一天也是虚拟事件,判断排除为父本,然后添加。不处理,一个月第一天事件会丢失
before_times = before_times - 1;
}
if (bk_billRepeatType == 1) { // 单独处理天事件,计算出第一次出现在时间段的事件时间
virtualLong = bk_billDuedate + (before_times + 1) * 7
* (DAYMILLIS);
calendar.setTimeInMillis(virtualLong);
} else if (bk_billRepeatType == 2) {
virtualLong = bk_billDuedate + (before_times + 1) * (2 * 7)
* DAYMILLIS;
calendar.setTimeInMillis(virtualLong);
} else if (bk_billRepeatType == 3) {
virtualLong = bk_billDuedate + (before_times + 1) * (4 * 7)
* DAYMILLIS;
calendar.setTimeInMillis(virtualLong);
} else if (bk_billRepeatType == 4) {
virtualLong = bk_billDuedate + (before_times + 1) * (15)
* DAYMILLIS;
calendar.setTimeInMillis(virtualLong);
}
while (startDate <= virtualLong && virtualLong <= endDate) { // 插入虚拟事件
Map<String, Object> bMap = new HashMap<String, Object>();
bMap.putAll(iMap);
bMap.put("ep_billDueDate", virtualLong);
bMap.put("indexflag", 2); // 2表示虚拟事件
virtualDataList.add(bMap);
if (bk_billRepeatType == 1) {
calendar.add(Calendar.DAY_OF_MONTH, 7);
} else if (bk_billRepeatType == 2) {
calendar.add(Calendar.DAY_OF_MONTH, 2 * 7);
} else if (bk_billRepeatType == 3) {
calendar.add(Calendar.DAY_OF_MONTH, 4 * 7);
} else if (bk_billRepeatType == 4) {
calendar.add(Calendar.DAY_OF_MONTH, 15);
} else if (bk_billRepeatType == 5) {
Calendar calendarCloneCalendar = (Calendar) calendar
.clone();
int currentMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
calendarCloneCalendar.add(Calendar.MONTH,
1);
int nextMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
if (currentMonthDay > nextMonthDay) {
calendar.add(Calendar.MONTH, 1
+ 1);
} else {
calendar.add(Calendar.MONTH, 1);
}
}else if (bk_billRepeatType == 6) {
Calendar calendarCloneCalendar = (Calendar) calendar
.clone();
int currentMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
calendarCloneCalendar.add(Calendar.MONTH,
2);
int nextMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
if (currentMonthDay > nextMonthDay) {
calendar.add(Calendar.MONTH, 2
+ 2);
} else {
calendar.add(Calendar.MONTH, 2);
}
}else if (bk_billRepeatType == 7) {
Calendar calendarCloneCalendar = (Calendar) calendar
.clone();
int currentMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
calendarCloneCalendar.add(Calendar.MONTH,
3);
int nextMonthDay = calendarCloneCalendar
.get(Calendar.DAY_OF_MONTH);
if (currentMonthDay > nextMonthDay) {
calendar.add(Calendar.MONTH, 3
+ 3);
} else {
calendar.add(Calendar.MONTH, 3);
}
} else if (bk_billRepeatType == 8) {
calendar.add(Calendar.YEAR, 1);
}
virtualLong = calendar.getTimeInMillis();
}
finalDataList.addAll(virtualDataList);
}// 遍历模板结束,产生结果为一个父本加若干虚事件的list
/*
* 开始处理重复特例事件特例事件,并且来时合并
*/
List<Map<String, Object>>oDataList = BillsDao.selectBillItemByBE(context, start, end);
Log.v("mtest", "特例结果大小" +oDataList );
List<Map<String, Object>> delectDataListf = new ArrayList<Map<String, Object>>(); // finalDataList要删除的结果
List<Map<String, Object>> delectDataListO = new ArrayList<Map<String, Object>>(); // oDataList要删除的结果
for (Map<String, Object> fMap : finalDataList) { // 遍历虚拟事件
int pbill_id = (Integer) fMap.get("_id");
long pdue_date = (Long) fMap.get("ep_billDueDate");
for (Map<String, Object> oMap : oDataList) {
int cbill_id = (Integer) oMap.get("billItemHasBillRule");
long cdue_date = (Long) oMap.get("ep_billDueDate");
int bk_billsDelete = (Integer) oMap.get("ep_billisDelete");
if (cbill_id == pbill_id) {
if (bk_billsDelete == 2) {// 改变了duedate的特殊事件
long old_due = (Long) oMap.get("ep_billItemDueDateNew");
if (old_due == pdue_date) {
delectDataListf.add(fMap);//该改变事件在时间范围内,保留oMap
}
} else if (bk_billsDelete == 1) {
if (cdue_date == pdue_date) {
delectDataListf.add(fMap);
delectDataListO.add(oMap);
}
} else {
if (cdue_date == pdue_date) {
delectDataListf.add(fMap);
}
}
}
}// 遍历特例事件结束
}// 遍历虚拟事件结束
// Log.v("mtest", "delectDataListf的大小"+delectDataListf.size());
// Log.v("mtest", "delectDataListO的大小"+delectDataListO.size());
finalDataList.removeAll(delectDataListf);
oDataList.removeAll(delectDataListO);
finalDataList.addAll(oDataList);
List<Map<String, Object>> mOrdinaryList = BillsDao.selectOrdinaryBillRuleByBE(context, start, end);
finalDataList.addAll(mOrdinaryList);
// Log.v("mtest", "finalDataList的大小"+finalDataList.size());
long b = System.currentTimeMillis();
Log.v("mtest", "算法耗时"+(b-a));
return finalDataList;
}
終了日のない定期的な予定がある場合はどうすればよいでしょうか?スペースはいくら安くても、無限のスペースがあるわけではないので、解決策 2 はスターターではありません...
「終了日がない」という問題は、今世紀末の終了日に解決できると提案してもいいでしょうか。毎日のイベントであっても、スペースの量は依然として安価です。