¿Cuál es la mejor manera de modelar eventos recurrentes en una aplicación de calendario?

225

Estoy creando una aplicación de calendario grupal que necesita soportar eventos recurrentes, pero todas las soluciones que he encontrado para manejar estos eventos parecen un truco. Puedo limitar qué tan lejos se puede mirar, y luego generar todos los eventos a la vez. O puedo almacenar los eventos como repetitivos y mostrarlos dinámicamente cuando uno mira hacia adelante en el calendario, pero tendré que convertirlos en un evento normal si alguien quiere cambiar los detalles en una instancia particular del evento.

Estoy seguro de que hay una mejor manera de hacerlo, pero aún no la he encontrado. ¿Cuál es la mejor manera de modelar eventos recurrentes, donde puede cambiar detalles o eliminar instancias de eventos particulares?

(Estoy usando Ruby, pero no permita que eso limite su respuesta. Sin embargo, si hay una biblioteca específica de Ruby o algo así, es bueno saberlo).

Clinton N. Dreisbach
fuente

Respuestas:

93

Usaría un concepto de 'enlace' para todos los eventos recurrentes futuros. Se muestran dinámicamente en el calendario y se vinculan a un solo objeto de referencia. Cuando los eventos han tenido lugar, el enlace se rompe y el evento se convierte en una instancia independiente. Si intenta editar un evento recurrente, solicite cambiar todos los elementos futuros (es decir, cambiar una referencia vinculada única) o cambiar solo esa instancia (en cuyo caso, conviértala en una instancia independiente y luego realice el cambio). El último caso es un poco problemático, ya que debe realizar un seguimiento en su lista periódica de todos los eventos futuros que se convirtieron a una sola instancia. Pero, esto es completamente factible.

Entonces, en esencia, tenga 2 clases de eventos: instancias únicas y eventos recurrentes.

usuario16068
fuente
Realmente me gusta su idea de vincular y convertir eventos a independientes después de que hayan pasado. Dos preguntas: - ¿Por qué convertirlos a instancias fijas independientes? ¿Por qué no dejarlos completamente dinámicos? - ¿Puedes compartir referencias para el concepto de enlace propuesto? ¡Gracias por adelantado!
rtindru
@rtindru un caso de uso que encontré para convertir eventos a autónomos es cuando tiene que usar el modelo de eventos con otros modelos en su base de datos. Por ejemplo, para verificar la asistencia a un evento, querrá asociar usuarios con un evento real que sucedió (o sucederá).
Clinton Yeboah el
33

Puede haber muchos problemas con eventos recurrentes, permítanme destacar algunos que conozco.

Solución 1 - sin instancias

Almacene la cita original + datos de recurrencia, no almacene todas las instancias.

Problemas:

  • Tendrá que calcular todas las instancias en una ventana de fecha cuando las necesite, costoso
  • No es posible manejar excepciones (es decir, elimina una de las instancias, la mueve o, mejor dicho, no puede hacer esto con esta solución)

Solución 2 - almacenar instancias

Almacene todo desde 1, pero también todas las instancias, vinculadas a la cita original.

Problemas:

  • Ocupa mucho espacio (pero el espacio es barato, muy pequeño)
  • Las excepciones deben manejarse con gracia, especialmente si regresa y edita la cita original después de hacer una excepción. Por ejemplo, si mueve la tercera instancia un día hacia adelante, ¿qué pasa si retrocede y edita la hora de la cita original, reinserta otra en el día original y deja la movida? ¿Desvincular el movido? ¿Intenta cambiar el movido apropiadamente?

Por supuesto, si no vas a hacer excepciones, entonces cualquiera de las soluciones debería estar bien, y básicamente eliges entre un escenario de compensación tiempo / espacio.

