Как лучше всего моделировать повторяющиеся события в приложении календаря?

225

Я создаю приложение для группового календаря, которое должно поддерживать повторяющиеся события, но все решения, которые я придумал для обработки этих событий, кажутся хаком. Я могу ограничить, насколько далеко можно заглянуть вперед, а затем сгенерировать все события одновременно. Или я могу сохранить события как повторяющиеся и динамически отображать их, когда кто-то смотрит в календарь, но мне придется преобразовать их в обычное событие, если кто-то захочет изменить детали конкретного экземпляра события.

Я уверен, что есть лучший способ сделать это, но я еще не нашел это. Каков наилучший способ моделирования повторяющихся событий, где вы можете изменить детали или удалить отдельные экземпляры событий?

(Я использую Ruby, но, пожалуйста, не позволяйте этому ограничивать ваш ответ. Однако, если есть библиотека для Ruby или что-то еще, это полезно знать.)

Клинтон Н. Дрейсбах
источник

Ответы:

93

Я бы использовал концепцию «ссылка» для всех будущих повторяющихся событий. Они динамически отображаются в календаре и ссылаются на один ссылочный объект. Когда события произошли, связь разрывается, и событие становится отдельным экземпляром. Если вы попытаетесь отредактировать повторяющееся событие, то предложите изменить все будущие элементы (т.е. изменить одну связанную ссылку) или изменить только этот экземпляр (в этом случае преобразовать его в автономный экземпляр и затем внести изменения). Последний случай немного проблематичен, так как вам нужно отслеживать в своем повторяющемся списке все будущие события, которые были преобразованы в один экземпляр. Но это вполне выполнимо.

Итак, по сути, есть 2 класса событий - единичные экземпляры и повторяющиеся события.

user16068
источник
Очень нравится ваша идея связать и преобразовать события в отдельные после того, как они прошли. Два вопроса: - Зачем вообще конвертировать их в автономные фиксированные экземпляры? Почему бы не оставить их полностью динамичными? - Можете поделиться ссылкой на предложенную концепцию ссылок! Заранее спасибо!
rtindru
@rtindru Я нашел вариант использования для преобразования событий в автономный режим, когда вам нужно использовать модель событий с другими моделями в вашей базе данных. Например, для проверки посещаемости события вам необходимо связать пользователей с реальным событием, которое произошло (или произойдет).
Клинтон Йебоа
33

С повторяющимися событиями может быть много проблем, позвольте мне выделить некоторые из них, о которых я знаю.

Решение 1 - нет случаев

Сохранять исходные данные о встрече + повторения, не хранить все экземпляры.

Проблемы:

  • Вам нужно будет рассчитать все экземпляры в окне даты, когда они вам нужны, дорого
  • Невозможно обработать исключения (т.е. вы удаляете один из экземпляров или перемещаете его, или, скорее, вы не можете сделать это с этим решением)

Решение 2 - хранить экземпляры

Сохраните все, начиная с 1, но также и все экземпляры, связанные с исходной встречей.

Проблемы:

  • Занимает много места (но место дешевое, настолько незначительное)
  • Исключения должны обрабатываться изящно, особенно если вы вернетесь и отредактируете исходную встречу после создания исключения. Например, если вы перенесете третий экземпляр на один день вперед, что если вы вернетесь назад и отредактируете время первоначальной встречи, повторно вставите другую в исходный день и оставите перенесенную? Отсоединить перемещенный? Попробуй поменять перемещённую соответственно?

Конечно, если вы не собираетесь делать исключения, тогда любое решение должно быть подходящим, и вы в основном выбираете из сценария временного / пространственного компромисса.

