What's the best way to model recurring events in a calendar application? [closed]
Asked Answered
U

17

252

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 limit how far ahead one can look, and then generate all the events at once. Or I can store the events as repeating and dynamically display them when one looks ahead on the calendar, but I'll have to convert them to a normal event if someone wants to change the details on a particular instance of the event.

I'm sure there's a better way to do this, but I haven't found it yet. What's the best way to model recurring events, where you can change details of or delete particular event instances?

(I'm using Ruby, but please don't let that constrain your answer. If there's a Ruby-specific library or something, though, that's good to know.)

Ulm answered 17/9, 2008 at 17:37 Comment(1)
Martin Fowler - Recurring Events for Calendars contains some interesting insights and patterns. Runt gem implements this pattern.Propitious
A
108

I would use a 'link' concept for all future recurring events. They are dynamically displayed in the calendar and link back to a single reference object. When events have taken place the link is broken and the event becomes a standalone instance. If you attempt to edit a recurring event then prompt to change all future items (i.e. change single linked reference) or change just that instance (in which case convert this to a standalone instance and then make change). The latter cased is slightly problematic as you need to keep track in your recurring list of all future events that were converted to single instance. But, this is entirely do-able.

So, in essence, have 2 classes of events - single instances and recurring events.

Arsenious answered 17/9, 2008 at 17:45 Comment(2)
Really like your idea of linking and converting events to standalone after they have passed. Two questions: - Why convert them to standalone fixed instances at all? Why not leave them completely dynamic? - Can you share reference for the proposed link concept! Thanks in advance!Madisonmadlen
@Madisonmadlen a use case I found for converting events to standalone is when you have to use the event model with other models in your database. For example for checking attendance for an event, you will want to associate users with a real event that happened (or will happen).Snob
P
66

I have developed multiple calendar-based applications, and also authored a set of reusable JavaScript calendar components that support recurrence. I also wrote up an overview of how to design for recurrence that should be helpful. While there are a few bits that are specific to the library I wrote, the vast majority of the advice offered is general to any calendar implementation.