Lasse V. Karlsen
fuente
36
¿Qué pasa si tiene una cita recurrente sin fecha de finalización? Tan barato como el espacio es, usted no tiene espacio infinito, por lo que la solución 2 es un no-arrancador allí ...
Shaul Behr
13
La solución n. ° 1 en realidad puede manejar excepciones. Por ejemplo, RFC5545 sugiere que se almacenen como: a) una lista de fechas excluidas (cuando elimina una ocurrencia); b) ocurrencias "materializadas" con referencias al prototipo (cuando mueve una ocurrencia).
Andy Mikhaylenko
@ Andy, algunas adiciones interesantes a la respuesta de Lasse. Voy a intentarlo.
Jonathan Wilson
1
@Shaul: No creo que sea un no titular. John Skeet, que es bastante respetado en SO, sugiere almacenar instancias generadas en su respuesta a la misma pregunta: stackoverflow.com/a/10151804/155268
Usuario
1
@Usuario: reconocido, gracias. Es muy extraño: hice mi comentario hace más de 4 años, y desde entonces no he tenido que lidiar con este problema. Justo ayer comencé a diseñar un nuevo módulo que involucra citas recurrentes, y me preguntaba cómo manejarlos. Y luego, recibí una notificación SO de tu comentario esta mañana. En serio espeluznante! ¡Pero gracias! :-)
Shaul Behr
21

He desarrollado múltiples aplicaciones basadas en el calendario y también he creado un conjunto de componentes de calendario JavaScript reutilizables que admiten la recurrencia. Escribí una descripción general de cómo diseñar para la recurrencia que podría ser útil para alguien. Si bien hay algunos bits que son específicos de la biblioteca que escribí, la gran mayoría de los consejos ofrecidos son generales para cualquier implementación de calendario.

Algunos de los puntos clave:

  • Almacene la recurrencia utilizando el formato iCal RRULE : esa es una rueda que realmente no desea reinventar
  • ¡NO almacene instancias de eventos recurrentes individuales como filas en su base de datos! Siempre almacene un patrón de recurrencia.
  • Hay muchas formas de diseñar su esquema de evento / excepción, pero se proporciona un ejemplo de punto de partida básico
  • Todos los valores de fecha / hora deben almacenarse en UTC y convertirse a local para su visualización
  • La fecha de finalización almacenada para un evento recurrente siempre debe ser la fecha de finalización del rango de recurrencia (o la "fecha máxima" de su plataforma si se repite "para siempre") y la duración del evento debe almacenarse por separado. Esto es para garantizar una forma sensata de consultar eventos más adelante.
  • Se incluye alguna discusión sobre la generación de instancias de eventos y estrategias de edición recurrente

Es un tema realmente complicado con muchos, muchos enfoques válidos para implementarlo. Diré que en realidad he implementado la recurrencia varias veces con éxito, y desconfiaría de recibir consejos sobre este tema de cualquiera que no lo haya hecho.

Brian Moeskau
fuente
Tal vez almacene las recurrencias como eventos cuando sucedan para que su historial de calendario sea exacto
Richard Haven
@ RichardHaven Nunca haría eso. Siempre debe generar instancias a partir de patrones RRULE consistentemente, pasado, presente o futuro. No habría razón para hacer algo diferente para los acontecimientos históricos. Su lógica simplemente debe evaluar un RRULE contra cualquier intervalo de fechas arbitrario y devolver instancias de eventos coincidentes.
Brian Moeskau
@BrianMoeskau resumen agradable y útil!
Przemek Nowak
@BrianMoeskau ¿Pero entonces las vistas anteriores de su calendario no muestran información inexacta cuando alguien edita el RRULE después de que ya ocurrieron algunos eventos? ¿O tal vez en ese caso "ramificaría" el RRULE y mantendría versiones modificadas de los patrones RRULE que representan exactamente las ocurrencias pasadas reales?
Christian
1
@christian Cuando se actualiza una regla de recurrencia en la mayoría de los calendarios, normalmente aparecen como "editar todos los eventos, o solo este o solo en el futuro", lo que permite al usuario elegir el comportamiento. En la mayoría de los casos, el usuario probablemente significa "cambiarlo en el futuro", pero de nuevo, depende de usted decidir cómo funciona su software y qué opciones le da al usuario.
Brian Moeskau
19

Es posible que desee ver las implementaciones de software iCalendar o el estándar en sí mismo ( RFC 2445 RFC 5545 ). Los que vienen a la mente rápidamente son los proyectos de Mozilla http://www.mozilla.org/projects/calendar/ Una búsqueda rápida revela también http://icalendar.rubyforge.org/ .

Se pueden considerar otras opciones dependiendo de cómo va a almacenar los eventos. ¿Está creando su propio esquema de base de datos? ¿Usando algo basado en iCalendar, etc.?

