Qual é a melhor maneira de modelar eventos recorrentes em um aplicativo de calendário?

225

Estou criando um aplicativo de calendário de grupo que precisa oferecer suporte a eventos recorrentes, mas todas as soluções que eu tenho para lidar com esses eventos parecem um hack. Posso limitar o quão à frente se pode olhar e depois gerar todos os eventos de uma só vez. Ou posso armazenar os eventos como repetidos e exibi-los dinamicamente quando se olha para a frente no calendário, mas terei que convertê-los em um evento normal se alguém quiser alterar os detalhes de uma instância específica do evento.

Tenho certeza de que há uma maneira melhor de fazer isso, mas ainda não a encontrei. Qual é a melhor maneira de modelar eventos recorrentes, onde você pode alterar detalhes ou excluir determinadas instâncias de eventos?

(Estou usando Ruby, mas não deixe que isso restrinja sua resposta. Se houver uma biblioteca específica para Ruby ou algo assim, é bom saber.)

Clinton N. Dreisbach
fonte

Respostas:

93

Eu usaria um conceito de 'link' para todos os eventos recorrentes futuros. Eles são exibidos dinamicamente no calendário e vinculados a um único objeto de referência. Quando os eventos ocorrem, o link é interrompido e o evento se torna uma instância independente. Se você tentar editar um evento recorrente, solicite a alteração de todos os itens futuros (por exemplo, alterar referência vinculada única) ou apenas a instância (nesse caso, converta-a em uma instância autônoma e faça a alteração). O último caso é um pouco problemático, pois você precisa acompanhar sua lista recorrente de todos os eventos futuros que foram convertidos em instância única. Mas, isso é totalmente factível.

Portanto, em essência, tenha 2 classes de eventos - instâncias únicas e eventos recorrentes.

user16068
fonte
Gosto muito da sua ideia de vincular e converter eventos para autônomos depois que eles passam. Duas perguntas: - Por que convertê-los em instâncias fixas independentes? Por que não deixá-los completamente dinâmicos? - Você pode compartilhar referência para o conceito de link proposto! Desde já, obrigado!
Rtindru 31/10/19
@rtindru um caso de uso que encontrei para converter eventos em autônomos é quando você precisa usar o modelo de evento com outros modelos no banco de dados. Por exemplo, para verificar a presença em um evento, você desejará associar usuários a um evento real que aconteceu (ou acontecerá).
Clinton Yeboah
60

Martin Fowler - Eventos recorrentes para calendários contém algumas idéias e padrões interessantes.

A gema Runt implementa esse padrão.

Daniel Maurić
fonte
Alguns exemplos melhores de código seriam bons, alguém conhece um projeto que implementou isso?
Tadeu Maia
33

Pode haver muitos problemas com eventos recorrentes, deixe-me destacar alguns que eu conheço.

Solução 1 - sem instâncias

Armazene o compromisso original + os dados de recorrência, não armazene todas as instâncias.

Problemas:

  • Você precisará calcular todas as instâncias em uma janela de data quando precisar delas, caro
  • Não é possível lidar com exceções (por exemplo, você exclui uma das instâncias ou a move, ou melhor, não pode fazer isso com esta solução)

Solução 2 - instâncias da loja

Armazene tudo de 1, mas também todas as instâncias, vinculadas ao compromisso original.

Problemas:

  • Exige muito espaço (mas o espaço é barato, muito pequeno)
  • As exceções devem ser tratadas normalmente, especialmente se você voltar e editar o compromisso original depois de fazer uma exceção. Por exemplo, se você avançar a terceira instância um dia, e se voltar e editar o horário do compromisso original, reinsira outro no dia original e deixe o que foi movido? Desvincular o movido? Tentar alterar o movido adequadamente?

Obviamente, se você não for fazer exceções, qualquer uma das soluções deve ser boa e você basicamente escolhe um cenário de troca de tempo / espaço.

