Jaki jest najlepszy sposób modelowania powtarzających się wydarzeń w aplikacji kalendarza?

224

Buduję aplikację kalendarza grupowego, która musi obsługiwać powtarzające się wydarzenia, ale wszystkie rozwiązania, które wymyśliłem, aby obsłużyć te wydarzenia, wydają się być włamaniem. Mogę ograniczyć, jak daleko można spojrzeć, a następnie wygenerować wszystkie zdarzenia naraz. Lub mogę przechowywać wydarzenia jako powtarzające się i wyświetlać je dynamicznie, gdy spojrzy się na kalendarz, ale będę musiał przekonwertować je na normalne wydarzenie, jeśli ktoś chce zmienić szczegóły dotyczące konkretnego wystąpienia wydarzenia.

Jestem pewien, że jest na to lepszy sposób, ale jeszcze go nie znalazłem. Jaki jest najlepszy sposób modelowania powtarzających się zdarzeń, w którym można zmienić szczegóły lub usunąć poszczególne wystąpienia zdarzeń?

(Używam Ruby, ale nie pozwól, by to ograniczało twoją odpowiedź. Jeśli jest jednak biblioteka specyficzna dla Ruby lub coś takiego, to dobrze wiedzieć.)

Clinton N. Dreisbach
źródło

Odpowiedzi:

93

Użyłbym koncepcji „linku” do wszystkich przyszłych wydarzeń cyklicznych. Są one dynamicznie wyświetlane w kalendarzu i łączą się z jednym obiektem odniesienia. Po zdarzeniu łącze zostaje zerwane, a zdarzenie staje się samodzielną instancją. Jeśli spróbujesz edytować powtarzające się zdarzenie, wyświetl monit o zmianę wszystkich przyszłych elementów (tj. Zmień pojedyncze odniesienie połączone) lub zmień tylko to wystąpienie (w takim przypadku przekonwertuj je na samodzielne wystąpienie, a następnie wprowadź zmiany). Ta druga sprawa jest nieco problematyczna, ponieważ musisz śledzić na swojej cyklicznej liście wszystkie przyszłe zdarzenia, które zostały przekonwertowane na pojedyncze wystąpienie. Ale jest to całkowicie wykonalne.

Zasadniczo mają 2 klasy zdarzeń - pojedyncze wystąpienia i zdarzenia cykliczne.

użytkownik16068
źródło
Naprawdę podoba Ci się pomysł łączenia i przekształcania zdarzeń w samodzielne po ich zakończeniu. Dwa pytania: - Po co w ogóle konwertować je na samodzielne stałe wystąpienia? Dlaczego nie pozostawić ich całkowicie dynamicznych? - Czy możesz udostępnić referencje do proponowanej koncepcji linków! Z góry dziękuję!
rtindru
@rtindru Przypadek użycia znaleziony przeze mnie do konwersji zdarzeń na samodzielny to sytuacja, w której musisz użyć modelu zdarzeń z innymi modelami w bazie danych. Na przykład w celu sprawdzenia frekwencji na wydarzeniu będziesz chciał powiązać użytkowników z prawdziwym wydarzeniem, które się wydarzyło (lub nastąpi).
Clinton Yeboah
60

Martin Fowler - Cykliczne wydarzenia dla kalendarzy zawiera kilka ciekawych spostrzeżeń i wzorów.

Klejnot Runt wdraża ten wzór.

Daniel Maurić
źródło
Przydałoby się kilka lepszych przykładów kodu. Czy ktoś zna projekt, który to wdrożył?
Tadeu Maia,
33

Może występować wiele problemów z powtarzającymi się wydarzeniami, pozwól mi podkreślić kilka, które znam.

Rozwiązanie 1 - brak wystąpień

Przechowuj oryginalne spotkanie + dane o cyklach, nie przechowuj wszystkich wystąpień.

Problemy:

  • Będziesz musiał obliczyć wszystkie wystąpienia w oknie daty, gdy będą potrzebne, kosztowne
  • Nie można obsłużyć wyjątków (tzn. Usunąć jedno z wystąpień lub przenieść je, a raczej nie można tego zrobić za pomocą tego rozwiązania)

Rozwiązanie 2 - przechowuj instancje

Przechowuj wszystko od 1, ale także wszystkie wystąpienia, powiązane z pierwotnym terminem.

