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

StackOverflow https://stackoverflow.com/questions/85699

Вопрос

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

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

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

Это было полезно?

Решение

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

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

Другие советы

Мартин Фаулер - Повторяющиеся события для календарей содержит несколько интересных идей и закономерностей.

Коротышка gem реализует этот шаблон.

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

Решение 1 - нет экземпляров

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

Проблемы:

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

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

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

Проблемы:

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

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

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

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

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

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

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


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

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

Краткое изложение:Я использую две модели, одну плоскую, для отображения календаря, и одну attribut'd для функциональности.

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

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

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

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

Я использую схему базы данных, как описано ниже, для хранения параметров повторяемости

http://github.com/bakineggs/recurring_events_for

Затем я использую runt для динамического вычисления дат.

https://github.com/mlipper/runt

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

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

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

Я бы рекомендовал использовать возможности библиотеки date и семантику модуля range в 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\"]"

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

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

Ознакомьтесь с приведенной ниже статьей о трех хороших библиотеках даты и времени 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 повторяющихся событий (указывая диапазон дат или нет).Правила могут быть проанализированы / сериализованы для сохранения их в вашей модели.

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

Библиотека поддерживает очень сложные шаблоны, часовые пояса и даже сопутствующие события.

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

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

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

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

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

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

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

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

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

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

Я просто реализовал эту функцию!Логика такова, сначала вам нужны две таблицы.Таблица правил хранит общие или перерабатывает родительские события.В ItemTable хранятся события цикла.Например, когда вы создаете циклическое событие, время начала - 6 ноября 2015 года, время окончания - 6 декабря (или навсегда), цикл рассчитан на одну неделю.Вы вставляете данные в таблицу правил, поля которой следующие:

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 (long start, long end), основанную на времени начала и окончания, еженедельно, вы можете вычислять нужную вам коллекцию, < Цикл 11.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) извините за мой английский, я пытался.

Это мое повторяющееся событие:

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 здесь не подходит...

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top