What's the best way to model recurring events in a calendar application?

后端 未结 18 2364
感情败类
感情败类 2020-11-27 08:54

I\'m building a group calendar application that needs to support recurring events, but all the solutions I\'ve come up with to handle these events seem like a hack. I can li

相关标签:
18条回答
  • 2020-11-27 09:28

    What if you have a recurring appointment with no end date? As cheap as space is, you don't have infinite space, so Solution 2 is a non-starter there...

    May I suggest that "no end date" can be resolved to an end date at the end of the century. Even for a dayly event the amount of space remains cheap.

    0 讨论(0)
  • 2020-11-27 09:29
    1. Keep track of a recurrence rule (probably based on iCalendar, per @Kris K.). This will include a pattern and a range (Every third Tuesday, for 10 occurrences).
    2. For when you want to edit/delete a specific occurrence, keep track of exception dates for the above recurrence rule (dates where the event doesn't occur as the rule specifies).
    3. If you deleted, that's all you need, if you edited, create another event, and give it a parent ID set to the main event. You can choose whether to include all of the main event's information in this record, or if it only holds the changes and inherits everything that doesn't change.

    Note that if you allow recurrence rules that don't end, you have to think about how to display your now infinite amount of information.

    Hope that helps!

    0 讨论(0)
  • 2020-11-27 09:30

    You may want to look at iCalendar software implementations or the standard itself (RFC 2445 RFC 5545). Ones to come to mind quickly are the Mozilla projects http://www.mozilla.org/projects/calendar/ A quick search reveals http://icalendar.rubyforge.org/ as well.

    Other options can be considered depending on how you're going to store the events. Are you building your own database schema? Using something iCalendar-based, etc.?

    0 讨论(0)
  • 2020-11-27 09:33

    You store the events in iCalendar format directly, which allows for open-ended repetition, time-zone localisation and so forth.

    You could store these in a CalDAV server and then when you want to display the events you can use the option of the report defined in CalDAV to ask the server to do the expansion of the recurring events across the viewed period.

    Or you could store them in a database yourself and use some kind of iCalendar parsing library to do the expansion, without needing the PUT/GET/REPORT to talk to a backend CalDAV server. This is probably more work - I'm sure CalDAV servers hide complexity somewhere.

    Having the events in iCalendar format will probably make things simpler in the long run as people will always want them to be exported for putting in other software anyway.

    0 讨论(0)
  • 2020-11-27 09:37

    I have Simply implemented this feature! Logic is as follows, first you need two tables. RuleTable store general or recycle paternal events. ItemTable is stored cycle events. For example, when you create a cyclic event, the start time for 6 November 2015, the end time for the December 6 (or forever), cycle for one week. You insert data into a RuleTable, fields are as follows:

    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.
    

    Now you want to query November 20 to December 20 data. You can write a function RecurringEventBE (long start, long end), based on the starting and ending time, WeekLy, you can calculate the collection you want, < cycleA11.20, cycleA 11.27, cycleA 12.4 ......>. In addition to November 6, and the rest I called him a virtual event. When the user changes a virtual event' name after (cycleA11.27 for example), you insert a data into a ItemTable. Fields are as follows:

    TableID: 1 
    Name, cycleB  
    StartTime, 27 November 2014  
    EndTime,November 6 2015  
    Cycletype, WeekLy
    Foreignkey, 1 (pointingto the table recycle paternal events).
    

    In function RecurringEventBE (long start, long end), you use this data covering virtual event (cycleB11.27) sorry about my english, I tried.

    This is my 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 讨论(0)
  • 2020-11-27 09:38

    I'm using the database schema as described below to store the recurrence parameters

    http://github.com/bakineggs/recurring_events_for

    Then I use runt to dynamically calculate the dates.

    https://github.com/mlipper/runt

    0 讨论(0)
提交回复
热议问题