Problemy:

  • Zajmuje dużo miejsca (ale miejsce jest tanie, więc niewielkie)
  • Wyjątki muszą być obsługiwane z wdziękiem, szczególnie jeśli cofniesz się i edytujesz oryginalne spotkanie po dokonaniu wyjątku. Na przykład, jeśli przeniesiesz trzecią instancję o jeden dzień do przodu, co jeśli cofniesz się i edytujesz godzinę pierwotnego spotkania, włóż ponownie inną w pierwotnym dniu i zostaw przeniesioną? Odłączyć przeniesiony? Czy próbujesz odpowiednio zmienić przeniesiony?

Oczywiście, jeśli nie zamierzasz robić wyjątków, każde z tych rozwiązań powinno być w porządku i zasadniczo wybierasz ze scenariusza kompromisu czas / przestrzeń.

Lasse V. Karlsen
źródło
36
Co zrobić, jeśli masz spotkanie cykliczne bez daty końcowej? Tak tanio jak kosmos, nie masz nieskończonej przestrzeni, więc Solution 2 nie uruchamia tam ...
Shaul Behr
13
Rozwiązanie nr 1 faktycznie obsługuje wyjątki. Na przykład RFC5545 sugeruje, że są one przechowywane jako: a) lista wykluczonych dat (po usunięciu wystąpienia); b) „zmaterializowane” wystąpienia z odniesieniami do prototypu (po przeniesieniu wystąpienia).
Andy Mikhaylenko
@ Andy, kilka interesujących dodatków do odpowiedzi Lasse. Spróbuję tych.
Jonathan Wilson
1
@Shaul: Nie wydaje mi się, żeby to nie był starter. John Skeet, który jest dość szanowany w SO, sugeruje przechowywanie wygenerowanych instancji w swojej odpowiedzi na zasadniczo to samo pytanie: stackoverflow.com/a/10151804/155268
Użytkownik
1
@ Użytkownik - potwierdzono, dziękuję. To takie dziwne - skomentowałem to ponad 4 lata temu i od tamtej pory nie musiałem naprawdę zajmować się tym problemem. Wczoraj zacząłem projektować nowy moduł, który obejmuje powtarzające się spotkania i zastanawiałem się, jak sobie z nimi poradzić. A potem - dostałem powiadomienie SO o twoim komentarzu dziś rano. Poważnie straszne! Ale dziękuję! :-)
Shaul Behr
20

Opracowałem wiele aplikacji opartych na kalendarzach, a także jestem autorem zestawu składników kalendarza JavaScript wielokrotnego użytku, które obsługują powtarzanie. Napisałem przegląd tego, jak zaprojektować wznowienie, które może być komuś pomocne. Chociaż jest kilka bitów, które są specyficzne dla biblioteki, którą napisałem, zdecydowana większość udzielonych porad dotyczy wszystkich implementacji kalendarza.

Niektóre kluczowe punkty:

  • Przechowuj rekurencje w formacie iCal RRULE - to jedno koło, którego tak naprawdę nie chcesz wymyślać na nowo
  • NIE przechowuj poszczególnych instancji zdarzeń cyklicznych jako wierszy w bazie danych! Zawsze przechowuj wzorzec nawrotu.
  • Istnieje wiele sposobów zaprojektowania schematu zdarzenia / wyjątku, ale podano podstawowy przykładowy punkt wyjścia
  • Wszystkie wartości daty / godziny powinny być przechowywane w UTC i konwertowane na lokalne w celu wyświetlenia
  • Data końcowa przechowywana dla zdarzenia cyklicznego powinna zawsze być datą końcową zakresu cyklicznego (lub „maksymalną datą” platformy, jeśli cykliczna „wieczność”), a czas trwania zdarzenia należy przechowywać osobno. Ma to na celu zapewnienie rozsądnego sposobu późniejszego zapytania o zdarzenia.
  • Uwzględniono niektóre dyskusje dotyczące generowania instancji zdarzeń i strategii edycji cyklicznej

To naprawdę skomplikowany temat z wieloma, wieloma ważnymi podejściami do jego wdrożenia. Powiem, że kilkakrotnie z powodzeniem wdrożyłem wznowienie i nieufnie będę udzielał porad na ten temat każdemu, kto tego nie zrobił.

