Wie lassen sich wiederkehrende Ereignisse in einer Kalenderanwendung am besten modellieren?

224

Ich erstelle eine Gruppenkalenderanwendung, die wiederkehrende Ereignisse unterstützen muss, aber alle Lösungen, die ich zur Behandlung dieser Ereignisse entwickelt habe, scheinen ein Hack zu sein. Ich kann einschränken, wie weit man vorausschauen kann, und dann alle Ereignisse gleichzeitig generieren. Oder ich kann die Ereignisse als sich wiederholend speichern und dynamisch anzeigen, wenn man im Kalender nach vorne schaut, aber ich muss sie in ein normales Ereignis konvertieren, wenn jemand die Details zu einer bestimmten Instanz des Ereignisses ändern möchte.

Ich bin mir sicher, dass es einen besseren Weg gibt, aber ich habe ihn noch nicht gefunden. Was ist der beste Weg, um wiederkehrende Ereignisse zu modellieren, bei denen Sie Details bestimmter Ereignisinstanzen ändern oder löschen können?

(Ich verwende Ruby, aber bitte lassen Sie sich dadurch Ihre Antwort nicht einschränken. Wenn es jedoch eine Ruby-spezifische Bibliothek oder ähnliches gibt, ist das gut zu wissen.)

Clinton N. Dreisbach
quelle

Antworten:

93

Ich würde ein "Link" -Konzept für alle zukünftigen wiederkehrenden Ereignisse verwenden. Sie werden dynamisch im Kalender angezeigt und mit einem einzelnen Referenzobjekt verknüpft. Wenn Ereignisse stattgefunden haben, wird die Verbindung unterbrochen und das Ereignis wird zu einer eigenständigen Instanz. Wenn Sie versuchen, ein wiederkehrendes Ereignis zu bearbeiten, werden Sie aufgefordert, alle zukünftigen Elemente zu ändern (dh eine einzelne verknüpfte Referenz zu ändern) oder nur diese Instanz zu ändern (in diesem Fall konvertieren Sie diese in eine eigenständige Instanz und nehmen Sie dann Änderungen vor). Letzteres ist etwas problematisch, da Sie in Ihrer wiederkehrenden Liste aller zukünftigen Ereignisse, die in eine einzelne Instanz konvertiert wurden, den Überblick behalten müssen. Dies ist jedoch durchaus machbar.

Haben Sie also im Wesentlichen zwei Ereignisklassen - einzelne Instanzen und wiederkehrende Ereignisse.

user16068
quelle
Mag deine Idee, Ereignisse zu verknüpfen und in eigenständige Ereignisse umzuwandeln, nachdem sie vergangen sind. Zwei Fragen: - Warum sie überhaupt in eigenständige feste Instanzen konvertieren? Warum lassen Sie sie nicht völlig dynamisch? - Können Sie die Referenz für das vorgeschlagene Link-Konzept teilen? Danke im Voraus!
rtindru
@rtindru Ein Anwendungsfall, den ich für die Konvertierung von Ereignissen in Standalone gefunden habe, ist, wenn Sie das Ereignismodell mit anderen Modellen in Ihrer Datenbank verwenden müssen. Wenn Sie beispielsweise die Teilnahme an einem Ereignis überprüfen möchten, möchten Sie Benutzer einem realen Ereignis zuordnen, das aufgetreten ist (oder eintreten wird).
Clinton Yeboah
60

Martin Fowler - Wiederkehrende Ereignisse für Kalender enthält einige interessante Einblicke und Muster.

Runt Gem implementiert dieses Muster.

Daniel Maurić
quelle
Einige bessere Codebeispiele wären nett, kennt jemand ein Projekt, das dies implementiert hat?
Tadeu Maia
33

Bei wiederkehrenden Ereignissen kann es viele Probleme geben. Lassen Sie mich einige hervorheben, von denen ich weiß.

Lösung 1 - keine Instanzen