Лассе В. Карлсен
источник
36
Что делать, если у вас есть повторяющаяся встреча без даты окончания? Как бы ни было дешево пространство, у вас нет бесконечного пространства, поэтому Решение 2 - это не стартер ...
Шаул Бер
13
Решение № 1 на самом деле может обрабатывать исключения. Например, RFC5545 предполагает, что они хранятся в виде: a) списка исключенных дат (когда вы удаляете вхождение); б) «материализованные» вхождения со ссылками на прототип (при перемещении вхождения).
Энди Михайленко
@ Энди, несколько интересных дополнений к ответу Лассе. Собираюсь попробовать.
Джонатан Уилсон
1
@Shaul: я не думаю, что это не стартер. Джон Скит, весьма уважаемый в SO, предлагает хранить сгенерированные экземпляры в своем ответе на один и тот же вопрос: stackoverflow.com/a/10151804/155268
пользователь
1
@ Пользователь - признал, спасибо. Это так странно - я сделал свой комментарий более 4 лет назад, и с тех пор мне не нужно было заниматься этим вопросом. Буквально вчера я занялся разработкой нового модуля, который включает повторяющиеся встречи, и мне было интересно, как справиться с ними. А потом - я получил ТАКОЕ уведомление о вашем комментарии сегодня утром. Серьезно жуткий! Но спасибо! :-)
Шауль Бер
21

Я разработал несколько приложений на основе календаря, а также создал набор повторно используемых компонентов календаря JavaScript, которые поддерживают повторяемость. Я написал обзор того, как спроектировать повторение, которое может быть полезным для кого-то. Хотя есть несколько битов, специфичных для библиотеки, которую я написал, подавляющее большинство предлагаемых советов является общим для любой реализации календаря.

Некоторые из ключевых моментов:

  • Рецидивы магазина с помощью ческого формата RRULE - вот одно колеса , вы действительно не хотите , чтобы изобрести
  • НЕ хранить индивидуальные повторяющиеся события экземпляры как строки в базе данных! Всегда сохраняйте шаблон повторения.
  • Есть много способов спроектировать вашу схему событий / исключений, но приведен пример базовой отправной точки
  • Все значения даты / времени должны быть сохранены в UTC и преобразованы в локальные для отображения
  • Дата окончания, сохраняемая для повторяющегося события, всегда должна быть датой окончания диапазона повторения (или «максимальной даты» вашей платформы, если повторяющаяся «навсегда»), а продолжительность события должна храниться отдельно. Это делается для того, чтобы обеспечить разумный способ запроса событий позже.
  • Некоторое обсуждение создания экземпляров событий и стратегий редактирования повторений включено

Это действительно сложная тема с множеством подходящих подходов к ее реализации. Я скажу, что на самом деле я несколько раз успешно реализовывал повторение, и я бы с осторожностью посоветовался по этому вопросу со всеми, кто этого не делал.

Брайан Мескау
источник
Возможно, храните повторения как события, когда они происходят, чтобы история вашего календаря была точной
Ричард Хейвен,
@RichardHaven Я бы никогда этого не сделал. Вы должны всегда генерировать экземпляры из шаблонов RRULE последовательно, в прошлом, настоящем или будущем. Не было бы никакой причины делать что-то другое для исторических событий. Ваша логика должна просто сравнивать RRULE с любым произвольным диапазоном дат и возвращать совпадающие экземпляры событий.
Брайан Мескау
@BrianMoeskau хороший и полезный обзор!
Пшемек Новак
@BrianMoeskau Но тогда не будут ли прошлые просмотры вашего календаря показывать неточную информацию, когда кто-то редактирует RRULE после того, как некоторые события уже произошли? Или, может быть, в этом случае вы «разветвляете» RRULE и сохраняете измененные версии шаблонов RRULE, точно представляющие фактические прошлые вхождения?
Кристиан
1
@christian Когда вы обновляете правило повторения в большинстве календарей, они обычно предлагают «изменить все события, или только это, или только будущее», позволяя пользователю выбирать поведение. В большинстве случаев пользователь, вероятно, имеет в виду «изменить его в будущем», но опять же, вам решать, как работает ваше программное обеспечение и какие опции вы предоставляете пользователю.
Брайан Мескау
19

Возможно, вы захотите взглянуть на реализации программного обеспечения iCalendar или сам стандарт ( RFC 2445 RFC 5545 ). Быстро всплывают проекты Mozilla http://www.mozilla.org/projects/calendar/ Быстрый поиск также показывает http://icalendar.rubyforge.org/ .

Другие варианты могут быть рассмотрены в зависимости от того, как вы собираетесь хранить события. Вы строите свою собственную схему базы данных? Используете что-то на основе iCalendar и т. Д.?