Brian Moeskau
źródło
Być może przechowuj powtarzające się zdarzenia, gdy się one zdarzają, aby historia Twojego kalendarza była dokładna
Richard Haven
@RichardHaven Nigdy bym tego nie zrobił. Zawsze powinieneś generować instancje na podstawie wzorców RRULE konsekwentnie, przeszłych, obecnych lub przyszłych. Nie byłoby powodu robić czegoś innego w przypadku wydarzeń historycznych. Twoja logika powinna po prostu oceniać RRULE na podstawie dowolnego dowolnego zakresu dat i zwracać pasujące wystąpienia zdarzeń.
Brian Moeskau,
@BrianMoeskau ładny i pomocny przegląd!
Przemek Nowak
@BrianMoeskau Ale czy wcześniejsze wyświetlenia Twojego kalendarza nie pokazują niedokładnych informacji, gdy ktoś edytuje RRULE po wystąpieniu niektórych zdarzeń? A może w takim przypadku „rozgałęziłbyś” RRULE i utrzymywałbyś zmodyfikowane wersje wzorców RRULE reprezentujące dokładnie rzeczywiste zdarzenia z przeszłości?
chrześcijanin
1
@christian Gdy aktualizujesz regułę powtarzania w większości kalendarzy, zazwyczaj wyświetlają się monity „edytuj wszystkie zdarzenia, tylko to jedno lub tylko przyszłe”, pozwalając użytkownikowi wybrać zachowanie. W większości przypadków użytkownik prawdopodobnie oznacza „zmień to w przyszłości”, ale znowu to Ty decydujesz, jak działa twoje oprogramowanie i jakie opcje dajesz użytkownikowi.
Brian Moeskau,
19

Możesz przyjrzeć się implementacjom oprogramowania iCalendar lub samemu standardowi ( RFC 2445 RFC 5545 ). Do głowy przychodzą szybko projekty Mozilli http://www.mozilla.org/projects/calendar/ Szybkie wyszukiwanie ujawnia również http://icalendar.rubyforge.org/ .

Można rozważyć inne opcje w zależności od tego, jak zamierzasz przechowywać wydarzenia. Czy budujesz własny schemat bazy danych? Używasz czegoś opartego na iCalendar itp.?

Kris Kumler
źródło
gdybyś tylko mógł podać link do jednego z tych postów, Twój post byłby idealny
Jean
7
Wygląda na to, że RFC2445 stało się przestarzałe przez RFC5545 ( tools.ietf.org/html/rfc5545 )
Eric Freese
16

Pracuję z następującymi:

oraz klejnot w toku, który rozszerza formtastic o typ wejściowy: recurring ( form.schedule :as => :recurring), który renderuje interfejs podobny do iCal i before_filterponownie serializuje widok w IceCubeobiekt, getto.

Moim pomysłem jest ułatwienie dodawania powtarzających się atrybutów do modelu i łatwe łączenie go w widoku. Wszystko w kilku wierszach.


Co mi to daje? Atrybuty indeksowane, edytowalne, cykliczne.

eventssklepy pojedyncza instancja dni i jest wykorzystywane w widoku kalendarza / pomocnik powiedzieć task.schedulePrzechowuje yaml'd IceCubeobiektu, dzięki czemu można zrobić połączeń, takich jak: task.schedule.next_suggestion.

Podsumowanie: używam dwóch modeli, jednego płaskiego, do wyświetlania kalendarza i jednego atrybutu dla funkcjonalności.

Vee
źródło
Byłbym zainteresowany zobaczeniem, co wymyśliłeś. Czy masz gdzieś git / blog / proof of concept? Dzięki!
montrealmike,
Pracuję również nad czymś podobnym. Chciałbym zobaczyć Twoje wdrożenie
przemyślane
5
  1. Śledź regułę powtarzalności (prawdopodobnie opartą na iCalendar, według @ Kris K. ). Obejmuje to wzorzec i zakres (co trzeci wtorek, na 10 wystąpień).
  2. Gdy chcesz edytować / usunąć określone wystąpienie, śledź daty wyjątków dla powyższej reguły cykliczności (daty, w których zdarzenie nie występuje zgodnie z regułą).
  3. Jeśli usunąłeś, to wszystko, czego potrzebujesz, jeśli edytowałeś, utwórz kolejne wydarzenie i nadaj mu identyfikator nadrzędny ustawiony na wydarzenie główne. Możesz wybrać, czy dołączyć wszystkie informacje o wydarzeniu głównym do tego rekordu, czy tylko zawiera on zmiany i dziedziczy wszystko, co się nie zmienia.