Kris Kumler
fuente
si pudieras proporcionar un enlace a uno de estos, tu publicación sería perfecta
Jean
77
Parece que RFC2445 ha quedado obsoleto por RFC5545 ( tools.ietf.org/html/rfc5545 )
Eric Freese
16

Estoy trabajando con lo siguiente:

y una gema en progreso que extiende formtastic con un tipo de entrada: recurring ( form.schedule :as => :recurring), que representa una interfaz similar a iCal y una before_filterpara serializar la vista en un IceCubeobjeto nuevamente, ghetto-ly.

Mi idea es hacer que sea increíblemente fácil agregar atributos recurrentes a un modelo y conectarlo fácilmente en la vista. Todo en un par de líneas.


Entonces, ¿qué me da esto? Atributos indexados, editables, recurrentes.

eventstiendas de una sola instancia día, y se utiliza en la vista de calendario / ayudante dicen que task.schedulealmacena la yaml'd IceCubeobjeto, para que pueda hacer llamadas como: task.schedule.next_suggestion.

Resumen: utilizo dos modelos, uno plano, para la visualización del calendario y otro atribuido para la funcionalidad.

Vee
fuente
Me interesaría ver lo que se te ocurrió. ¿Tienes un git / blog / prueba de concepto en alguna parte? ¡Gracias!
montrealmike
Estoy trabajando en algo similar también. Me encantaría ver su implementación
thoughtpunch
5
  1. Realice un seguimiento de una regla de recurrencia (probablemente basada en iCalendar, según @ Kris K. ). Esto incluirá un patrón y un rango (cada tercer martes, por 10 veces).
  2. Para cuando desee editar / eliminar una ocurrencia específica, realice un seguimiento de las fechas de excepción para la regla de recurrencia anterior (fechas en las que el evento no ocurre como lo especifica la regla).
  3. Si eliminó, eso es todo lo que necesita, si editó, cree otro evento y dele un ID de padre configurado para el evento principal. Puede elegir si desea incluir toda la información del evento principal en este registro, o si solo contiene los cambios y hereda todo lo que no cambia.

Tenga en cuenta que si permite reglas de recurrencia que no terminan, debe pensar en cómo mostrar su cantidad ahora infinita de información.

¡Espero que ayude!

bdukes
fuente
4

Recomiendo usar el poder de la biblioteca de fechas y la semántica del módulo de rango de ruby. Un evento recurrente es realmente un tiempo, un intervalo de fechas (inicio y final) y, por lo general, un solo día de la semana. Usando fecha y rango puede responder cualquier pregunta:

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

¡Produce todos los días del evento, incluido el año bisiesto!

# =>"[\"2008-01-04\", \"2008-01-11\", \"2008-01-18\", \"2008-01-25\", \"2008-02-01\", \"2008-02-08\", \"2008-02-15\", \"2008-02-22\", \"2008-02-29\", \"2008-03-07\", \"2008-03-14\", \"2008-03-21\", \"2008-03-28\"]"
Purfideas
fuente
2
Esto no es muy flexible. Un modelo de evento recurrente a menudo requeriría especificar el período de repetición (por hora, semanal, quincenal, etc.). Además, la repetición no puede ser calificado por un total, en lugar de una fecha final para la última ocurrencia
Bo Jeanes
"Un evento recurrente es [..] por lo general un único día de la semana", esto es sólo un caso de uso limitado y no maneja muchos otros como 'El quinto día de cada mes", etc.
TheRaven
3

A partir de estas respuestas, he encontrado una solución. Me gusta mucho la idea del concepto de enlace. Los eventos recurrentes podrían ser una lista vinculada, con la cola que conoce su regla de recurrencia. Cambiar un evento sería fácil, porque los enlaces permanecen en su lugar, y eliminar un evento también es fácil: simplemente desvincula un evento, lo elimina y vuelve a vincular el evento antes y después. Todavía tiene que consultar eventos recurrentes cada vez que alguien mira un nuevo período de tiempo nunca antes visto en el calendario, pero de lo contrario, esto es bastante limpio.

Clinton N. Dreisbach
fuente
2

