Який найкращий спосіб моделювати повторювані події в програмі календаря?


224

Я будую додаток для групового календаря, який повинен підтримувати повторювані події, але всі рішення, які я придумав, щоб обробити ці події, здаються злому. Я можу обмежити, як далеко вперед можна подивитися, а потім генерувати всі події одразу. Або я можу зберігати події як повторювані та динамічно відображати їх, коли хтось дивиться вперед у календарі, але мені доведеться перетворити їх у звичайну подію, якщо хтось хоче змінити деталі на конкретному екземплярі події.

Я впевнений, що є кращий спосіб зробити це, але я його ще не знайшов. Який найкращий спосіб моделювати повторювані події, де ви можете змінити деталі або видалити окремі екземпляри подій?

(Я використовую Ruby, але, будь ласка, не дозволяйте це обмежувати вашу відповідь. Якщо є бібліотека для Ruby або щось подібне, це добре знати.)

Відповіді:


93

Я б використовував концепцію "посилання" для всіх майбутніх повторюваних подій. Вони динамічно відображаються в календарі і посилаються назад на один довідковий об’єкт. Коли події відбулися, посилання розривається, і подія стає окремим екземпляром. Якщо ви намагаєтесь відредагувати повторювану подію, тоді запропонуйте змінити всі майбутні елементи (тобто змінити одну зв'язану посилання) або змінити лише цей екземпляр (у такому випадку конвертуйте це в окремий екземпляр і потім внесіть зміни). Останнє обгрунтування є мало проблематичним, оскільки вам потрібно відстежувати у своєму повторюваному списку всіх майбутніх подій, які були перетворені на один екземпляр. Але це цілком можливо.

Отже, по суті, є 2 класи подій - поодинокі екземпляри та повторювані події.


Дуже подобається вашій ідеї пов’язати та перетворити події на самостійні після їх проходження. Два питання: - Навіщо взагалі перетворювати їх у самостійні фіксовані екземпляри? Чому б не залишити їх повністю динамічними? - Чи можете ви поділитися посиланням на запропоновану концепцію посилання! Спасибі заздалегідь!
rtindru

@rtindru випадок використання, який я знайшов для перетворення подій у самостійний, - це коли вам доведеться використовувати модель подій з іншими моделями у вашій базі даних. Наприклад, для перевірки відвідуваності події, ви хочете пов’язати користувачів із реальною подією, яка сталася (або відбудеться).
Клінтон Йебоа


33

З повторюваними подіями може виникнути багато проблем, дозвольте виділити декілька, про які я знаю.

Рішення 1 - жодних випадків

Зберігайте оригінальні зустрічі + дані про повторення, не зберігайте всі екземпляри.

Проблеми:

  • Вам доведеться обчислити всі екземпляри у вікні дати, коли вони вам потрібні, дорого
  • Неможливо обробити винятки (тобто ви видалите один із екземплярів або перемістите його, а точніше, ви не можете зробити це за допомогою цього рішення)

Рішення 2 - зберігати екземпляри

Зберігайте все, починаючи з 1, а також усі екземпляри, пов’язані з початковою зустріччю.

Проблеми:

  • Займає багато місця (але простір дешевий, такий незначний)
  • Винятки повинні бути оброблені витончено, особливо якщо ви повернетесь та відредагували первісну зустріч після винятку. Наприклад, якщо ви рухаєтесь третьою інстанцією на один день вперед, що робити, якщо ви повернетесь назад і відредагуєте час первинної зустрічі, повторно вставити інший у вихідний день і залишити перенесений? Від’єднати переміщений? Спробуйте змінити переміщений?

Звичайно, якщо ви не збираєтесь робити винятки, то будь-яке рішення повинно бути нормальним, і ви в основному вибираєте сценарій торгування часом / простором.


36
Що робити, якщо у вас повторювана зустріч без дати закінчення? Настільки дешевий, як і простір, у вас немає нескінченного простору, тому рішення 2 - це нестартер там ...
Шаул Бехр

13
Рішення №1 насправді може обробляти винятки. Наприклад, RFC5545 припускає, що вони зберігаються у вигляді: а) списку виключених дат (коли ви видаляєте подію); б) "матеріалізовані" події з посиланнями на прототип (коли ви переміщуєте подію).
Енді Михайленко

@Andy, кілька цікавих доповнень до відповіді Лассе. Постараюсь спробувати.
Джонатан Вілсон