Pamiętaj, że jeśli zezwolisz na reguły powtarzania, które się nie kończą, musisz pomyśleć o tym, jak wyświetlić teraz nieskończoną ilość informacji.

Mam nadzieję, że to pomaga!

bdukes
źródło
4

Poleciłbym użyć mocy biblioteki dat i semantyki modułu zakresu ruby. Cykliczne wydarzenie to tak naprawdę czas, zakres dat (początek i koniec) i zazwyczaj jeden dzień tygodnia. Korzystając z daty i zakresu, możesz odpowiedzieć na każde pytanie:

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

Produkuje wszystkie dni wydarzenia, w tym rok przestępny!

# =>"[\"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
źródło
2
To nie jest bardzo elastyczne. Model zdarzenia cyklicznego często wymagałby określenia okresu powtórzeń (co godzinę, co tydzień, co dwa tygodnie itp.). Dodatkowo, wznowienie może nie być zakwalifikowane przez całkowitą liczbę, a raczej datę końcową ostatniego wystąpienia
Bo Jeanes
„Wydarzeniem cyklicznym jest [...] zwykle jeden dzień tygodnia”, jest to tylko jeden przypadek ograniczonego użycia i nie obsługuje wielu innych, takich jak „Piąty dzień każdego miesiąca” itp.
theraven
3

Z tych odpowiedzi wyszukałem rozwiązanie. Naprawdę podoba mi się pomysł koncepcji linku. Powtarzające się zdarzenia mogą być listą połączoną, a ogon zna swoją zasadę powtarzania. Zmiana jednego wydarzenia byłaby wówczas łatwa, ponieważ linki pozostają na miejscu, a usuwanie wydarzenia jest również łatwe - wystarczy odłączyć wydarzenie, usunąć je i ponownie połączyć wydarzenie przed i po nim. Nadal musisz sprawdzać powtarzające się zdarzenia za każdym razem, gdy ktoś patrzy na nowy okres, którego nigdy wcześniej nie oglądano w kalendarzu, ale poza tym jest to całkiem czyste.

Clinton N. Dreisbach
źródło
2

Możesz przechowywać zdarzenia jako powtarzające się, a jeśli dana instancja została edytowana, utwórz nowe zdarzenie o tym samym identyfikatorze. Następnie, patrząc na wydarzenie, wyszukaj wszystkie zdarzenia o tym samym identyfikatorze zdarzenia, aby uzyskać wszystkie informacje. Nie jestem pewien, czy utworzyłeś własną bibliotekę wydarzeń, czy używasz już istniejącej, więc może to nie być możliwe.

Vincent McNabb
źródło
Użyłem tego rozwiązania raz. Podoba mi się zasada przechowywania zmodyfikowanej instancji jako nowego jednorazowego wydarzenia, które wie, kim jest jej mama. W ten sposób możesz pozostawić wszystkie pola puste, oprócz tych, które są inne dla zdarzenia potomnego. Pamiętaj, że musisz mieć dodatkowe pole określające, które dziecko tej matki edytujesz.
Wytze
1

W javascript:

Obsługa harmonogramów cyklicznych: http://bunkat.github.io/later/

Obsługa złożonych zdarzeń i zależności między tymi harmonogramami: http://bunkat.github.io/schedule/

Zasadniczo, tworzysz reguły, a następnie prosisz bibliotekę, aby obliczyła N kolejnych cyklicznych zdarzeń (określając zakres dat lub nie). Reguły można analizować / serializować w celu zapisania ich w modelu.

Jeśli masz wydarzenie cykliczne i chcesz zmodyfikować tylko jedno powtórzenie, możesz użyć funkcji wyjątku (), aby zamknąć dany dzień, a następnie dodać nowe zmodyfikowane zdarzenie dla tego wpisu.

Biblioteka obsługuje bardzo skomplikowane wzorce, strefy czasowe, a nawet croning.

Flavien Volken
źródło
0

Przechowuj zdarzenia jako powtarzające się i wyświetlaj je dynamicznie, jednak pozwól, aby cykliczne zdarzenie zawierało listę konkretnych zdarzeń, które mogą zastąpić domyślne informacje w danym dniu.

Po zapytaniu o powtarzające się zdarzenie może sprawdzić, czy nie ma określonego zastąpienia na ten dzień.