Крис Кумлер
источник
если бы вы могли просто дать ссылку на один из них, ваш пост был бы идеальным
Жан
7
Похоже, что RFC2445 устарел в RFC5545 ( tools.ietf.org/html/rfc5545 )
Эрик Фриз,
16

Я работаю со следующим:

и жемчужина в процессе разработки, которая расширяет форму с помощью типа ввода: recurring ( form.schedule :as => :recurring), который визуализирует интерфейс, подобный iCal, и a before_filterдля IceCubeповторной сериализации представления в объект, гетто-ly.

Моя идея состоит в том, чтобы невероятно легко добавлять повторяющиеся атрибуты в модель и легко подключать ее в представлении. Все в пару строк.


Так что это мне дает? Индексированные, редактируемые, повторяющиеся атрибуты.

eventsхранит один экземпляр дня, и используются в календаре / помощник сказать task.scheduleхранит в yaml'd IceCubeобъект, так что вы можете делать звонки , как: task.schedule.next_suggestion.

Напомним: я использую две модели: одну плоскую для отображения календаря и одну атрибутную для функциональности.

Vee
источник
Мне было бы интересно посмотреть, что вы придумали. Есть ли у вас где-нибудь гит / блог / подтверждение концепции? Спасибо!
Монреальмике
Я тоже работаю над чем-то похожим.
Хотелось
5
  1. Следите за правилом повторения (вероятно, на основе iCalendar, за @ Kris K. ). Это будет включать в себя шаблон и диапазон (каждый третий вторник, для 10 случаев).
  2. Если вы хотите отредактировать / удалить конкретное вхождение, отслеживайте даты исключений для указанного выше правила повторения (даты, когда событие не происходит, как указано в правиле).
  3. Если вы удалили, это все, что вам нужно, если вы отредактировали, создайте другое событие и присвойте ему родительский идентификатор, установленный для основного события. Вы можете выбрать, включать ли всю информацию о главном событии в эту запись, или она содержит только изменения и наследует все, что не меняется.

Обратите внимание, что если вы разрешаете правила повторения, которые не заканчиваются, вам нужно подумать о том, как отображать бесконечное количество информации.

Надеюсь, это поможет!

bdukes
источник
4

Я бы порекомендовал использовать всю мощь библиотеки дат и семантику модуля диапазона ruby. Повторяющееся событие - это действительно время, диапазон дат (начало и конец) и, как правило, один день недели. Используя дату и диапазон, вы можете ответить на любой вопрос:

#!/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\"]"
Purfideas
источник
2
Это не очень гибко. Модель повторяющихся событий часто требует указания периода повторения (ежечасно, еженедельно, раз в две недели и т. Д.). Кроме того, повторение может быть не квалифицировано по общему числу, а скорее как дата окончания последнего вхождения
Бо Жанес
«Периодическим событием является [..] обычно один день недели», это всего лишь один случай ограниченного использования и не распространяется на многие другие, такие как «5-й день каждого месяца» и т. Д.
theraven
3

Из этих ответов я как бы отсеял решение. Мне очень нравится идея концепции ссылки. Повторяющиеся события могут быть связанным списком, причем хвост знает свое правило повторения. В этом случае изменение одного события будет простым, поскольку ссылки остаются на месте, а удаление события также легко - вы просто отсоединяете событие, удаляете его и повторно связываете событие до и после него. Вам по-прежнему приходится запрашивать повторяющиеся события каждый раз, когда кто-то смотрит на новый период времени, который никогда не просматривался в календаре, но в остальном это довольно чисто.

Клинтон Н. Дрейсбах
источник
2

Вы можете сохранить события как повторяющиеся, и, если конкретный экземпляр был отредактирован, создать новое событие с тем же идентификатором события. Затем при поиске события найдите все события с одинаковым идентификатором события, чтобы получить всю информацию. Я не уверен, что вы свернули свою собственную библиотеку событий или используете уже существующую, так что это может оказаться невозможным.

Винсент Макнабб
источник
Я использовал это решение один раз. Мне нравится принцип хранения измененного экземпляра как нового одноразового события, которое знает, кто его мама. Таким образом, вы можете оставить все поля пустыми, кроме тех, которые отличаются для дочернего события. Обратите внимание, что у вас должно быть дополнительное поле, указывающее, какого ребенка этой матери вы редактируете.
Wytze
1

В JavaScript:

Обработка повторяющихся расписаний: http://bunkat.github.io/later/

Обработка сложных событий и зависимостей между этими расписаниями: http://bunkat.github.io/schedule/

По сути, вы создаете правила, а затем просите библиотеку вычислить следующие N повторяющихся событий (с указанием диапазона дат или нет). Правила могут быть проанализированы / сериализованы для сохранения их в вашей модели.

Если у вас есть повторяющееся событие и вы хотите изменить только одно повторение, вы можете использовать функцию исключения (), чтобы отменить определенный день, а затем добавить новое измененное событие для этой записи.

Библиотека поддерживает очень сложные паттерны, часовые пояса и даже хронологические события.

Флавиен Волкен
источник
0

Сохраняйте события как повторяющиеся и динамически отображайте их, однако позволяйте повторяющимся событиям содержать список определенных событий, которые могут переопределять информацию по умолчанию в определенный день.

Когда вы запрашиваете повторяющееся событие, оно может проверить конкретное переопределение для этого дня.

Если пользователь вносит изменения, то вы можете спросить, хочет ли он обновить все экземпляры (данные по умолчанию) или только на этот день (создать новое конкретное событие и добавить его в список).

Если пользователь просит удалить все повторения этого события, у вас также есть список специфических особенностей, и вы можете легко их удалить.

Единственный проблемный случай - пользователь хочет обновить это событие и все будущие события. В этом случае вам придется разделить повторяющееся событие на две части. На этом этапе вы можете захотеть связать повторяющиеся события каким-либо образом, чтобы вы могли удалить их все.

Эндрю Джонсон
источник
0

Для программистов .NET, которые готовы платить некоторые лицензионные сборы, вы можете найти Aspose.Network полезным ... он включает в себя совместимую с iCalendar библиотеку для повторяющихся встреч.

Шауль Бер
источник
0

Вы сохраняете события непосредственно в формате iCalendar, что позволяет осуществлять повторение в открытом формате, локализацию часового пояса и т. Д.

Вы можете сохранить их на сервере CalDAV, а затем, когда вы захотите отобразить события, вы можете использовать опцию отчета, определенную в CalDAV, чтобы попросить сервер выполнить расширение повторяющихся событий за просматриваемый период.

Или вы можете сохранить их в базе данных самостоятельно и использовать некую библиотеку синтаксического анализа iCalendar для расширения, не нуждаясь в PUT / GET / REPORT для связи с внутренним сервером CalDAV. Это, вероятно, больше работы - я уверен, что серверы CalDAV где-то скрывают сложность.

Наличие событий в формате iCalendar, вероятно, в конечном итоге упростит ситуацию, так как люди всегда будут хотеть, чтобы их экспортировали для установки другого программного обеспечения в любом случае.

karora
источник
0

Я просто реализовал эту функцию! Логика такова, для начала нужно две таблицы. RuleTable хранит общие или перезапускает отцовские события. ItemTable - это сохраненные события цикла. Например, при создании циклического события время начала 6 ноября 2015 г., время окончания цикла 6 декабря (или навсегда) для одной недели. Вы вставляете данные в 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.

Теперь вы хотите запросить данные с 20 ноября по 20 декабря. Вы можете написать функцию RecurringEventBE (длинный старт, длинный конец), основываясь на времени начала и окончания, WeekLy, вы можете рассчитать желаемую коллекцию, <циклA11.20, цикл А 11.27, цикл А 12.4 ......>. Помимо 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); //RuleTablejust 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;
}   
fozua
источник
-5

Что делать, если у вас есть повторяющаяся встреча без даты окончания? Как бы ни было дешево пространство, у вас нет бесконечного пространства, так что Решение 2 - это не стартер ...

Могу ли я предположить, что «конечная дата не может быть решена» до конечной даты в конце века. Даже для ежедневного мероприятия количество места остается дешевым.

poumtatalia
источник
7
Как скоро мы забудем уроки y2k ... :)
Ян Мерсер
10
Давайте предположим, что у нас есть 1000 пользователей, каждый из которых имеет несколько ежедневных событий. 3 события × 1000 пользователей × 365 дней × (2100-2011 = 89 лет) = 97,5 миллиона записей. Вместо 3000 "планов". Гм ...
Энди Михайленко