Puede almacenar los eventos como repetitivos, y si se editó una instancia particular, cree un nuevo evento con el mismo ID de evento. Luego, cuando busque el evento, busque todos los eventos con el mismo ID de evento para obtener toda la información. No estoy seguro de si rodó su propia biblioteca de eventos, o si está utilizando una existente, por lo que puede que no sea posible.

Vincent McNabb
fuente
Usé esta solución una vez. Me gusta el principio de almacenar una instancia modificada como un nuevo evento único que sabe quién es su madre. De esa manera, puede dejar todos los campos vacíos, excepto los que son diferentes para el evento secundario. Tenga en cuenta que tendrá que tener un campo adicional que especifique qué hijo de esta madre está editando.
Wytze
1

En javascript:

Manejo de horarios recurrentes: http://bunkat.github.io/later/

Manejo de eventos complejos y dependencias entre esos horarios: http://bunkat.github.io/schedule/

Básicamente, crea las reglas y luego le pide a la biblioteca que calcule los próximos N eventos recurrentes (especificando un rango de fechas o no). Las reglas se pueden analizar / serializar para guardarlas en su modelo.

Si tiene un evento recurrente y desea modificar solo una recurrencia, puede usar la función except () para descartar un día en particular y luego agregar un nuevo evento modificado para esta entrada.

La biblioteca admite patrones muy complejos, zonas horarias e incluso eventos cronológicos.

Flavien Volken
fuente
0

Almacene los eventos como repetitivos y muéstrelos dinámicamente, sin embargo, permita que el evento recurrente contenga una lista de eventos específicos que podrían anular la información predeterminada en un día específico.

Cuando consulta el evento recurrente, puede verificar una anulación específica para ese día.

Si un usuario realiza cambios, puede preguntar si desea actualizar para todas las instancias (detalles predeterminados) o solo ese día (realice un nuevo evento específico y agréguelo a la lista).

Si un usuario solicita eliminar todas las repeticiones de este evento, también tiene la lista de detalles a mano y puede eliminarlos fácilmente.

El único caso problemático sería si el usuario desea actualizar este evento y todos los eventos futuros. En cuyo caso, tendrá que dividir el evento recurrente en dos. En este punto, es posible que desee considerar vincular eventos recurrentes de alguna manera para que pueda eliminarlos todos.

Andrew Johnson
fuente
0

Para los programadores .NET que están preparados para pagar algunas tarifas de licencia, puede encontrar útil Aspose.Network ... incluye una biblioteca compatible con iCalendar para citas periódicas .

Shaul Behr
fuente
0

Usted almacena los eventos en formato iCalendar directamente, lo que permite la repetición abierta, la localización de zonas horarias, etc.

Puede almacenarlos en un servidor CalDAV y luego, cuando desee mostrar los eventos, puede usar la opción del informe definido en CalDAV para pedirle al servidor que realice la expansión de los eventos recurrentes durante el período visualizado.

O bien, puede almacenarlos en una base de datos usted mismo y usar algún tipo de biblioteca de análisis iCalendar para hacer la expansión, sin necesidad de PUT / GET / REPORT para comunicarse con un servidor CalDAV de back-end. Probablemente esto sea más trabajo: estoy seguro de que los servidores CalDAV ocultan la complejidad en alguna parte.

Tener los eventos en formato iCalendar probablemente hará las cosas más simples a largo plazo ya que la gente siempre querrá que se exporten para poner otro software de todos modos.

karora
fuente
0

¡Simplemente he implementado esta función! La lógica es la siguiente, primero necesitas dos tablas. RuleTable almacena eventos paternos generales o de reciclaje. ItemTable es eventos de ciclo almacenados. Por ejemplo, cuando crea un evento cíclico, la hora de inicio para el 6 de noviembre de 2015, la hora de finalización para el 6 de diciembre (o para siempre), ciclo durante una semana. Inserta datos en una tabla de reglas, los campos son los siguientes:

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.

Ahora desea consultar datos del 20 de noviembre al 20 de diciembre. Puede escribir una función RecurringEventBE (inicio largo, final largo), en función del tiempo de inicio y finalización, WeekLy, puede calcular la colección que desea, <cycleA11.20, cycleA 11.27, cycleA 12.4 ......>. Además del 6 de noviembre, y el resto lo llamé un evento virtual. Cuando el usuario cambia el nombre de un evento virtual después (cicloA11.27, por ejemplo), inserta datos en una tabla de elementos. Los campos son los siguientes:

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

