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

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

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

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

Ответы [ 18 ]

2 голосов
/ 11 февраля 2011

Проверьте статью ниже для трех хороших ruby ​​библиотек даты / времени. В частности, ice_cube кажется хорошим выбором для правил повторения и других вещей, которые понадобятся в календаре событий. http://www.rubyinside.com/3-new-date-and-time-libraries-for-rubyists-3238.html

2 голосов
/ 17 сентября 2008

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

1 голос
/ 23 ноября 2014

В JavaScript:

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

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

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

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

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

0 голосов
/ 06 ноября 2014

Я просто реализовал эту функцию! Логика такова, для начала нужно две таблицы. 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 (длинный старт, длинный конец) вы используете эти данные, охватывающие виртуальное событие (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;
}   
0 голосов
/ 14 марта 2012

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

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

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

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

0 голосов
/ 17 сентября 2008

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

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

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

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

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

0 голосов
/ 01 февраля 2010

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

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

0 голосов
/ 19 мая 2009

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

...