1
@Shaul: Я не думаю, що це нестандарт. Джон Скіт, який досить добре поважається на SO, пропонує у своїй відповіді зберігати генеровані екземпляри на те саме питання: stackoverflow.com/a/10151804/155268
Користувач

1
@User - зізнався, дякую. Це так дивно - я зробив свій коментар понад 4 роки тому, і з тих пір мені не потрібно було займатися цим питанням. Лише вчора я почав розробляти новий модуль, який передбачає повторювані зустрічі, і мені було цікаво, як з ними впоратися. А потім - я отримав повідомлення про ваш коментар сьогодні вранці. Серйозно моторошно! Але дякую! :-)
Шауль Бехр

20

Я розробив кілька додатків на основі календаря, а також створив набір компонентів календаря JavaScript для багаторазового використання, які підтримують повторення. Я написав огляд того, як розробити рецидиви, які можуть комусь корисні. Хоча є декілька бітів, характерних для бібліотеки, про яку я писав, переважна більшість запропонованих порад є загальною для будь-якої реалізації календаря.

Деякі ключові моменти:

  • Зберігайте повторення у форматі iCal RRULE - це одне колесо, яке ви дійсно не бажаєте винаходити
  • НЕ зберігайте окремі екземпляри, що повторюються, як рядки у вашій базі даних! Завжди зберігайте шаблон повторення.
  • Існує багато способів розробити схему подій / винятків, але наведено основний приклад початкової точки
  • Усі значення дати / часу слід зберігати в UTC та перетворювати на місцеві для відображення
  • Кінцевою датою, що зберігається для повторюваної події, завжди має бути дата закінчення діапазону повторень (або "максимальна дата" вашої платформи, якщо повторюється "назавжди"), а тривалість події повинна зберігатися окремо. Це забезпечить розумний спосіб запиту подій пізніше.
  • Додано певну дискусію щодо створення примірників подій та стратегій редагування повторень

Це дійсно складна тема з багатьма, багатьма вагомими підходами до її реалізації. Я скажу, що фактично я кілька разів успішно впроваджував рецидиви, і я б з обережністю брав поради з цього приводу у всіх, хто насправді цього не робив.


Можливо, зберігайте рецидиви як події, коли вони трапляються, щоб історія вашого календаря була точною
Річард Хейвен

@RichardHaven Я б ніколи цього не робив. Ви завжди повинні генерувати екземпляри з шаблонів RRULE послідовно, минуле, теперішнє або майбутнє. Не було б підстав робити щось інше для історичних подій. Ваша логіка повинна просто оцінити RRULE по відношенню до будь-якого довільного діапазону дат та повернути збігаючі екземпляри подій.
Брайан Москау

@BrianMoeskau приємний і корисний огляд!
Przemek Nowak

@BrianMoeskau Але чи не минулі перегляди вашого календаря не відображали неточну інформацію, коли хтось редагує RRULE після того, як деякі випадки вже відбулися? Чи, можливо, у цьому випадку ви б "розгалужували" RRULE і зберігали модифіковані версії шаблонів RRULE, що представляють саме фактичні минулі події?
християнський

1
@christian Коли ви оновлюєте правило повторення у більшості календарів, вони зазвичай підказують, як "редагувати всі події, або лише цю, або лише майбутню", що дозволяє користувачеві обирати поведінку. У більшості випадків користувач, ймовірно, означає "змінити його вперед", але знову ж таки, вирішувати, як працює ваше програмне забезпечення та які варіанти ви надаєте користувачеві, саме вам.
Брайан Москау

19

Ви можете подивитися на реалізацію програмного забезпечення iCalendar або на сам стандарт ( RFC 2445 RFC 5545 ). Можливо, швидко приходять до тями проекти Mozilla http://www.mozilla.org/projects/calendar/ Швидкий пошук виявляє і http://icalendar.rubyforge.org/ .

Інші варіанти можуть бути розглянуті залежно від способу зберігання подій. Ви будуєте власну схему баз даних? Використання чогось на базі iCalendar тощо?


якби ви могли просто надати посилання на одне з цих, ваше повідомлення було б ідеальним
Жан,

7
Схоже, RFC2445 застарів RFC5545 ( tools.ietf.org/html/rfc5545 )
Eric Freese

16

Я працюю з наступним:

і дорогоцінний камінь, що прогресує, який розширює форматик з типом введення: recurring ( form.schedule :as => :recurring), який робить iCal-подібний інтерфейс і a, before_filterщоб знову серіалізувати подання на IceCubeоб’єкт, гетто-лі.

Моя ідея - зробити неправдоподібним просто додавати атрибути, що повторюються, до моделі та легко підключати їх у вигляді. Все в пару рядків.


То що мені це дає? Індексовані, здатні редагувати атрибути, що повторюються.

eventsзберігає один примірник дня, і використовуються в календарі / помічник сказати task.scheduleзберігає в yaml'd IceCubeоб'єкт, так що ви можете робити дзвінки , як: task.schedule.next_suggestion.

Резюме: я використовую дві моделі, одну плоску, для відображення календаря та один атрибут для функціональності.


Мені було б цікаво подивитися, що ви придумали. У вас є десь git / blog / доказ концепції? Дякую!
montrealmike

Я також працюю над чимось подібним.
Будемо раді


5
  1. Слідкуйте за правилом повторення (можливо, на основі iCalendar, per @ Kris K. ). Це буде включати схему та діапазон (Кожного третього вівторка для 10 випадків).
  2. Тому що, коли ви хочете відредагувати / видалити певну подію, слідкуйте за датами винятку для вищевказаного правила повторення (дати, коли подія не відбувається, як зазначено в правилі).
  3. Якщо ви видалили, це все, що вам потрібно, якщо ви редагували, створіть іншу подію та надайте їй батьківський ідентифікатор, встановлений для головної події. Ви можете вибрати, чи включати всю інформацію про основну подію до цього запису, або якщо вона містить лише зміни та успадковує все, що не змінюється.

Зауважте, що якщо ви дозволите правила повторення, які не закінчуються, вам доведеться подумати над тим, як відобразити тепер нескінченну кількість інформації.

Сподіваюся, що це допомагає!


4

Я рекомендую використовувати потужність бібліотеки дат та семантику модуля діапазону рубіну. Повторювана подія - це дійсно час, діапазон дат (початок і кінець) і, як правило, один день тижня. Використовуючи дату та діапазон, ви можете відповісти на будь-яке питання:

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

Проводить усі дні події, включаючи високосний рік!

# =>"[\"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\"]"

2
Це не дуже гнучко. Модель, що повторюється, часто вимагає уточнення періоду повторення (щогодини, щотижня, щотижня тощо). Крім того, повтор може бути не кваліфікований за загальною кількістю, скоріше кінцевою датою останнього явища
Бо Джіанес

«Події, що повторюються [..] , як правило , один день тижня», це тільки один випадок обмежене використання і не обробляє багато інших , такі , як «5 - й день кожного місяця» і т.д.
theraven

3

З цих відповідей я начебто просіяв рішення. Мені дуже подобається ідея концепції посилання. Повторювані події можуть бути зв'язаним списком, при цьому хвіст знає своє правило повторення. Змінити одну подію було б потім просто, оскільки посилання залишаються на місці, а також видалити подію досить просто - ви просто від’єднаєте подію, видалите її та повторно зв’яжете подію до та після неї. Вам все одно доведеться запитувати повторювані події щоразу, коли хтось дивиться на новий часовий період, який ніколи не дивилися раніше в календарі, але в іншому випадку це досить чисто.


2

Ви можете зберігати події як повторювані, і якщо певний екземпляр був відредагований, створіть нову подію з тим самим ідентифікатором події. Потім, шукаючи подію, шукайте всі події з тим самим ідентифікатором події, щоб отримати всю інформацію. Я не впевнений, чи ви прокрутили власну бібліотеку подій, чи ви використовуєте існуючу, тому це може бути неможливо.


Я використовував це рішення один раз. Мені подобається принцип зберігання модифікованого екземпляра як нової разової події, яка знає, хто його мама. Таким чином ви можете залишити всі поля порожніми, крім тих, які відрізняються від дочірньої події. Зауважте, що вам доведеться мати додаткове поле, яке визначає, яку дитину цієї матері ви редагуєте.
Wytze


1

У JavaScript:

Обробка повторюваних графіків: http://bunkat.github.io/later/

Обробка складних подій та залежностей між цими розкладами: http://bunkat.github.io/schedule/

В основному ви створюєте правила, після чого просите lib обчислити наступні N повторюваних подій (із зазначенням діапазону дат чи ні). Правила можна проаналізувати / серіалізувати для збереження їх у вашій моделі.