Speichern Sie die ursprünglichen Termin- und Wiederholungsdaten. Speichern Sie nicht alle Instanzen.

Probleme:

  • Sie müssen alle Instanzen in einem Datumsfenster berechnen, wenn Sie sie benötigen. Dies ist kostspielig
  • Ausnahmen können nicht behandelt werden (dh Sie löschen eine der Instanzen oder verschieben sie, oder besser gesagt, Sie können dies mit dieser Lösung nicht tun).

Lösung 2 - Instanzen speichern

Speichern Sie alles von 1, aber auch alle Instanzen, die mit dem ursprünglichen Termin verknüpft sind.

Probleme:

  • Nimmt viel Platz ein (aber Platz ist billig, so gering)
  • Ausnahmen müssen ordnungsgemäß behandelt werden, insbesondere wenn Sie nach dem Ausführen einer Ausnahme den ursprünglichen Termin erneut bearbeiten. Wenn Sie beispielsweise die dritte Instanz einen Tag vorwärts verschieben, was ist, wenn Sie zurückgehen und die Zeit des ursprünglichen Termins bearbeiten, eine andere am ursprünglichen Tag erneut einfügen und die verschobene verlassen? Die Verknüpfung des Verschobenen aufheben? Versuchen Sie, die verschobene entsprechend zu ändern?

Wenn Sie keine Ausnahmen machen, sollte natürlich jede der beiden Lösungen in Ordnung sein, und Sie wählen grundsätzlich aus einem Zeit / Raum-Kompromiss-Szenario.

Lasse V. Karlsen
quelle
36
Was ist, wenn Sie einen wiederkehrenden Termin ohne Enddatum haben? So billig der Platz auch ist, Sie haben keinen unendlichen Platz, also ist Lösung 2 dort ein Nichtstarter ...
Shaul Behr
13
Lösung Nr. 1 kann tatsächlich Ausnahmen behandeln. Zum Beispiel schlägt RFC5545 vor, dass sie gespeichert werden als: a) eine Liste ausgeschlossener Daten (wenn Sie ein Ereignis löschen); b) "materialisierte" Ereignisse mit Verweisen auf den Prototyp (wenn Sie ein Ereignis verschieben).
Andy Mikhaylenko
@ Andy, einige interessante Ergänzungen zu Massees Antwort. Ich werde es versuchen.
Jonathan Wilson
1
@Shaul: Ich denke nicht, dass es ein Nichtstarter ist. John Skeet, der auf SO ziemlich respektiert wird, schlägt vor, generierte Instanzen in seiner Antwort auf im Grunde die gleiche Frage zu speichern
Benutzer
1
@ Benutzer - bestätigt, danke. Es ist so seltsam - ich habe meinen Kommentar vor über 4 Jahren abgegeben und musste mich seitdem nicht mehr wirklich mit diesem Thema befassen. Erst gestern habe ich ein neues Modul entworfen, das wiederkehrende Termine beinhaltet, und ich habe mich gefragt, wie ich damit umgehen soll. Und dann - ich habe heute Morgen eine SO-Benachrichtigung über Ihren Kommentar erhalten. Ernsthaft gruselig! Aber danke! :-)
Shaul Behr
20

Ich habe mehrere kalenderbasierte Anwendungen entwickelt und eine Reihe wiederverwendbarer JavaScript-Kalenderkomponenten erstellt, die die Wiederholung unterstützen. Ich habe einen Überblick darüber geschrieben, wie man für eine Wiederholung entwirft jemanden hilfreich sein könnte. Während es einige Bits gibt, die spezifisch für die Bibliothek sind, die ich geschrieben habe, ist die überwiegende Mehrheit der angebotenen Ratschläge für jede Kalenderimplementierung allgemein.