Jeśli użytkownik wprowadza zmiany, możesz zapytać, czy chce dokonać aktualizacji dla wszystkich instancji (szczegóły domyślne), czy tylko tego dnia (zrób nowe konkretne zdarzenie i dodaj je do listy).

Jeśli użytkownik poprosi o usunięcie wszystkich powtórzeń tego zdarzenia, masz również listę szczegółów do pod ręką i możesz je łatwo usunąć.

Jedynym problematycznym przypadkiem jest sytuacja, gdy użytkownik chce zaktualizować to zdarzenie i wszystkie przyszłe zdarzenia. W takim przypadku musisz podzielić wydarzenie cykliczne na dwa. W tym momencie możesz rozważyć powiązanie powtarzających się zdarzeń, aby móc je wszystkie usunąć.

Andrew Johnson
źródło
0

Dla programistów .NET, którzy są gotowi uiścić pewne opłaty licencyjne, Aspose.Network może okazać się przydatny ... zawiera bibliotekę kompatybilną z iCalendar do cyklicznych spotkań.

Shaul Behr
źródło
0

Zdarzenia są zapisywane bezpośrednio w formacie iCalendar, co pozwala na powtarzalne powtarzanie, lokalizację stref czasowych i tak dalej.

Można je zapisać na serwerze CalDAV, a następnie, aby wyświetlić zdarzenia, można skorzystać z opcji raportu zdefiniowanej w CalDAV, aby poprosić serwer o rozwinięcie cyklicznych zdarzeń w całym oglądanym okresie.

Lub możesz sam przechowywać je w bazie danych i użyć jakiegoś rodzaju biblioteki parsowania iCalendar, aby wykonać rozbudowę, bez potrzeby używania PUT / GET / REPORT do komunikacji z serwerem CalDAV zaplecza. To chyba więcej pracy - jestem pewien, że serwery CalDAV gdzieś ukrywają złożoność.

Posiadanie wydarzeń w formacie iCalendar prawdopodobnie na dłuższą metę uprości sprawę, ponieważ ludzie zawsze będą chcieli eksportować je w celu zainstalowania innego oprogramowania.

karora
źródło
0

Właśnie zaimplementowałem tę funkcję! Logika jest następująca: najpierw potrzebujesz dwóch tabel. RuleTable przechowuj ogólne lub przetwarzaj zdarzenia ojcowskie. ItemTable to przechowywane zdarzenia cykliczne. Na przykład, gdy tworzysz wydarzenie cykliczne, czas rozpoczęcia 6 listopada 2015 r., Czas zakończenia 6 grudnia (lub na zawsze), cykl na tydzień. Wstawiasz dane do tabeli reguł, pola są następujące:

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.

Teraz chcesz wysłać zapytanie do danych od 20 listopada do 20 grudnia. Możesz napisać funkcję RecurringEventBE (długi start, długi koniec), na podstawie czasu rozpoczęcia i zakończenia, WeekLy, możesz obliczyć żądaną kolekcję, <cykl A11.20, cykl A 11,27, cykl A 12,4 ......>. Oprócz 6 listopada, a resztę nazwałem go wirtualnym wydarzeniem. Gdy użytkownik zmienia nazwę zdarzenia wirtualnego po (na przykład cykl A1.11.2), wstawiasz dane do tabeli pozycji. Pola są następujące:

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

W funkcji RecurringEventBE (długi początek, długi koniec) używasz tych danych obejmujących zdarzenie wirtualne (cykl B11.27) przepraszam za mój angielski, próbowałem.

To jest mój 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
źródło
-5

Co zrobić, jeśli masz spotkanie cykliczne bez daty końcowej? Choć kosmiczna jest tania, nie ma nieskończonej przestrzeni, więc Solution 2 nie uruchamia się tam ...

Chciałbym zasugerować, że „brak daty końcowej” można rozwiązać na datę końcową pod koniec wieku. Nawet na codzienne wydarzenie ilość miejsca pozostaje tania.

poumtatalia
źródło
7
Jak szybko zapomnimy lekcji y2k ... :)
Ian Mercer
10
Załóżmy, że mamy 1000 użytkowników, każdy z kilkoma codziennymi wydarzeniami. 3 zdarzenia × 1000 użytkowników × 365 dni × (2100-2011 = 89 lat) = 97,5 miliona rekordów. Zamiast 3000 „planów”. Um ...
Andy Mikhaylenko,