Lasse V. Karlsen
fonte
36
E se você tiver um compromisso recorrente sem data de término? Tão barato quanto o espaço é, você não tem espaço infinito, então Solução 2 é um non-starter lá ...
Shaul Behr
13
A solução 1, na verdade, pode lidar com exceções. Por exemplo, o RFC5545 sugere que eles sejam armazenados como: a) uma lista de datas excluídas (quando você exclui uma ocorrência); b) ocorrências "materializadas" com referências ao protótipo (quando você move uma ocorrência).
Andy Mikhaylenko
@ Andy, algumas adições interessantes à resposta de Lasse. Vou tentar aqueles.
Jonathan Wilson
1
@ Shaul: Eu não acho que seja um não iniciante. John Skeet, que é bastante respeitado no SO, sugere armazenar instâncias geradas em sua resposta para basicamente a mesma pergunta: stackoverflow.com/a/10151804/155268
Usuário
1
@ Usuário - reconhecido, obrigado. É tão estranho - fiz meu comentário há mais de 4 anos e realmente não preciso lidar com esse problema desde então. Ontem, comecei a projetar um novo módulo que envolve compromissos recorrentes e fiquei pensando em como lidar com eles. E então - recebi uma notificação SO do seu comentário esta manhã. Seriamente assustador! Mas obrigada! :-)
Shaul Behr
21

Desenvolvi vários aplicativos baseados em calendário e também criei um conjunto de componentes reutilizáveis ​​de calendário JavaScript que oferecem suporte à recorrência. Eu escrevi uma visão geral de como projetar para recorrência que pode ser útil para alguém. Embora existam alguns bits específicos da biblioteca que escrevi, a grande maioria dos conselhos oferecidos é geral para qualquer implementação de calendário.

Alguns dos pontos principais:

  • Armazene a recorrência usando o formato iCal RRULE - essa é uma roda que você realmente não deseja reinventar
  • NÃO armazene instâncias de eventos recorrentes individuais como linhas no seu banco de dados! Sempre armazene um padrão de recorrência.
  • Existem várias maneiras de projetar seu esquema de evento / exceção, mas um exemplo básico de ponto de partida é fornecido
  • Todos os valores de data / hora devem ser armazenados em UTC e convertidos em local para exibição
  • A data de término armazenada para um evento recorrente deve sempre ser a data de término do intervalo de recorrência (ou a "data máxima" da sua plataforma se recorrente "para sempre") e a duração do evento deve ser armazenada separadamente. Isso é para garantir uma maneira sensata de consultar eventos posteriormente.
  • Algumas discussões sobre a geração de instâncias de eventos e estratégias de edição de recorrência estão incluídas

É um tópico realmente complicado, com muitas abordagens válidas para implementá-lo. Eu direi que na verdade eu implementei a recorrência várias vezes com sucesso, e eu gostaria de receber conselhos sobre esse assunto de quem ainda não o fez.

Brian Moeskau
fonte
Talvez armazenar recorrências como eventos quando eles acontecem para que a sua história calendário é precisa
Richard Haven
@RichardHaven Eu nunca faria isso. Você sempre deve gerar instâncias dos padrões RRULE de forma consistente, passada, presente ou futura. Não haveria razão para fazer algo diferente para eventos históricos. Sua lógica deve simplesmente avaliar uma RRULE em relação a qualquer período arbitrário e retornar instâncias de eventos correspondentes.
Brian Moeskau
Visão geral agradável e útil @BrianMoeskau!
Przemek Nowak
@BrianMoeskau Mas as exibições anteriores do seu calendário não mostravam informações imprecisas quando alguém edita o RRULE depois que algumas ocorrências já ocorreram? Ou talvez nesse caso você "ramifique" o RRULE e mantenha as versões modificadas dos padrões RRULE representando exatamente as ocorrências passadas reais?
cristão
1
@christian Quando você atualiza uma regra de recorrência na maioria dos calendários, eles geralmente são exibidos como "editar todos os eventos, apenas este, ou apenas o futuro", permitindo que o usuário escolha o comportamento. Na maioria dos casos, o usuário provavelmente significa "alterá-lo daqui para frente", mas novamente, cabe a você decidir como o seu software funciona e quais opções você oferece ao usuário.
Brian Moeskau 31/03/19
19

Você pode examinar as implementações do software iCalendar ou o próprio padrão ( RFC 2445 RFC 5545 ). Os que mais vêm à mente são os projetos Mozilla http://www.mozilla.org/projects/calendar/ Uma pesquisa rápida também revela http://icalendar.rubyforge.org/ .

Outras opções podem ser consideradas, dependendo de como você armazenará os eventos. Você está construindo seu próprio esquema de banco de dados? Usando algo baseado no iCalendar etc.?

Kris Kumler
fonte
se você poderia apenas fornecer um link para um destes seu post seria perfeito
Jean
7
Looks como RFC2445 foi tornada obsoleta pela RFC5545 ( tools.ietf.org/html/rfc5545 )
Eric Freese
16