Einige der wichtigsten Punkte:

  • Speichern Sie Wiederholungen mit dem iCal RRULE-Format - das ist ein Rad, das Sie wirklich nicht neu erfinden möchten
  • Speichern Sie KEINE einzelnen wiederkehrenden Ereignisse Instanzen als Zeilen in der Datenbank! Speichern Sie immer ein Wiederholungsmuster.
  • Es gibt viele Möglichkeiten, Ihr Ereignis- / Ausnahmeschema zu entwerfen, es wird jedoch ein grundlegendes Beispiel für einen Ausgangspunkt bereitgestellt
  • Alle Datums- / Zeitwerte sollten in UTC gespeichert und zur Anzeige in lokal konvertiert werden
  • Das für ein wiederkehrendes Ereignis gespeicherte Enddatum sollte immer das Enddatum des Wiederholungsbereichs sein (oder das "Maximaldatum" Ihrer Plattform, wenn es "für immer" wiederholt wird), und die Ereignisdauer sollte separat gespeichert werden. Dies soll sicherstellen, dass Ereignisse später vernünftig abgefragt werden.
  • Einige Diskussionen zum Generieren von Ereignisinstanzen und Strategien zum Bearbeiten von Wiederholungen sind enthalten

Es ist ein wirklich kompliziertes Thema mit vielen, vielen gültigen Ansätzen zur Implementierung. Ich werde sagen, dass ich die Wiederholung tatsächlich mehrmals erfolgreich implementiert habe, und ich würde mich davor hüten, Ratschläge zu diesem Thema von jedem zu erhalten, der dies nicht wirklich getan hat.

Brian Moeskau
quelle
Speichern Sie Wiederholungen möglicherweise als Ereignisse, wenn sie auftreten, damit Ihr Kalenderverlauf korrekt ist
Richard Haven
@ RichardHaven Das würde ich nie tun. Sie sollten Instanzen immer konsistent aus RRULE-Mustern generieren, in der Vergangenheit, Gegenwart oder Zukunft. Es würde keinen Grund geben, für historische Ereignisse etwas anderes zu tun. Ihre Logik sollte einfach eine RRULE anhand eines beliebigen Datumsbereichs auswerten und übereinstimmende Ereignisinstanzen zurückgeben.
Brian Moeskau
@BrianMoeskau schöne und hilfreiche Übersicht!
Przemek Nowak
@BrianMoeskau Aber würden dann in früheren Ansichten Ihres Kalenders keine ungenauen Informationen angezeigt, wenn jemand die RRULE bearbeitet, nachdem einige Vorkommnisse bereits aufgetreten sind? Oder würden Sie in diesem Fall die RRULE "verzweigen" und modifizierte Versionen der RRULE-Muster beibehalten, die genau die tatsächlichen Ereignisse der Vergangenheit darstellen?
Christian
1
@christian Wenn Sie eine Wiederholungsregel in den meisten Kalendern aktualisieren, werden Sie normalerweise wie "Alle Ereignisse bearbeiten oder nur dieses oder nur zukünftige" bearbeiten, damit der Benutzer das Verhalten auswählen kann. In den meisten Fällen bedeutet der Benutzer wahrscheinlich "künftig ändern", aber es liegt wiederum an Ihnen, zu entscheiden, wie Ihre Software funktioniert und welche Optionen Sie dem Benutzer geben.
Brian Moeskau
19

Möglicherweise möchten Sie sich die Implementierungen der iCalendar-Software oder den Standard selbst ( RFC 2445 RFC 5545 ) ansehen . Schnell in den Sinn kommen die Mozilla-Projekte http://www.mozilla.org/projects/calendar/ Eine schnelle Suche zeigt http://icalendar.rubyforge.org/ .

Abhängig davon, wie Sie die Ereignisse speichern, können andere Optionen in Betracht gezogen werden. Erstellen Sie Ihr eigenes Datenbankschema? Verwenden Sie etwas iCalendar-basiertes usw.?

Kris Kumler
quelle
Wenn Sie nur einen Link zu einem dieser Links bereitstellen könnten, wäre Ihr Beitrag perfekt
Jean
7
Es sieht so aus, als ob RFC2445 von RFC5545 ( tools.ietf.org/html/rfc5545 ) überholt wurde
Eric Freese
16