Якщо у вас є подія, що повторюється, і ви хочете змінити лише один повтор, ви можете скористатися функцією виключення () для відхилення певного дня, а потім додати нову змінену подію для цього запису.

Ліб підтримує дуже складні візерунки, часові пояси та навіть події кронування.


0

Зберігайте події як повторювані та динамічно відображайте їх, однак дозволяйте повторюваній події містити перелік конкретних подій, які можуть змінити інформацію за замовчуванням у певний день.

Коли ви запитуєте подію, що повторюється, вона може перевірити наявність конкретного переопрацювання на цей день.

Якщо користувач вносить зміни, ви можете запитати, чи хоче він оновити для всіх примірників (деталі за замовчуванням) або просто в цей день (внести нову конкретну подію та додати її до списку).

Якщо користувач просить видалити всі повторення цієї події, ви також маєте перелік специфікацій, які ви можете передати, і ви можете легко їх видалити.

Єдиним проблемним випадком було б, якщо користувач хоче оновити цю подію та всі майбутні події. У такому випадку вам доведеться розділити повторювану подію на дві частини. На цьому етапі ви можете розглянути можливість зв’язування повторюваних подій певним чином, щоб ви могли видалити їх усі.


0

Для програмістів .NET, які готові платити деякі ліцензійні внески, ви можете виявити корисними Aspose.Network ... до неї входить бібліотека, сумісна з iCalendar, для повторних зустрічей.


0

Ви зберігаєте події у форматі iCalendar безпосередньо, що дозволяє відкрити повторення, локалізацію часових поясів тощо.

Ви можете зберігати їх на сервері CalDAV, а потім, коли ви хочете відобразити події, ви можете скористатися опцією звіту, визначеної в CalDAV, щоб попросити сервер виконати розширення повторюваних подій протягом переглянутого періоду.

Або ви можете самостійно зберігати їх у базі даних та використовувати якусь бібліотеку розбору iCalendar для розширення, не потребуючи PUT / GET / REPORT для розмови з сервером CalDAV із заднім числом. Це, мабуть, більше роботи - я впевнений, що сервери CalDAV десь приховують складність.

Здійснення подій у форматі iCalendar, ймовірно, спростить речі в довгостроковій перспективі, оскільки люди завжди хочуть експортувати їх для розміщення іншого програмного забезпечення.


0

Я просто реалізував цю функцію! Логіка така: спочатку вам потрібно дві таблиці. RuleTable зберігає загальні або переробляє батьківські події. ItemTable зберігає події циклу. Наприклад, коли ви створюєте циклічну подію, час початку 6 листопада 2015 року, час закінчення циклу 6 (або назавжди) циклу протягом одного тижня. Ви вставляєте дані в таблицю RuleTable: поля такі:

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.

Тепер ви хочете запитувати дані з 20 листопада по 20 грудня. Ви можете записати функцію RecurringEventBE (довгий старт, довгий кінець), виходячи з часу початку та закінчення, WeekLy, ви можете обчислити потрібну колекцію, <cycleA11.20, cycleA 11.27, cycleA 12.4 ......>. Окрім 6 листопада та решти я назвав його віртуальною подією. Коли користувач змінює ім'я віртуальної події після (наприклад, цикл11.27), ви вставляєте дані в ItemTable. Поля такі:

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

У функції RecurringEventBE (довгий старт, довгий кінець) ви використовуєте ці дані, що охоплюють віртуальну подію (циклB11.27), вибачте за мою англійську мову, я спробував.

Це мій повторюванийEventBE:

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;
}   

-5

Що робити, якщо у вас повторювана зустріч без дати закінчення? Настільки дешевий, як і простір, у вас немає нескінченного простору, тому рішення 2 - це нестартер там ...

Чи можу я припустити, що "жодна дата закінчення" не може бути вирішена до кінцевої дати наприкінці століття. Навіть для щоденної події кількість місця залишається дешевою.


7
Як тільки ми забудемо уроки y2k ... :)
Ян Мерсер

10
Припустимо, у нас є 1000 користувачів, кожен з яких проводить по кілька щоденних подій. 3 події × 1000 користувачів × 365 днів × (2100-2011 = 89 років) = 97,5 мільйона записів. Замість 3000 "планів". Гм ...
Енді Михайленко
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.