Quelle est la meilleure façon de modéliser des événements récurrents dans une application de calendrier?

225

Je crée une application de calendrier de groupe qui doit prendre en charge les événements récurrents, mais toutes les solutions que j'ai trouvées pour gérer ces événements semblent être un hack. Je peux limiter jusqu'où l'on peut regarder, puis générer tous les événements à la fois. Ou je peux stocker les événements en les répétant et les afficher dynamiquement quand on regarde en avant sur le calendrier, mais je devrai les convertir en un événement normal si quelqu'un veut changer les détails d'une instance particulière de l'événement.

Je suis sûr qu'il existe une meilleure façon de procéder, mais je ne l'ai pas encore trouvée. Quelle est la meilleure façon de modéliser des événements récurrents, où vous pouvez modifier les détails ou supprimer des instances d'événement particulières?

(J'utilise Ruby, mais s'il vous plaît ne laissez pas cela limiter votre réponse. S'il y a une bibliothèque spécifique à Ruby ou quelque chose, cependant, c'est bon à savoir.)

Clinton N. Dreisbach
la source

Réponses:

93

J'utiliserais un concept de «lien» pour tous les futurs événements récurrents. Ils sont affichés dynamiquement dans le calendrier et sont liés à un seul objet de référence. Lorsque des événements ont eu lieu, le lien est rompu et l'événement devient une instance autonome. Si vous tentez de modifier un événement récurrent, invitez à modifier tous les éléments futurs (c'est-à-dire à modifier une seule référence liée) ou à modifier uniquement cette instance (auquel cas convertissez-la en une instance autonome, puis apportez des modifications). Ce dernier boîtier est légèrement problématique car vous devez garder une trace dans votre liste récurrente de tous les événements futurs qui ont été convertis en une seule instance. Mais, c'est tout à fait faisable.

Donc, essentiellement, vous disposez de 2 classes d'événements - des instances uniques et des événements récurrents.

user16068
la source
Aimez vraiment votre idée de lier et de convertir des événements en autonomes après leur passage. Deux questions: - Pourquoi les convertir en instances fixes autonomes? Pourquoi ne pas les laisser complètement dynamiques? - Pouvez-vous partager une référence pour le concept de lien proposé! Merci d'avance!
rtindru
@rtindru, un cas d'utilisation que j'ai trouvé pour convertir des événements en autonome, c'est quand vous devez utiliser le modèle d' événement avec d'autres modèles dans votre base de données. Par exemple, pour vérifier la participation à un événement, vous souhaiterez associer des utilisateurs à un événement réel qui s'est produit (ou se produira).
Clinton Yeboah
33

Il peut y avoir de nombreux problèmes avec les événements récurrents, permettez-moi d'en souligner quelques-uns que je connais.

Solution 1 - pas d'instances

Stockez le rendez-vous d'origine + les données de récurrence, ne stockez pas toutes les instances.

Problèmes:

  • Vous devrez calculer toutes les instances dans une fenêtre de date quand vous en aurez besoin, coûteux
  • Impossible de gérer les exceptions (c'est-à-dire que vous supprimez l'une des instances, ou la déplacez, ou plutôt, vous ne pouvez pas le faire avec cette solution)

Solution 2 - Stocker des instances

Stockez tout depuis 1, mais aussi toutes les instances, liées au rendez-vous d'origine.

Problèmes:

  • Prend beaucoup d'espace (mais l'espace est bon marché, donc mineur)
  • Les exceptions doivent être gérées avec élégance, surtout si vous revenez en arrière et modifiez le rendez-vous d'origine après avoir fait une exception. Par exemple, si vous déplacez la troisième instance un jour en avant, que se passe-t-il si vous revenez en arrière et modifiez l'heure du rendez-vous d'origine, réinsérez-en une autre le jour d'origine et quittez celle déplacée? Dissocier celui déplacé? Essayez de changer celui déplacé de manière appropriée?

Bien sûr, si vous n'allez pas faire d'exceptions, alors l'une ou l'autre solution devrait convenir, et vous choisissez essentiellement un scénario de compromis temps / espace.

Lasse V. Karlsen
la source
36
Et si vous avez un rendez-vous récurrent sans date de fin? Aussi bon que l'espace, vous n'avez pas d'espace infini, donc la Solution 2 est un non-star là-bas ...
Shaul Behr
13
La solution n ° 1 peut gérer les exceptions. Par exemple, la RFC5545 suggère qu'elles sont stockées comme: a) une liste de dates exclues (lorsque vous supprimez une occurrence); b) des occurrences "matérialisées" avec des références au prototype (lorsque vous déplacez une occurrence).
Andy Mikhaylenko
@Andy, quelques ajouts intéressants à la réponse de Lasse. Je vais essayer.
Jonathan Wilson
1
@Shaul: Je ne pense pas que ce soit un non-starter. John Skeet, qui est assez bien respecté sur SO, suggère de stocker les instances générées dans sa réponse à essentiellement la même question: stackoverflow.com/a/10151804/155268
Utilisateur
1
@User - reconnu, merci. C'est tellement bizarre - j'ai fait mon commentaire il y a plus de 4 ans, et je n'ai pas vraiment eu besoin de traiter ce problème depuis. Pas plus tard qu'hier, j'ai commencé à concevoir un nouveau module qui implique des rendez-vous récurrents, et je me demandais comment les gérer. Et puis - j'ai reçu une notification SO de votre commentaire ce matin. Sérieusement effrayant! Mais merci! :-)
Shaul Behr
21