Ich arbeite mit Folgendem:

und ein Juwel in Bearbeitung, das formtastic um einen Eingabetyp erweitert: Wiederkehrend ( form.schedule :as => :recurring), der eine iCal-ähnliche Schnittstelle rendert und before_filterdie Ansicht IceCubeghetto- artig wieder in ein Objekt serialisiert .

Meine Idee ist es, es unglaublich einfach zu machen, einem Modell wiederkehrende Attribute hinzuzufügen und es einfach in der Ansicht zu verbinden. Alles in ein paar Zeilen.


Was gibt mir das? Indizierte, bearbeitbare, wiederkehrende Attribute.

eventsspeichert eine einzige Tag Instanz und in der Kalenderansicht verwendet wird / sagen Helfer task.scheduledas yaml'd speichert IceCubeObjekt, so dass Sie Anrufe wie tun können: task.schedule.next_suggestion.

Fazit: Ich verwende zwei Modelle, ein flaches für die Kalenderanzeige und ein Attribut für die Funktionalität.

Vee
quelle
Es würde mich interessieren, was Sie sich ausgedacht haben. Hast du irgendwo einen Git / Blog / Proof of Concept? Vielen Dank!
Montrealmike
Ich arbeite auch an etwas Ähnlichem. Würde gerne Ihre Implementierung sehen
Gedankenschlag
5
  1. Verfolgen Sie eine Wiederholungsregel (wahrscheinlich basierend auf iCalendar, per @ Kris K. ). Dies beinhaltet ein Muster und einen Bereich (jeden dritten Dienstag für 10 Vorkommen).
  2. Wenn Sie ein bestimmtes Vorkommen bearbeiten / löschen möchten, verfolgen Sie die Ausnahmedaten für die oben genannte Wiederholungsregel (Daten, an denen das Ereignis nicht wie in der Regel angegeben auftritt).
  3. Wenn Sie gelöscht haben, ist dies alles, was Sie benötigen. Wenn Sie bearbeitet haben, erstellen Sie ein anderes Ereignis und geben Sie ihm eine übergeordnete ID, die für das Hauptereignis festgelegt ist. Sie können wählen, ob alle Informationen des Hauptereignisses in diesen Datensatz aufgenommen werden sollen oder ob nur die Änderungen gespeichert und alles geerbt werden, was sich nicht ändert.

Beachten Sie, dass Sie überlegen müssen, wie Sie Ihre jetzt unendlich viele Informationen anzeigen können, wenn Sie Wiederholungsregeln zulassen, die nicht enden.

Hoffentlich hilft das!

bdukes
quelle
4

Ich würde empfehlen, die Leistung der Datumsbibliothek und die Semantik des Bereichsmoduls von Ruby zu verwenden. Ein wiederkehrendes Ereignis ist wirklich eine Zeit, ein Zeitraum (ein Start und ein Ende) und normalerweise ein einzelner Wochentag. Mit Datum & Bereich können Sie jede Frage beantworten:

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

Produziert alle Tage der Veranstaltung, einschließlich des Schaltjahres!

# =>"[\"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
quelle
2
Das ist nicht sehr flexibel. Ein wiederkehrendes Ereignismodell erfordert häufig die Angabe des Wiederholungszeitraums (stündlich, wöchentlich, vierzehntägig usw.). Außerdem wird die Wiederholung möglicherweise nicht durch eine Gesamtzahl qualifiziert, sondern durch ein Enddatum für das letzte Auftreten
Bo Jeanes
"Ein wiederkehrendes Ereignis ist [..] normalerweise ein einzelner Wochentag", dies ist nur ein begrenzter Anwendungsfall und behandelt nicht viele andere wie "Der 5. Tag eines jeden Monats" usw.
theraven
3