Estou trabalhando com o seguinte:

e uma gema em andamento que estende o formtastic com um tipo de entrada: recurring ( form.schedule :as => :recurring), que renderiza uma interface semelhante ao iCal e before_filterserializa a visualização em um IceCubeobjeto novamente, gueto.

Minha idéia é tornar incrivelmente fácil adicionar atributos recorrentes a um modelo e conectá-lo facilmente na visualização. Tudo em algumas linhas.


Então, o que isso me dá? Atributos indexados, editáveis ​​e recorrentes.

eventslojas de uma única instância dia, e é usado na exibição de calendário / ajudante dizer task.schedulearmazena o yaml'd IceCubeobjeto, de modo que você pode fazer chamadas como: task.schedule.next_suggestion.

Recapitulando: Eu uso dois modelos, um plano, para a exibição do calendário e um atributo para a funcionalidade.

Vee
fonte
Eu estaria interessado em ver o que você inventou. Você tem um git / blog / prova de conceito em algum lugar? Obrigado!
precisa
Estou trabalhando em algo semelhante também. Adoraria ver a sua implementação
thoughtpunch
5
  1. Acompanhe uma regra de recorrência (provavelmente baseada no iCalendar, por @ Kris K. ). Isso incluirá um padrão e um intervalo (a cada terceira terça-feira, por 10 ocorrências).
  2. Para quando você quiser editar / excluir uma ocorrência específica, acompanhe as datas de exceção da regra de recorrência acima (datas em que o evento não ocorre conforme a regra especifica).
  3. Se você excluiu, é tudo o que precisa; se você editou, crie outro evento e atribua um ID pai ao evento principal. Você pode optar por incluir todas as informações do evento principal nesse registro ou se ele contém apenas as alterações e herda tudo o que não muda.

Observe que, se você permitir regras de recorrência que não terminem, terá que pensar em como exibir sua quantidade agora infinita de informações.

Espero que ajude!

bdukes
fonte
4

Eu recomendaria usar o poder da biblioteca de datas e a semântica do módulo range do ruby. Um evento recorrente é realmente um horário, um período (um começo e um fim) e geralmente um único dia da semana. Usando data e intervalo, você pode responder a qualquer pergunta:

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

Produz todos os dias do evento, incluindo o ano bissexto!

# =>"[\"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
fonte
2
Isso não é muito flexível. Um modelo de evento recorrente geralmente requer a especificação do período de repetição (a cada hora, semanal, quinzenal, etc.). Além disso, a recorrência não pode ser qualificado por um número total, em vez de uma data final para a última ocorrência
Bo Jeanes
"Um evento recorrente geralmente é [...] um único dia da semana", este é apenas um caso de uso limitado e não lida com muitos outros, como 'O quinto dia de cada mês "etc.
até
3

A partir dessas respostas, eu meio que selecionei uma solução. Eu realmente gosto da idéia do conceito de link. Eventos recorrentes podem ser uma lista vinculada, com a cauda conhecendo sua regra de recorrência. Alterar um evento seria fácil, pois os links permanecem no lugar e a exclusão de um evento também é fácil - basta desvincular um evento, excluí-lo e vincular novamente o evento antes e depois dele. Você ainda precisa consultar eventos recorrentes toda vez que alguém olha para um novo período nunca visto antes no calendário, mas, caso contrário, isso é bastante claro.

Clinton N. Dreisbach
fonte
2

Você pode armazenar os eventos como repetidos e, se uma instância específica foi editada, crie um novo evento com o mesmo ID de evento. Em seguida, ao procurar o evento, procure todos os eventos com o mesmo ID de evento para obter todas as informações. Não tenho certeza se você rolou sua própria biblioteca de eventos ou se está usando uma existente, portanto, pode não ser possível.

Vincent McNabb
fonte
Eu usei essa solução uma vez. Gosto do princípio de armazenar uma instância modificada como um novo evento pontual que sabe quem é sua mãe. Dessa forma, você pode deixar todos os campos em branco, exceto os que são diferentes para o evento filho. Observe que você precisará ter um campo extra especificando qual filho dessa mãe você está editando.
27512 Wytze
1

Em javascript:

Como lidar com agendas recorrentes: http://bunkat.github.io/later/

Manipulação de eventos complexos e dependências entre esses agendamentos: http://bunkat.github.io/schedule/

Basicamente, você cria as regras e solicita à lib que calcule os próximos N eventos recorrentes (especificando um intervalo de datas ou não). As regras podem ser analisadas / serializadas para salvá-las em seu modelo.