J'ai développé plusieurs applications basées sur le calendrier et j'ai également créé un ensemble de composants de calendrier JavaScript réutilisables qui prennent en charge la récurrence. J'ai rédigé un aperçu de la façon de concevoir pour la récurrence qui pourrait être utile à quelqu'un. Bien qu'il existe quelques bits spécifiques à la bibliothèque que j'ai écrite, la grande majorité des conseils offerts sont généraux pour toute implémentation de calendrier.

Quelques points clés:

  • Stocker la récurrence en utilisant le format iCal RRULE - c'est une roue que vous ne voulez vraiment pas réinventer
  • Ne stockez PAS les instances d' événements récurrents individuels sous forme de lignes dans votre base de données! Enregistrez toujours un modèle de récurrence.
  • Il existe de nombreuses façons de concevoir votre schéma d'événement / exception, mais un exemple de point de départ de base est fourni
  • Toutes les valeurs de date / heure doivent être stockées en UTC et converties en locales pour l'affichage
  • La date de fin stockée pour un événement récurrent doit toujours être la date de fin de la plage de récurrence (ou la "date max" de votre plateforme si elle se répète "pour toujours") et la durée de l'événement doit être stockée séparément. Il s'agit de garantir une manière saine d'interroger les événements ultérieurement.
  • Une discussion sur la génération d'instances d'événements et les stratégies d'édition de récurrence est incluse

C'est un sujet vraiment compliqué avec de nombreuses approches valables pour le mettre en œuvre. Je dirai que j'ai effectivement mis en œuvre la récurrence plusieurs fois avec succès, et je me garderais de prendre conseil à ce sujet de quiconque ne l'a pas fait.

Brian Moeskau
la source
Peut-être stocker les récurrences en tant qu'événements quand elles se produisent afin que votre historique de calendrier soit précis
Richard Haven
@RichardHaven Je ne ferais jamais ça. Vous devez toujours générer des instances à partir de modèles RRULE de manière cohérente, passée, présente ou future. Il n'y aurait aucune raison de faire quelque chose de différent pour les événements historiques. Votre logique doit simplement évaluer un RRULE par rapport à toute plage de dates arbitraire et renvoyer des instances d'événements correspondantes.
Brian Moeskau
@BrianMoeskau aperçu agréable et utile!
Przemek Nowak
@BrianMoeskau Mais alors, les vues passées de votre calendrier ne montreraient-elles pas des informations inexactes lorsque quelqu'un modifie le RRULE après que certains événements se soient déjà produits? Ou peut-être que dans ce cas, vous "brancheriez" le RRULE et conserveriez des versions modifiées des modèles RRULE représentant exactement les événements passés réels?
christian
1
@christian Lorsque vous mettez à jour une règle de récurrence dans la plupart des calendriers, ils vous invitent généralement à "modifier tous les événements, ou celui-ci uniquement, ou à venir uniquement", permettant à l'utilisateur de choisir le comportement. Dans la plupart des cas, l'utilisateur signifie probablement «changer la situation à l'avenir», mais encore une fois, c'est à vous de décider comment votre logiciel fonctionne et quelles options vous donnez à l'utilisateur.
Brian Moeskau
19

Vous voudrez peut-être regarder les implémentations du logiciel iCalendar ou la norme elle-même ( RFC 2445 RFC 5545 ). Ceux qui viennent à l'esprit rapidement sont les projets Mozilla http://www.mozilla.org/projects/calendar/ Une recherche rapide révèle également http://icalendar.rubyforge.org/ .

D'autres options peuvent être envisagées en fonction de la façon dont vous allez stocker les événements. Construisez-vous votre propre schéma de base de données? Vous utilisez quelque chose basé sur iCalendar, etc.?

Kris Kumler
la source
si vous pouviez simplement fournir un lien vers l'un de ces articles, votre message serait parfait
Jean
7
On dirait que RFC2445 a été rendu obsolète par RFC5545 ( tools.ietf.org/html/rfc5545 )
Eric Freese
16

Je travaille avec les éléments suivants:

et un joyau en cours qui étend formtastic avec un type d'entrée: recurring ( form.schedule :as => :recurring), qui rend une interface de type iCal et a before_filterpour sérialiser à nouveau la vue en un IceCubeobjet, ghetto-ly.

Mon idée est de faciliter l'incrédibilité pour ajouter des attributs récurrents à un modèle et le connecter facilement dans la vue. Le tout en quelques lignes.


Alors qu'est-ce que cela me donne? Attributs indexés, modifiables et récurrents.

eventsstocke une seule instance de jour, et est utilisé dans l'affichage du calendrier / aide disent task.schedulestocke le yaml'd IceCubeobjet, de sorte que vous pouvez faire des appels comme: task.schedule.next_suggestion.

Récapitulation: j'utilise deux modèles, un plat, pour l'affichage du calendrier, et un attribut pour la fonctionnalité.

Vee
la source
Je serais intéressé de voir ce que vous avez proposé. Avez-vous un git / blog / preuve de concept n'importe où? Merci!
montrealmike
Je travaille aussi sur quelque chose de similaire. J'adorerais voir votre implémentation
penséepunch
5
  1. Gardez une trace d'une règle de récurrence (probablement basée sur iCalendar, par @ Kris K. ). Cela comprendra un modèle et une plage (tous les troisièmes mardis, pour 10 occurrences).
  2. Si vous souhaitez modifier / supprimer une occurrence spécifique, gardez une trace des dates d'exception pour la règle de récurrence ci-dessus (dates où l'événement ne se produit pas comme la règle le spécifie).
  3. Si vous avez supprimé, c'est tout ce dont vous avez besoin, si vous avez modifié, créez un autre événement et attribuez-lui un ID parent défini pour l'événement principal. Vous pouvez choisir d'inclure toutes les informations de l'événement principal dans cet enregistrement, ou s'il contient uniquement les modifications et hérite de tout ce qui ne change pas.

Notez que si vous autorisez des règles de récurrence qui ne se terminent pas, vous devez réfléchir à la façon d'afficher votre quantité désormais infinie d'informations.

J'espère que cela pourra aider!

bdukes
la source
4

Je recommanderais d'utiliser la puissance de la bibliothèque de dates et la sémantique du module de plage de ruby. Un événement récurrent est en réalité une heure, une plage de dates (un début et une fin) et généralement un seul jour de la semaine. En utilisant la date et la plage, vous pouvez répondre à n'importe quelle 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

Produit tous les jours de l'événement, y compris l'année bissextile!

# =>"[\"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
la source
2
Ce n'est pas très flexible. Un modèle d'événement récurrent nécessiterait souvent de spécifier la période de répétition (horaire, hebdomadaire, bimensuelle, etc.). De plus, la récurrence pourrait ne pas être qualifiée par un nombre total, plutôt par une date de fin pour la dernière occurrence
Bo Jeanes
"Un événement récurrent est [..] généralement un seul jour de la semaine", ce n'est qu'un cas d'utilisation limitée et ne gère pas beaucoup d'autres tels que "Le 5ème jour de chaque mois" etc.
theraven
3

À partir de ces réponses, j'ai en quelque sorte trouvé une solution. J'aime vraiment l'idée du concept de lien. Les événements récurrents peuvent être une liste chaînée, la queue connaissant sa règle de récurrence. La modification d'un événement serait alors facile, car les liens restent en place, et la suppression d'un événement est également facile - il suffit de dissocier un événement, de le supprimer et de le lier à nouveau avant et après. Vous devez toujours interroger les événements récurrents chaque fois que quelqu'un regarde une nouvelle période jamais vue auparavant dans le calendrier, mais sinon c'est assez propre.

Clinton N. Dreisbach
la source
2

Vous pouvez stocker les événements comme répétitifs et, si une instance particulière a été modifiée, créer un nouvel événement avec le même ID d'événement. Ensuite, lorsque vous recherchez l'événement, recherchez tous les événements avec le même ID d'événement pour obtenir toutes les informations. Je ne sais pas si vous avez lancé votre propre bibliothèque d'événements, ou si vous en utilisez une existante, donc ce n'est peut-être pas possible.

Vincent McNabb
la source
J'ai utilisé cette solution une fois. J'aime le principe de stockage d'une instance modifiée comme un nouvel événement ponctuel qui sait qui est sa maman. De cette façon, vous pouvez laisser tous les champs vides, à l'exception de ceux qui sont différents pour l'événement enfant. Notez que vous devrez avoir un champ supplémentaire spécifiant quel enfant de cette mère vous éditez.
Wytze
1

En javascript:

Gestion des plannings récurrents: http://bunkat.github.io/later/

Gestion des événements complexes et des dépendances entre ces planifications: http://bunkat.github.io/schedule/

Fondamentalement, vous créez les règles puis vous demandez à la bibliothèque de calculer les N événements récurrents suivants (en spécifiant une plage de dates ou non). Les règles peuvent être analysées / sérialisées pour les enregistrer dans votre modèle.