Aus diesen Antworten habe ich eine Art Lösung herausgesucht. Die Idee des Link-Konzepts gefällt mir sehr gut. Wiederkehrende Ereignisse können eine verknüpfte Liste sein, wobei der Schwanz seine Wiederholungsregel kennt. Das Ändern eines Ereignisses wäre dann einfach, da die Verknüpfungen bestehen bleiben und das Löschen eines Ereignisses ebenfalls einfach ist. Sie müssen lediglich die Verknüpfung eines Ereignisses aufheben, es löschen und das Ereignis davor und danach erneut verknüpfen. Sie müssen immer noch wiederkehrende Ereignisse abfragen, wenn jemand einen neuen Zeitraum betrachtet, der noch nie zuvor im Kalender angezeigt wurde. Andernfalls ist dies ziemlich sauber.

Clinton N. Dreisbach
quelle
2

Sie können die Ereignisse als sich wiederholend speichern. Wenn eine bestimmte Instanz bearbeitet wurde, erstellen Sie ein neues Ereignis mit derselben Ereignis-ID. Suchen Sie dann beim Nachschlagen des Ereignisses nach allen Ereignissen mit derselben Ereignis-ID, um alle Informationen zu erhalten. Ich bin mir nicht sicher, ob Sie Ihre eigene Ereignisbibliothek gerollt haben oder ob Sie eine vorhandene verwenden, sodass dies möglicherweise nicht möglich ist.

Vincent McNabb
quelle
Ich habe diese Lösung einmal verwendet. Ich mag das Prinzip, eine modifizierte Instanz als neues einmaliges Ereignis zu speichern, das weiß, wer seine Mutter ist. Auf diese Weise können Sie alle Felder leer lassen, mit Ausnahme derjenigen, die für das untergeordnete Ereignis unterschiedlich sind. Beachten Sie, dass Sie ein zusätzliches Feld benötigen, um anzugeben, welches Kind dieser Mutter Sie bearbeiten.
Wytze
1

In Javascript:

Umgang mit wiederkehrenden Zeitplänen: http://bunkat.github.io/later/

Behandlung komplexer Ereignisse und Abhängigkeiten zwischen diesen Zeitplänen: http://bunkat.github.io/schedule/

Grundsätzlich erstellen Sie die Regeln und fordern die Bibliothek auf, die nächsten N wiederkehrenden Ereignisse zu berechnen (Angabe eines Datumsbereichs oder nicht). Die Regeln können analysiert / serialisiert werden, um sie in Ihrem Modell zu speichern.

Wenn Sie ein wiederkehrendes Ereignis haben und nur eine Wiederholung ändern möchten, können Sie die Ausnahme () verwenden. Funktion exception einen bestimmten Tag schließen und dann ein neues geändertes Ereignis für diesen Eintrag hinzufügen.

Die Bibliothek unterstützt sehr komplexe Muster, Zeitzonen und sogar Croning-Ereignisse.

Flavien Volken
quelle
0

Speichern Sie die Ereignisse als sich wiederholend und zeigen Sie sie dynamisch an. Ermöglichen Sie jedoch, dass das wiederkehrende Ereignis eine Liste bestimmter Ereignisse enthält, die die Standardinformationen an einem bestimmten Tag überschreiben können.

Wenn Sie das wiederkehrende Ereignis abfragen, kann es nach einer bestimmten Überschreibung für diesen Tag suchen.

Wenn ein Benutzer Änderungen vornimmt, können Sie fragen, ob er für alle Instanzen (Standarddetails) oder nur für diesen Tag (ein neues spezifisches Ereignis erstellen und zur Liste hinzufügen) aktualisieren möchte.

Wenn ein Benutzer alle Wiederholungen dieses Ereignisses löschen möchte, haben Sie auch die Liste der Details zur Hand und können diese leicht entfernen.

Der einzige problematische Fall wäre, wenn der Benutzer dieses Ereignis und alle zukünftigen Ereignisse aktualisieren möchte. In diesem Fall müssen Sie das wiederkehrende Ereignis in zwei Teile teilen. An dieser Stelle möchten Sie möglicherweise wiederkehrende Ereignisse in irgendeiner Weise verknüpfen, damit Sie sie alle löschen können.

