personal para mejor gratis google español descargar celular calendario app agenda ruby algorithm calendar data-modeling recurrence

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



la mejor agenda para android 2018 (18)

Estoy creando una aplicación de calendario grupal que necesita admitir eventos recurrentes, pero todas las soluciones que he encontrado para manejar estos eventos parecen un hack. Puedo limitar la distancia que uno puede ver y luego generar todos los eventos a la vez. O puedo almacenar los eventos como repetidos 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 todavía no lo he encontrado. ¿Cuál es la mejor manera de modelar eventos recurrentes, donde puede cambiar los detalles o eliminar instancias de eventos particulares?

(Estoy usando Ruby, pero no permitas que eso restrinja tu respuesta. Si hay una biblioteca específica de Ruby o algo así, es bueno saberlo).


¿Qué sucede si tiene una cita recurrente sin fecha de finalización? Por muy barato que sea el espacio, no tiene espacio infinito, por lo que la Solución 2 no es un motor de arranque 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.


  1. Mantenga un registro de una regla de recurrencia (probablemente basada en iCalendar, por @ Kris K. ). Esto incluirá un patrón y un rango (cada tercer martes, por 10 ocurrencias).
  2. Para cuando desee editar / eliminar una ocurrencia específica, haga un seguimiento de las fechas de excepción para la regla de recurrencia anterior (fechas en las que el evento no ocurre como especifica la regla).
  3. Si eliminó, eso es todo lo que necesita, si editó, cree otro evento y asigne una identificación principal al evento principal. Puede elegir si 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 que las reglas de repetición no terminen, debe pensar en cómo mostrar su cantidad ahora infinita de información.

¡Espero que ayude!


A partir de estas respuestas, he echado un vistazo a una solución. Me gusta mucho la idea del concepto de enlace. Los eventos recurrentes pueden ser una lista enlazada, con la cola conociendo su regla de recurrencia. Cambiar un evento sería fácil, ya que 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. Aún tiene que consultar eventos recurrentes cada vez que alguien mira un nuevo período de tiempo que nunca se ha visto antes en el calendario, pero por lo demás esto es bastante claro.


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 visualizar los eventos, puede usar la opción del informe definido en CalDAV para pedirle al servidor que haga la expansión de los eventos recurrentes a lo largo del período visto.

O puede almacenarlos en una base de datos y utilizar algún tipo de biblioteca de análisis iCalendar para realizar la expansión, sin necesidad de PUT / GET / REPORT para hablar con un servidor CalDAV de back-end. Probablemente sea más trabajo, estoy seguro de que los servidores CalDAV ocultan la complejidad en algún lugar.

Tener los eventos en el formato iCalendar probablemente simplificará las cosas a largo plazo, ya que la gente siempre querrá que se exporten para ponerlos en otro software de todos modos.


Almacene los eventos como repetidos y los muestre 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 todas las instancias (detalles predeterminados) o solo ese día (crear un nuevo evento específico y agregarlo a la lista).

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

El único caso sería problemático si el usuario quiere actualizar este evento y todos los eventos futuros. En cuyo caso tendrás 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.


En javascript:

Gestión de planificaciones recurrentes: http://bunkat.github.io/later/

Manejo de eventos complejos y dependencias entre esas programaciones: http://bunkat.github.io/schedule/

Básicamente, usted 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 de croning.


Es posible que desee consultar las implementaciones del software iCalendar o el estándar en sí ( 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 vaya a almacenar los eventos. ¿Estás construyendo tu propio esquema de base de datos? ¿Usando algo basado en iCalendar, etc.?


Estoy trabajando con lo siguiente:

y una gema en curso que extiende formtastic con un tipo de entrada: recurring ( form.schedule :as => :recurring ), que presenta una interfaz similar a iCal y un before_filter para serializar la vista en un objeto IceCube nuevamente, de forma guiada.

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.

events almacenan una instancia de un solo día, y se usan en la vista de calendario / helper say task.schedule almacena el objeto IceCube yaml''d, por lo que puede hacer llamadas como: task.schedule.next_suggestion .

Resumen: uso dos modelos, uno plano, para la visualización del calendario y un atributo para la funcionalidad.



He desarrollado múltiples aplicaciones basadas en el calendario y también he creado un conjunto de componentes de calendario de JavaScript reutilizables que admiten la repetición. Escribí una visión general de cómo diseñar para la recurrencia que podría ser útil a 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 maneras de diseñar el esquema de eventos / excepción, pero no se proporciona un ejemplo básico punto de partida
  • 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 asegurar una forma sensata de consultar eventos más adelante.
  • Se incluye un poco de discusión sobre la generación de instancias de eventos y estrategias de edición de repetición

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


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


Puede almacenar los eventos como repetidos y, si se edita una instancia en 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 ha introducido su propia biblioteca de eventos o si está utilizando una existente, por lo que es posible que no sea posible.


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

Solución 1 - no hay instancias

Almacenar cita original + datos de recurrencia, no almacenar todas las instancias.

Problemas:

  • Tendrá que calcular todas las instancias en una ventana de fecha cuando las necesite, costoso
  • No se pueden manejar las excepciones (es decir, si 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, enlazadas a la cita original.

Problemas:

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

Por supuesto, si no va a hacer excepciones, entonces cualquiera de las soluciones debería estar bien, y básicamente elige un escenario de intercambio de tiempo / espacio.


Recomiendo usar el poder de la biblioteca de fechas y la semántica del módulo de rango de ruby. Un evento recurrente es en realidad una hora, un intervalo de fechas (un comienzo y un final) y generalmente 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/"]"



Simplemente he implementado esta característica! La lógica es la siguiente, primero necesitas dos tablas. RuleTable tienda general o recicla eventos paternos. ItemTable se almacena los eventos del ciclo. Por ejemplo, cuando crea un evento cíclico, la hora de inicio del 6 de noviembre de 2015, la hora de finalización del 6 de diciembre (o para siempre), el ciclo de una semana. Insertar 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), según el tiempo de inicio y finalización, WeekLy, puede calcular la colección que desee, <cicloA11.20, cicloA 11.27, cicloA 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 (ciclo B11.27) disculpa 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); //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; }


Yo usaría un concepto de "enlace" para todos los eventos recurrentes futuros. Se muestran dinámicamente en el calendario y se vinculan a un único objeto de referencia. Cuando se han producido eventos, el enlace se rompe y el evento se convierte en una instancia independiente. Si intenta editar un evento recurrente, entonces solicite cambiar todos los elementos futuros (es decir, cambiar una referencia vinculada única) o cambiar solo esa instancia (en cuyo caso, convierta esto en una instancia independiente y luego realice el cambio). Este último caso es ligeramente problemático, ya que necesita realizar un seguimiento en su lista recurrente de todos los eventos futuros que se convirtieron en una sola instancia. Pero, esto es totalmente factible.

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