Se você tiver um evento recorrente e desejar modificar apenas uma recorrência, poderá usar a função except () para descartar um dia específico e, em seguida, adicionar um novo evento modificado para esta entrada.

A lib suporta padrões muito complexos, fusos horários e até eventos de criação de cronogramas.

Flavien Volken
fonte
0

Armazene os eventos como repetidos e exiba-os dinamicamente, no entanto, permita que o evento recorrente contenha uma lista de eventos específicos que podem substituir as informações padrão em um dia específico.

Quando você consulta o evento recorrente, ele pode verificar uma substituição específica para esse dia.

Se um usuário fizer alterações, você poderá perguntar se ele deseja atualizar para todas as instâncias (detalhes padrão) ou apenas naquele dia (crie um novo evento específico e adicione-o à lista).

Se um usuário solicitar a exclusão de todas as recorrências deste evento, você também terá a lista de detalhes em mãos e poderá removê-los facilmente.

O único caso problemático seria se o usuário deseja atualizar este evento e todos os eventos futuros. Nesse caso, você terá que dividir o evento recorrente em dois. Nesse ponto, você pode considerar a possibilidade de vincular eventos recorrentes de alguma forma para poder excluí-los todos.

Andrew Johnson
fonte
0

Para programadores .NET que estão preparados para pagar algumas taxas de licenciamento, o Aspose.Network pode ser útil ... inclui uma biblioteca compatível com o iCalendar para compromissos recorrentes.

Shaul Behr
fonte
0

Você armazena os eventos diretamente no formato iCalendar, o que permite repetições abertas, localização de fuso horário e assim por diante.

Você pode armazená-los em um servidor CalDAV e, quando desejar exibir os eventos, poderá usar a opção do relatório definido no CalDAV para solicitar ao servidor que faça a expansão dos eventos recorrentes no período visualizado.

Ou você pode armazená-los em um banco de dados e usar algum tipo de biblioteca de análise do iCalendar para fazer a expansão, sem a necessidade de PUT / GET / REPORT para conversar com um servidor CalDAV de back-end. Provavelmente é mais trabalhoso - tenho certeza que os servidores CalDAV ocultam a complexidade em algum lugar.

Ter os eventos no formato iCalendar provavelmente tornará as coisas mais simples a longo prazo, pois as pessoas sempre desejam que elas sejam exportadas para a instalação de outro software de qualquer maneira.

karora
fonte
0

Simplesmente implementei esse recurso! A lógica é a seguinte: primeiro você precisa de duas tabelas. RuleTable armazena eventos paternos gerais ou de reciclagem. ItemTable são eventos de ciclo armazenados. Por exemplo, quando você cria um evento cíclico, o horário de início de 6 de novembro de 2015, o horário de término do dia 6 de dezembro (ou para sempre), ciclo por uma semana. Você insere dados em uma RuleTable, os campos são os seguintes:

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.

Agora você deseja consultar os dados de 20 de novembro a 20 de dezembro. Você pode escrever uma função RecurringEventBE (início longo, fim longo), com base no horário de início e término, WeekLy, é possível calcular a coleção desejada, <cycleA11.20, cycleA 11.27, cycleA 12.4 ......>. Além de 6 de novembro, e o resto, eu o chamei de um evento virtual. Quando o usuário altera o nome de um evento virtual depois (cycleA11.27, por exemplo), você insere os dados em uma ItemTable. Os campos são os seguintes:

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

Na função RecurringEventBE (início longo, fim longo), você usa esses dados que abrangem o evento virtual (cycleB11.27), desculpe pelo meu inglês, tentei.

Este é o meu 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
fonte
-5

E se você tiver um compromisso recorrente sem data final? Por mais barato que seja o espaço, você não tem espaço infinito, então a Solução 2 é uma iniciação lá ...

Posso sugerir que "nenhuma data final" possa ser resolvida até uma data final no final do século. Mesmo para um evento diário, a quantidade de espaço permanece barata.

poumtatalia
fonte
7
Quão cedo esquecemos as lições do y2k ... :) #
Ian Ian Mercer
10
Vamos supor que temos 1000 usuários, cada um com alguns eventos diários. 3 eventos × 1.000 usuários × 365 dias × (2100-2011 = 89 anos) = 97,5 milhões de registros. Em vez de 3000 "planos". Um ...
Andy Mikhaylenko