Andrew Johnson
quelle
0

Für .NET-Programmierer, die bereit sind, Lizenzgebühren zu zahlen, ist Aspose.Network möglicherweise hilfreich. Es enthält eine iCalendar-kompatible Bibliothek für wiederkehrende Termine.

Shaul Behr
quelle
0

Sie speichern die Ereignisse direkt im iCalendar-Format, was eine unbegrenzte Wiederholung, Zeitzonenlokalisierung usw. ermöglicht.

Sie können diese auf einem CalDAV-Server speichern. Wenn Sie dann die Ereignisse anzeigen möchten, können Sie mithilfe der Option des in CalDAV definierten Berichts den Server auffordern, die Erweiterung der wiederkehrenden Ereignisse über den angezeigten Zeitraum durchzuführen.

Sie können sie auch selbst in einer Datenbank speichern und eine Art iCalendar-Analysebibliothek verwenden, um die Erweiterung durchzuführen, ohne dass PUT / GET / REPORT für die Kommunikation mit einem CalDAV-Backend-Server erforderlich ist. Dies ist wahrscheinlich mehr Arbeit - ich bin sicher, dass CalDAV-Server die Komplexität irgendwo verbergen.

Wenn die Ereignisse im iCalendar-Format vorliegen, wird dies auf lange Sicht wahrscheinlich einfacher, da die Benutzer immer möchten, dass sie exportiert werden, um ohnehin andere Software einzufügen.

Karora
quelle
0

Ich habe diese Funktion einfach implementiert! Die Logik ist wie folgt: Zuerst benötigen Sie zwei Tabellen. RuleTable speichert allgemeine oder recycelt väterliche Ereignisse. ItemTable speichert Zyklusereignisse. Wenn Sie beispielsweise ein zyklisches Ereignis erstellen, wird die Startzeit für den 6. November 2015, die Endzeit für den 6. Dezember (oder für immer) für eine Woche festgelegt. Sie fügen Daten in eine Regeltabelle ein. Die Felder lauten wie folgt:

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.

Jetzt möchten Sie Daten vom 20. November bis 20. Dezember abfragen. Sie können eine Funktion RecurringEventBE (langer Start, langes Ende) schreiben, basierend auf der Start- und Endzeit WeekLy. Sie können die gewünschte Sammlung berechnen: <ZyklusA11.20, ZyklusA 11.27, ZyklusA 12.4 ......>. Zusätzlich zum 6. November und dem Rest nannte ich ihn ein virtuelles Ereignis. Wenn der Benutzer den Namen eines virtuellen Ereignisses nach (z. B. Zyklus A11.27) ändert, fügen Sie Daten in eine ItemTable ein. Die Felder lauten wie folgt:

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

In der Funktion RecurringEventBE (langer Start, langes Ende) verwenden Sie diese Daten für ein virtuelles Ereignis (Zyklus B11.27). Entschuldigung für mein Englisch, ich habe es versucht.

Dies ist mein 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
quelle
-5

Was ist, wenn Sie einen wiederkehrenden Termin ohne Enddatum haben? So billig der Platz auch ist, Sie haben keinen unendlichen Platz, also ist Lösung 2 dort ein Nichtstarter ...

Darf ich vorschlagen, dass "kein Enddatum" auf ein Enddatum am Ende des Jahrhunderts aufgelöst werden kann. Selbst für eine tägliche Veranstaltung bleibt der Platz günstig.

Poumtatalia
quelle
7
Wie schnell vergessen wir die Lektionen von y2k ... :)
Ian Mercer
10
Nehmen wir an, wir haben 1000 Benutzer mit jeweils ein paar täglichen Ereignissen. 3 Ereignisse × 1000 Benutzer × 365 Tage × (2100-2011 = 89 Jahre) = 97,5 Millionen Datensätze. Anstelle von 3000 "Plänen".
Ähm