Some of the key points:

  • Store recurrence using the iCal RRULE format -- that's one wheel you really don't want to reinvent
  • Do NOT store individual recurring event instances as rows in your database! Always store a recurrence pattern.
  • There are many ways to design your event/exception schema, but a basic starting point example is provided
  • All date/time values should be stored in UTC and converted to local for display
  • The end date stored for a recurring event should always be the end date of the recurrence range (or your platform's "max date" if recurring "forever") and the event duration should be stored separately. This is to ensure a sane way of querying for events later. Read the linked article for more details about this.
  • Some discussion around generating event instances and recurrence editing strategies is included

It's a really complicated topic with many implementation strategies that could work. I will say that I've actually implemented recurrence several times successfully, and I would be wary of taking advice on this subject from anyone who hasn't actually done it.

Paschal answered 13/7, 2016 at 18:25 Comment(15)
Perhaps store recurrences as events when they happen so your calendar history is accurateSkirt
@RichardHaven I would never do that. You should always generate instances from RRULE patterns consistently, past, present or future. There would be no reason to do something different for historical events. Your logic should simply evaluate an RRULE against any arbitrary date range and return matching event instances.Paschal
@BrianMoeskau But then wouldn't past views of your calendar show inaccurate information when someone edits the RRULE after some occurrences have already occurred? Or maybe in that case you would "branch" the RRULE and keep modified versions of the RRULE patterns representing exactly the actual past occurrences?Jackboot
@BrianMoeskau Another point, regarding storing in UTC and converting to local for display: I agree that is the general best practice, but doesn't it break if you have a recurring event covering daylight savings changes? I mean: if we are basing all calculations in UTC, then "every Monday 8am local time" (which is probably what the user wants) will become "Monday 9am local DST" (for typical DST changes -- or the other way around after DST ends if the entry was actually created during DST). Am I missing something here?Jackboot
@Jackboot When you update a recurrence rule in most calendars, they typically prompt like "edit all events, or this one only, or future only" allowing the user to choose behavior. In most cases, the user probably means "change it going forward" but again, it's up to you to decide how your software works and what options you give to the user.Paschal
@Jackboot Not sure I understand your point about DST. Storing in UTC is the only real option, and the assumption is that when converting to local time your logic / library is properly DST-aware and using the user's timezone. In that case it works as expected (in my experience). If you stored an event in the user's local time as 1:15 am during the Oct DST change, is that before or after the time changed? Only UTC will work consistently there.Paschal
@Brian Moeskau Where I live, local time is UTC-3. Say I create an event "every Monday 8:00 local time"; it would get stored as "11:00Z". Come DST, local time changes to UTC-2. The event would now be recovered from the database as "11:00Z" == "9:00 UTC-2" (my DST local time), which is not what I wanted (I wanted 8:00 local time, regardless of DST). Note: if I created the event during DST (8:00 UTC-2), the database record would be "10:00Z", which off-DST would be recovered as "7:00 UTC-3" (again, not what I wanted).Jackboot
Maybe the best solution would be to store it in local time with the original timezone (e.g. store "8:00 UTC-3", which is effectively the same as "11:00Z" but also captures the user's local time when the event was created). This would allow you to retrieve the event in the user's local time even with DST changes, and would also allow correct conversions to other users' timezones where required.Jackboot
Reading through this link reminded me of the discussion in the latest comments. Although it is specifically about the timestamp and timestamptz types in PostgreSQL, the explanation about the differences in the concepts underlying these types is very relevant to what we were discussing here.Jackboot
@BrianMoeskau I found your gist/article very helpful. Just curious as to how you'd handle routing to a single instance of a recurring event? Something like /events/:start_date? Or /events/:encoded_string_including_start_date_signed_with_some_key?Physiological
@Physiological Great question -- in my implementations at the time I didn't have to handle URL routing to individual instances, but I can see the use in that. I would probably use the combo of event record ID + instance date (plus time too, if recurrence resolutions smaller than "daily" are allowed). You could encode that value, but you could even do it RESTfully like /event/<id>/<date> to keep it readable, e.g. /event/1234/2021-01-01. That would make it pretty easy to generate the links from your UI rendering code without any complex extra logic.Paschal
@BrianMoeskau How could I apply pagination? In my case a list view events instances with pagination, order by date DESC (overdue -> today -> future), having 10 items per page. Because we must query events in database first, then using logic in backend side to get recurrence events. It's difficult to apply pagination.Paragraph
@NguyenNhutTien There's no simple way around it, other than querying the DB, calculating event instances from your recurrence patterns, and THEN applying your pagination logic to those results. If performance is an issue, you could look into storing the generated event instances into an intermediate table, or some other style of caching (depending on how often your data changes).Paschal
thanks @BrianMoeskau for this knowledge share, amazing job! What about "to-do"-style reminder? How would you implement if single to-do is checked/mark as done? would you create an exception for each "done" item?Krueger
@MaciejPszczolinski I would probably just add a boolean attribute on the event model and use that when deciding what to display. "Done" really has nothing to do with recurrence, it's just another property of each recurring event instance that you can use as needed in your logic.Paschal
K
35

There can be many problems with recurring events, let me highlight a few that I know of.

Solution 1 - no instances

Store original appointment + recurrence data, do not store all the instances.

Problems:

  • You'll have to calculate all the instances in a date window when you need them, costly
  • Unable to handle exceptions (ie. you delete one of the instances, or move it, or rather, you can't do this with this solution)

Solution 2 - store instances

Store everything from 1, but also all the instances, linked back to the original appointment.

Problems:

  • Takes a lot of space (but space is cheap, so minor)
  • Exceptions must be handled gracefully, especially if you go back and edit the original appointment after making an exception. For instance, if you move the third instance one day forward, what if you go back and edit the time of the original appointment, re-insert another on the original day and leave the moved one? Unlink the moved one? Try to change the moved one appropriately?

Of course, if you're not going to do exceptions, then either solution should be fine, and you basically choose from a time/space trade off scenario.

Kee answered 19/5, 2009 at 11:38 Comment(3)
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...Torn
Solution #1 actually can handle exceptions. For example, RFC5545 suggests that they are stored as: a) a list of excluded dates (when you delete an occurrence); b) "materialized" occurrences with references to the prototype (when you move an occurrence).Bullhorn
@Shaul: I don't think it's a non-starter. John Skeet, who is pretty well respected on SO, suggests storing generated instances in his answer to basically the same question: https://mcmap.net/q/118667/-should-i-store-dates-or-recurrence-rules-in-my-database-when-building-a-calendar-appDwinnell
K
20

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.?

Kibosh answered 17/9, 2008 at 17:40 Comment(1)
Looks like RFC2445 has been made obsolete by RFC5545 (tools.ietf.org/html/rfc5545)Kenyatta
M
16

I'm working with the following:

and a gem in progress that extends formtastic with an input type :recurring (form.schedule :as => :recurring), which renders an iCal-like interface and a before_filter to serialize the view into an IceCube object again, ghetto-ly.

My idea is to make it incredibility easy to add recurring attributes to a model and connect it easily in the view. All in a couple of lines.


So what does this give me? Indexed, Edit-able, Recurring attributes.

events stores a single day instance, and is used in the calendar view/helper say task.schedule stores the yaml'd IceCube object, so you can do calls like : task.schedule.next_suggestion.

Recap: I use two models, one flat, for the calendar display, and one attribute'd for the functionality.

Mastermind answered 23/10, 2010 at 0:19 Comment(0)
E
7

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

Exo answered 9/11, 2009 at 3:7 Comment(0)
S
5
  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!

Saltillo answered 17/9, 2008 at 17:50 Comment(0)
G
4

I'd recommend using the power of the date library and the semantics of the range module of ruby. A recurring event is really a time, a date range (a start & end) and usually a single day of the week. Using date & range you can answer any question:

#!/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

Produces all days of the event, including the leap year!

# =>"[\"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\"]"
Georg answered 17/9, 2008 at 18:1 Comment(2)
This is not very flexible. A recurring event model would often require specifying the repetition period (hourly, weekly, fortnightly, etc). Additionally the recurrence might not be qualified by a total number, rather an end date for the last occurrenceDolichocephalic
"A recurring event is [..] usually a single day of the week", this is just one limited use case and does not handle many others such as 'The 5th day of every month" etc.Sisterhood
U
4

From these answers, I've sort of sifted out a solution. I really like the idea of the link concept. Recurring events could be a linked list, with the tail knowing its recurrence rule. Changing one event would then be easy, because the links stay in place, and deleting an event is easy as well - you just unlink an event, delete it, and re-link the event before and after it. You still have to query recurring events every time someone looks at a new time period never been looked at before on the calendar, but otherwise this is pretty clean.

Ulm answered 17/9, 2008 at 18:4 Comment(0)
M
2

You could store the events as repeating, and if a particular instance was edited, create a new event with the same event ID. Then when looking up the event, search for all events with the same event ID to get all the information. I'm not sure if you rolled your own event library, or if you're using an existing one so it may not be possible.

Mcbroom answered 17/9, 2008 at 17:44 Comment(1)
I used this solution once. I like the principle of storing a modified instance as a new one-off event that knows who its mama is. That way you can leave all fields empty except the ones that are different for the child event. Note that you will have to have an extra field specifying which child of this mother you are editing.Kiesha
E
2

Check the article below for three good ruby date/time libraries. ice_cube in particular seems a solid choice for recurrence rules and other stuff that an event calendar would need. http://www.rubyinside.com/3-new-date-and-time-libraries-for-rubyists-3238.html

Emphysema answered 11/2, 2011 at 10:31 Comment(0)
Y
1

In javascript:

Handling recurring schedules: http://bunkat.github.io/later/

Handling complex events and dependencies between those schedules: http://bunkat.github.io/schedule/

Basically, you create the rules then you ask the lib to compute the next N recurring events (specifying a date range or not). The rules can be parsed / serialised for saving them into your model.

If you have a recurring event and would like to modify only one recurrence you can use the except() function to dismiss a particular day and then add a new modified event for this entry.

The lib supports very complex patterns, timezones and even croning events.

Yearling answered 23/11, 2014 at 10:18 Comment(0)
L
0

Store the events as repeating and dynamically display them, however allow the recurring event to contain a list of specific events that could override the default information on a specific day.

When you query the recurring event it can check for a specific override for that day.

If a user makes changes, then you can ask if he wants to update for all instances (default details) or just that day (make a new specific event and add it to the list).

If a user asks to delete all recurrences of this event you also have the list of specifics to hand and can remove them easily.

The only problematic case would be if the user wants to update this event and all future events. In which case you'll have to split the recurring event into two. At this point you may want to consider linking recurring events in some way so you can delete them all.

Lebbie answered 17/9, 2008 at 17:47 Comment(0)
T
0

For .NET programmers who are prepared to pay some licensing fees, you might find Aspose.Network useful... it includes an iCalendar compatible library for recurring appointments.

Torn answered 19/5, 2009 at 11:28 Comment(0)
G
0

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.

Genniegennifer answered 14/3, 2012 at 1:38 Comment(0)
T
0

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;
}   
Threemaster answered 6/11, 2014 at 2:31 Comment(0)
R
-5

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.

Rodman answered 1/2, 2010 at 14:12 Comment(2)
How soon we forget the lessons of y2k... :)Farfamed
Let's assume we have 1000 users, each with a couple of daily events. 3 events × 1000 users × 365 days × (2100-2011=89 years) = 97.5 million records. Instead of 3000 "plans". Um...Bullhorn

© 2022 - 2024 — McMap. All rights reserved.