En la función RecurringEventBE (inicio largo, final largo), utiliza estos datos que cubren el evento virtual (cicloB11.27) perdón por mi inglés, lo intenté.

Este es mi RecurringEventBE :

public static List<Map<String, Object>> recurringData(Context context,
        long start, long end) { // 重复事件的模板处理,生成虚拟事件(根据日期段)
     long a = System.currentTimeMillis();
    List<Map<String, Object>> finalDataList = new ArrayList<Map<String, Object>>();

    List<Map<String, Object>> tDataList = BillsDao.selectTemplateBillRuleByBE(context); //RuleTablejust select recurringEvent
    for (Map<String, Object> iMap : tDataList) {

        int _id = (Integer) iMap.get("_id");
        long bk_billDuedate = (Long) iMap.get("ep_billDueDate"); // 相当于事件的开始日期 Start
        long bk_billEndDate = (Long) iMap.get("ep_billEndDate"); // 重复事件的截止日期 End
        int bk_billRepeatType = (Integer) iMap.get("ep_recurringType"); // recurring Type 

        long startDate = 0; // 进一步精确判断日记起止点,保证了该段时间断获取的数据不未空,减少不必要的处理
        long endDate = 0;

        if (bk_billEndDate == -1) { // 永远重复事件的处理

            if (end >= bk_billDuedate) {
                endDate = end;
                startDate = (bk_billDuedate <= start) ? start : bk_billDuedate; // 进一步判断日记起止点,这样就保证了该段时间断获取的数据不未空
            }

        } else {

            if (start <= bk_billEndDate && end >= bk_billDuedate) { // 首先判断起止时间是否落在重复区间,表示该段时间有重复事件
                endDate = (bk_billEndDate >= end) ? end : bk_billEndDate;
                startDate = (bk_billDuedate <= start) ? start : bk_billDuedate; // 进一步判断日记起止点,这样就保证了该段时间断获取的数据不未空
            }
        }

        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(bk_billDuedate); // 设置重复的开始日期

        long virtualLong = bk_billDuedate; // 虚拟时间,后面根据规则累加计算
        List<Map<String, Object>> virtualDataList = new ArrayList<Map<String, Object>>();// 虚拟事件

        if (virtualLong == startDate) { // 所要求的时间,小于等于父本时间,说明这个是父事件数据,即第一条父本数据

            Map<String, Object> bMap = new HashMap<String, Object>();
            bMap.putAll(iMap);
            bMap.put("indexflag", 1); // 1表示父本事件
            virtualDataList.add(bMap);
        }

        long before_times = 0; // 计算从要求时间start到重复开始时间的次数,用于定位第一次发生在请求时间段落的时间点
        long remainder = -1;
        if (bk_billRepeatType == 1) {

            before_times = (startDate - bk_billDuedate) / (7 * DAYMILLIS);
            remainder = (startDate - bk_billDuedate) % (7 * DAYMILLIS);

        } else if (bk_billRepeatType == 2) {

            before_times = (startDate - bk_billDuedate) / (14 * DAYMILLIS);
            remainder = (startDate - bk_billDuedate) % (14 * DAYMILLIS);

        } else if (bk_billRepeatType == 3) {

            before_times = (startDate - bk_billDuedate) / (28 * DAYMILLIS);
            remainder = (startDate - bk_billDuedate) % (28 * DAYMILLIS);

        } else if (bk_billRepeatType == 4) {

            before_times = (startDate - bk_billDuedate) / (15 * DAYMILLIS);
            remainder = (startDate - bk_billDuedate) % (15 * DAYMILLIS);

        } else if (bk_billRepeatType == 5) {

            do { // 该段代码根据日历处理每天重复事件,当事件比较多的时候效率比较低

                Calendar calendarCloneCalendar = (Calendar) calendar
                        .clone();
                int currentMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);
                calendarCloneCalendar.add(Calendar.MONTH, 1);
                int nextMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);

                if (currentMonthDay > nextMonthDay) {
                    calendar.add(Calendar.MONTH, 1 + 1);
                    virtualLong = calendar.getTimeInMillis();
                } else {
                    calendar.add(Calendar.MONTH, 1);
                    virtualLong = calendar.getTimeInMillis();
                }

            } while (virtualLong < startDate);

        } else if (bk_billRepeatType == 6) {

            do { // 该段代码根据日历处理每天重复事件,当事件比较多的时候效率比较低

                Calendar calendarCloneCalendar = (Calendar) calendar
                        .clone();
                int currentMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);
                calendarCloneCalendar.add(Calendar.MONTH, 2);
                int nextMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);

                if (currentMonthDay > nextMonthDay) {
                    calendar.add(Calendar.MONTH, 2 + 2);
                    virtualLong = calendar.getTimeInMillis();
                } else {
                    calendar.add(Calendar.MONTH, 2);
                    virtualLong = calendar.getTimeInMillis();
                }

            } while (virtualLong < startDate);

        } else if (bk_billRepeatType == 7) {

            do { // 该段代码根据日历处理每天重复事件,当事件比较多的时候效率比较低

                Calendar calendarCloneCalendar = (Calendar) calendar
                        .clone();
                int currentMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);
                calendarCloneCalendar.add(Calendar.MONTH, 3);
                int nextMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);

                if (currentMonthDay > nextMonthDay) {
                    calendar.add(Calendar.MONTH, 3 + 3);
                    virtualLong = calendar.getTimeInMillis();
                } else {
                    calendar.add(Calendar.MONTH, 3);
                    virtualLong = calendar.getTimeInMillis();
                }

            } while (virtualLong < startDate);

        } else if (bk_billRepeatType == 8) {

            do {
                calendar.add(Calendar.YEAR, 1);
                virtualLong = calendar.getTimeInMillis();
            } while (virtualLong < startDate);

        }

        if (remainder == 0 && virtualLong != startDate) { // 当整除的时候,说明当月的第一天也是虚拟事件,判断排除为父本,然后添加。不处理,一个月第一天事件会丢失
            before_times = before_times - 1;
        }

        if (bk_billRepeatType == 1) { // 单独处理天事件,计算出第一次出现在时间段的事件时间

            virtualLong = bk_billDuedate + (before_times + 1) * 7
                    * (DAYMILLIS);
            calendar.setTimeInMillis(virtualLong);

        } else if (bk_billRepeatType == 2) {

            virtualLong = bk_billDuedate + (before_times + 1) * (2 * 7)
                    * DAYMILLIS;
            calendar.setTimeInMillis(virtualLong);
        } else if (bk_billRepeatType == 3) {

            virtualLong = bk_billDuedate + (before_times + 1) * (4 * 7)
                    * DAYMILLIS;
            calendar.setTimeInMillis(virtualLong);
        } else if (bk_billRepeatType == 4) {

            virtualLong = bk_billDuedate + (before_times + 1) * (15)
                    * DAYMILLIS;
            calendar.setTimeInMillis(virtualLong);
        }

        while (startDate <= virtualLong && virtualLong <= endDate) { // 插入虚拟事件
            Map<String, Object> bMap = new HashMap<String, Object>();
            bMap.putAll(iMap);
            bMap.put("ep_billDueDate", virtualLong);
            bMap.put("indexflag", 2); // 2表示虚拟事件
            virtualDataList.add(bMap);

            if (bk_billRepeatType == 1) {

                calendar.add(Calendar.DAY_OF_MONTH, 7);

            } else if (bk_billRepeatType == 2) {

                calendar.add(Calendar.DAY_OF_MONTH, 2 * 7);

            } else if (bk_billRepeatType == 3) {

                calendar.add(Calendar.DAY_OF_MONTH, 4 * 7);

            } else if (bk_billRepeatType == 4) {

                calendar.add(Calendar.DAY_OF_MONTH, 15);

            } else if (bk_billRepeatType == 5) {

                Calendar calendarCloneCalendar = (Calendar) calendar
                        .clone();
                int currentMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);
                calendarCloneCalendar.add(Calendar.MONTH,
                        1);
                int nextMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);

                if (currentMonthDay > nextMonthDay) {
                    calendar.add(Calendar.MONTH, 1
                            + 1);
                } else {
                    calendar.add(Calendar.MONTH, 1);
                }

            }else if (bk_billRepeatType == 6) {

                Calendar calendarCloneCalendar = (Calendar) calendar
                        .clone();
                int currentMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);
                calendarCloneCalendar.add(Calendar.MONTH,
                        2);
                int nextMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);

                if (currentMonthDay > nextMonthDay) {
                    calendar.add(Calendar.MONTH, 2
                            + 2);
                } else {
                    calendar.add(Calendar.MONTH, 2);
                }

            }else if (bk_billRepeatType == 7) {

                Calendar calendarCloneCalendar = (Calendar) calendar
                        .clone();
                int currentMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);
                calendarCloneCalendar.add(Calendar.MONTH,
                        3);
                int nextMonthDay = calendarCloneCalendar
                        .get(Calendar.DAY_OF_MONTH);

                if (currentMonthDay > nextMonthDay) {
                    calendar.add(Calendar.MONTH, 3
                            + 3);
                } else {
                    calendar.add(Calendar.MONTH, 3);
                }

            } else if (bk_billRepeatType == 8) {

                calendar.add(Calendar.YEAR, 1);

            }
            virtualLong = calendar.getTimeInMillis();

        }

        finalDataList.addAll(virtualDataList);

    }// 遍历模板结束,产生结果为一个父本加若干虚事件的list

    /*
     * 开始处理重复特例事件特例事件,并且来时合并
     */
    List<Map<String, Object>>oDataList = BillsDao.selectBillItemByBE(context, start, end);
    Log.v("mtest", "特例结果大小" +oDataList );


    List<Map<String, Object>> delectDataListf = new ArrayList<Map<String, Object>>(); // finalDataList要删除的结果
    List<Map<String, Object>> delectDataListO = new ArrayList<Map<String, Object>>(); // oDataList要删除的结果


    for (Map<String, Object> fMap : finalDataList) { // 遍历虚拟事件

        int pbill_id = (Integer) fMap.get("_id");
        long pdue_date = (Long) fMap.get("ep_billDueDate");

        for (Map<String, Object> oMap : oDataList) {

            int cbill_id = (Integer) oMap.get("billItemHasBillRule");
            long cdue_date = (Long) oMap.get("ep_billDueDate");
            int bk_billsDelete = (Integer) oMap.get("ep_billisDelete");

            if (cbill_id == pbill_id) {

                if (bk_billsDelete == 2) {// 改变了duedate的特殊事件
                    long old_due = (Long) oMap.get("ep_billItemDueDateNew");

                    if (old_due == pdue_date) {

                        delectDataListf.add(fMap);//该改变事件在时间范围内,保留oMap

                    }

                } else if (bk_billsDelete == 1) {

                    if (cdue_date == pdue_date) {

                        delectDataListf.add(fMap);
                        delectDataListO.add(oMap);

                    }

                } else {

                    if (cdue_date == pdue_date) {
                        delectDataListf.add(fMap);
                    }

                }

            }
        }// 遍历特例事件结束

    }// 遍历虚拟事件结束
    // Log.v("mtest", "delectDataListf的大小"+delectDataListf.size());
    // Log.v("mtest", "delectDataListO的大小"+delectDataListO.size());
    finalDataList.removeAll(delectDataListf);
    oDataList.removeAll(delectDataListO);
    finalDataList.addAll(oDataList);
    List<Map<String, Object>> mOrdinaryList = BillsDao.selectOrdinaryBillRuleByBE(context, start, end);
    finalDataList.addAll(mOrdinaryList);
    // Log.v("mtest", "finalDataList的大小"+finalDataList.size());
    long b = System.currentTimeMillis();
    Log.v("mtest", "算法耗时"+(b-a));

    return finalDataList;
}   
fozua
fuente
-5

¿Qué pasa si tiene una cita recurrente sin fecha de finalización? Tan barato como es el espacio, no tienes espacio infinito, por lo que la Solución 2 no es un iniciador allí ...

¿Puedo sugerir que "sin fecha de finalización" se puede resolver a una fecha de finalización de fin de siglo Incluso para un evento diario, la cantidad de espacio sigue siendo barata.

poumtatalia
fuente
77
Cuán pronto olvidamos las lecciones de y2k ... :)
Ian Mercer
10
Supongamos que tenemos 1000 usuarios, cada uno con un par de eventos diarios. 3 eventos × 1000 usuarios × 365 días × (2100-2011 = 89 años) = 97.5 millones de registros. En lugar de 3000 "planes". Um ...
Andy Mikhaylenko