Si vous avez un événement récurrent et souhaitez modifier une seule récurrence, vous pouvez utiliser la fonction except () pour ignorer un jour particulier, puis ajouter un nouvel événement modifié pour cette entrée.

La bibliothèque prend en charge des motifs très complexes, des fuseaux horaires et même des événements de croning.

Flavien Volken
la source
0

Stockez les événements en les répétant et affichez-les dynamiquement, mais autorisez l'événement récurrent à contenir une liste d'événements spécifiques qui pourraient remplacer les informations par défaut un jour spécifique.

Lorsque vous interrogez l'événement récurrent, il peut rechercher un remplacement spécifique pour ce jour.

Si un utilisateur apporte des modifications, vous pouvez lui demander s'il souhaite mettre à jour pour toutes les instances (détails par défaut) ou juste ce jour-là (créer un nouvel événement spécifique et l'ajouter à la liste).

Si un utilisateur demande de supprimer toutes les récurrences de cet événement, vous avez également la liste des détails à portée de main et vous pouvez les supprimer facilement.

Le seul cas problématique serait si l'utilisateur souhaite mettre à jour cet événement et tous les événements futurs. Dans ce cas, vous devrez diviser l'événement récurrent en deux. À ce stade, vous souhaiterez peut-être envisager de lier des événements récurrents d'une manière quelconque afin de pouvoir les supprimer tous.

Andrew Johnson
la source
0

Pour les programmeurs .NET qui sont prêts à payer des frais de licence, vous pourriez trouver Aspose.Network utile ... il comprend une bibliothèque compatible iCalendar pour les rendez-vous récurrents.

Shaul Behr
la source
0

Vous stockez directement les événements au format iCalendar, ce qui permet une répétition ouverte, une localisation de fuseau horaire, etc.

Vous pouvez les stocker dans un serveur CalDAV, puis lorsque vous souhaitez afficher les événements, vous pouvez utiliser l'option du rapport défini dans CalDAV pour demander au serveur de faire l'expansion des événements récurrents sur la période affichée.

Ou vous pouvez les stocker vous-même dans une base de données et utiliser une sorte de bibliothèque d'analyse iCalendar pour faire l'expansion, sans avoir besoin de PUT / GET / REPORT pour parler à un serveur CalDAV backend. C'est probablement plus de travail - je suis sûr que les serveurs CalDAV cachent la complexité quelque part.

Le fait d'avoir les événements au format iCalendar simplifiera probablement les choses à long terme, car les gens voudront toujours qu'ils soient exportés pour être intégrés à d'autres logiciels de toute façon.

Karora
la source
0

J'ai simplement implémenté cette fonctionnalité! La logique est la suivante, vous avez d'abord besoin de deux tables. RuleTable stocke les événements paternels généraux ou recyclés. ItemTable est des événements de cycle stockés. Par exemple, lorsque vous créez un événement cyclique, l'heure de début pour le 6 novembre 2015, l'heure de fin pour le 6 décembre (ou pour toujours), cycle d'une semaine. Vous insérez des données dans une table de règles, les champs sont les suivants:

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.

Vous souhaitez maintenant interroger les données du 20 novembre au 20 décembre. Vous pouvez écrire une fonction RecurringEventBE (début long, fin longue), en fonction des heures de début et de fin, WeekLy, vous pouvez calculer la collection que vous voulez, <cycleA11.20, cycleA 11.27, cycleA 12.4 ......>. En plus du 6 novembre et du reste, je l'ai appelé un événement virtuel. Lorsque l'utilisateur modifie le nom d'un événement virtuel après (cycleA11.27 par exemple), vous insérez des données dans un ItemTable. Les champs sont les suivants:

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

Dans la fonction RecurringEventBE (long start, long end), vous utilisez ces données couvrant l'événement virtuel (cycleB11.27) désolé pour mon anglais, j'ai essayé.

Ceci est mon événement récurrentBE:

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
la source
-5

Et si vous avez un rendez-vous récurrent sans date de fin? Aussi bon que l'espace, vous n'avez pas d'espace infini, donc la Solution 2 est un non-démarreur là-bas ...

Puis-je suggérer que "aucune date de fin" ne peut être résolue à une date de fin à la fin du siècle. Même pour un événement quotidien, la quantité d'espace reste bon marché.

poumtatalia
la source
7
Combien de temps nous oublions les leçons de l'an 2000 ... :)
Ian Mercer
10
Supposons que nous avons 1000 utilisateurs, chacun avec quelques événements quotidiens. 3 événements × 1000 utilisateurs × 365 jours × (2100-2011 = 89 ans) = 97,5 millions d'enregistrements. Au lieu de 3000 "plans". Hum ...
Andy Mikhaylenko