캘린더 응용 프로그램에서 되풀이 이벤트를 모델링하는 가장 좋은 방법은 무엇입니까?


224

반복 일정을 지원해야하는 그룹 일정 응용 프로그램을 작성하고 있지만 이러한 일정을 처리하기 위해 제시 한 모든 솔루션은 해킹처럼 보입니다. 어느 정도 앞을 볼 수 있는지 제한 한 다음 모든 이벤트를 한 번에 생성 할 수 있습니다. 또는 일정을 반복으로 저장하고 캘린더에서 미리 볼 때 동적으로 표시 할 수 있지만 누군가가 특정 이벤트 인스턴스의 세부 정보를 변경하려면 일반 이벤트로 변환해야합니다.

더 좋은 방법이 있다고 확신하지만 아직 찾지 못했습니다. 특정 이벤트 인스턴스의 세부 사항을 변경하거나 삭제할 수있는 반복 이벤트를 모델링하는 가장 좋은 방법은 무엇입니까?

(루비를 사용하고 있지만 답을 제한하지 마십시오. 루비 전용 라이브러리 나 다른 것이 있다면 알아두면 좋습니다.)

답변:


93

나는 미래의 모든 되풀이 이벤트에 대해 '링크'개념을 사용합니다. 달력에 동적으로 표시되고 단일 참조 객체로 다시 연결됩니다. 이벤트가 발생하면 링크가 끊어지고 이벤트가 독립형 인스턴스가됩니다. 되풀이 이벤트를 편집하려고하면 이후의 모든 항목 (예 : 단일 연결된 참조 변경)을 변경하거나 해당 인스턴스 만 변경하라는 메시지가 표시됩니다 (이 경우 독립형 인스턴스로 변환 한 다음 변경). 후자의 경우 단일 인스턴스로 변환 된 모든 향후 이벤트의 반복 목록을 추적해야하므로 약간 문제가 있습니다. 그러나 이것은 전적으로 가능합니다.

따라서 본질적으로 단일 인스턴스와 되풀이 이벤트라는 두 가지 클래스의 이벤트가 있습니다.


이벤트가 통과 된 후 이벤트를 독립형으로 링크하고 변환하려는 아이디어와 같습니다. 두 가지 질문 :-왜 독립형 고정 인스턴스로 변환해야합니까? 왜 그것들을 완전히 역동적으로 남겨 두지 않겠습니까? -제안 된 링크 개념에 대한 참조를 공유 할 수 있습니까? 미리 감사드립니다!
rtindru

@rtindru 이벤트를 독립형으로 변환하기 위해 찾은 유스 케이스는 데이터베이스의 다른 모델과 함께 이벤트 모델 을 사용해야하는 경우 입니다. 예를 들어, 이벤트 참석을 확인하기 위해 발생한 실제 이벤트와 사용자를 연관 시키려고합니다.
클린턴 예보


33

반복되는 이벤트에는 많은 문제가있을 수 있습니다. 제가 알고있는 몇 가지를 강조하겠습니다.

해결 방법 1-인스턴스 없음

원래 약속 + 되풀이 데이터를 저장하고 모든 인스턴스를 저장하지는 마십시오.

문제 :

  • 필요할 때 날짜 창에서 모든 인스턴스를 계산해야합니다.
  • 예외를 처리 할 수 ​​없습니다 (예 : 인스턴스 중 하나를 삭제하거나 이동하거나이 솔루션으로는이를 수행 할 수 없음)

솔루션 2-인스턴스 저장

원래 약속에 다시 연결된 1부터 모든 인스턴스를 모두 저장합니다.

문제 :

  • 많은 공간을 차지합니다 (그러나 공간은 저렴하므로 아주 작습니다)
  • 예외를 처리 한 후 원래 약속을 되돌아 가서 편집하는 경우 예외를 정상적으로 처리해야합니다. 예를 들어, 세 번째 인스턴스를 하루 앞으로 이동하면 원래 약속 시간으로 돌아가서 원래 날짜의 시간을 다시 삽입하고 이동 한 날짜를 그대로두면 어떻게됩니까? 이동 한 링크를 해제 하시겠습니까? 이동 한 것을 적절히 변경하려고합니까?

물론, 예외를하지 않는다면, 어느 쪽의 솔루션이든 괜찮을 것이며 기본적으로 시간 / 공간 상충 관계 시나리오에서 선택합니다.


36
종료 날짜가없는 되풀이 약속이 있으면 어떻게합니까? 공간이 저렴할수록 무한한 공간이 없기 때문에 솔루션 2는 그
출발점

13
솔루션 # 1은 실제로 예외를 처리 할 수 ​​있습니다. 예를 들어, RFC5545는 다음과 같이 저장하도록 제안합니다. a) 제외 된 날짜 목록 (발생을 삭제할 때); b) 프로토 타입을 참조하여 "구체화 된"발생 (발생을 이동할 때)
Andy Mikhaylenko

@Andy, Lasse의 답변에 흥미로운 추가 사항. 시도 해봐
Jonathan Wilson

1
@Shaul : 스타터가 아닌 것 같아요. SO에 대해 매우 존경받는 John Skeet은 기본적으로 동일한 질문에 대한 답변으로 생성 된 인스턴스를 저장하도록 제안합니다. stackoverflow.com/a/10151804/155268
User

1
@User-감사합니다. 너무 이상합니다-4 년 전에 의견을 말한 이후 로이 문제를 처리 할 필요가 없었습니다. 어제 저는 반복되는 약속이 포함 된 새로운 모듈을 디자인하게되었으며,이를 처리하는 방법이 궁금했습니다. 그리고 오늘 아침에 귀하의 의견에 대한 SO 통지를 받았습니다. 정말 짜증나! 그러나 감사합니다! :-)
Shaul Behr

20

여러 달력 기반 응용 프로그램을 개발했으며 반복을 지원하는 재사용 가능한 JavaScript 달력 구성 요소 집합도 작성했습니다. 누군가에게 도움이 될 수있는 재발을 디자인하는 방법에 대한 개요를 작성했습니다 . 내가 작성한 라이브러리에 특정한 몇 가지 비트가 있지만 제공되는 조언의 대부분은 모든 캘린더 구현에 일반적입니다.

핵심 포인트 중 일부 :

  • iCal RRULE 형식을 사용하여 되풀이를 저장하십시오 -그것은 정말로 재발 명하고 싶지 않은 바퀴입니다
  • 개별 반복 이벤트 인스턴스 를 데이터베이스에 행으로 저장하지 마십시오 ! 항상 반복 패턴을 저장하십시오.
  • 이벤트 / 예외 스키마를 설계하는 방법에는 여러 가지가 있지만 기본적인 시작점 예제가 제공됩니다.
  • 모든 날짜 / 시간 값은 UTC로 저장하고 표시하기 위해 로컬로 변환해야합니다
  • 되풀이 이벤트에 대해 저장된 종료 날짜는 항상 되풀이 범위종료 날짜 (또는 되풀이되는 "영구"인 경우 플랫폼의 "최대 날짜") 여야하며 이벤트 기간은 별도로 저장해야합니다. 이것은 나중에 이벤트를 깔끔하게 쿼리하는 방법입니다.
  • 이벤트 인스턴스 생성 및 반복 편집 전략에 대한 일부 토론이 포함되어 있습니다.

구현에 대한 많은 유효한 접근 방식이있는 정말 복잡한 주제입니다. 나는 실제로 반복을 여러 번 성공적으로 구현했다고 말하고 실제로 그것을하지 않은 사람 으로부터이 주제에 대해 조언을하는 것에 경계 할 것입니다.


아마도 당신의 캘린더 기록이 정확할 때 재발을 이벤트로 저장할 수 있습니다.
Richard Haven

@RichardHaven 나는 그렇게하지 않을 것입니다. 항상 RRULE 패턴에서 일관되게, 과거, 현재 또는 미래의 인스턴스를 생성해야합니다. 역사적 사건에 대해 다른 것을 할 이유가 없습니다. 로직은 임의의 날짜 범위에 대해 RRULE을 평가하고 일치하는 이벤트 인스턴스를 반환해야합니다.
Brian Moeskau

@BrianMoeskau 훌륭하고 유용한 개요!
Przemek Nowak 12

@BrianMoeskau 그러나 누군가가 이미 발생한 후에 RRULE을 편집하면 캘린더의 과거 뷰에 부정확 한 정보가 표시되지 않습니까? 또는이 경우 RRULE을 "분기 (branch)"하고 실제 과거 발생을 정확하게 나타내는 수정 된 버전의 RRULE 패턴을 유지합니까?
기독교

1
@christian 대부분의 캘린더에서 되풀이 규칙을 업데이트하면 일반적으로 사용자가 동작을 선택할 수 있도록 "모든 이벤트 또는이 이벤트 만 또는 나중에 만 편집"과 같은 프롬프트가 표시됩니다. 대부분의 경우 사용자는 "앞으로 변경"을 의미하지만 소프트웨어 작동 방식과 사용자에게 제공하는 옵션을 결정하는 것은 사용자의 몫입니다.
Brian Moeskau

19

iCalendar 소프트웨어 구현 또는 표준 자체 ( RFC 2445 RFC 5545 ) 를보고 싶을 수 있습니다 . 모질라 프로젝트는 http://www.mozilla.org/projects/calendar/ 에서 빠르게 기억할 수 있습니다. 빠른 검색 에서도 http://icalendar.rubyforge.org/ 가 드러납니다 .

이벤트를 저장하는 방법에 따라 다른 옵션을 고려할 수 있습니다. 자신 만의 데이터베이스 스키마를 구축하고 있습니까? iCalendar 기반 등을 사용하십니까?


이 중 하나에 대한 링크 만 제공하면 게시물이 완벽 할 것입니다.
Jean

7
RFC2445가 RFC5545에 의해 폐기 된 것 같습니다 ( tools.ietf.org/html/rfc5545 )
Eric Freese

16

나는 다음과 같이 일하고있다 :

form.schedule :as => :recurringiCal과 같은 인터페이스를 렌더링 before_filter하고 뷰를 IceCube다시 객체 로 직렬화 하는 입력 유형 : recurring ( )으로 형식을 확장하는 gem이 진행 중입니다 .

내 생각은 반복 속성을 모델에 쉽게 추가하고보기에서 쉽게 연결할 수 있도록하는 것입니다. 두 줄에 모두 있습니다.


그래서 이것은 무엇을 제공합니까? 인덱싱되고 편집 가능한 반복 속성입니다.

events하루 인스턴스를 저장하고 달력보기 / 도우미 task.schedule에서 yaml'd IceCube객체를 저장 한다고 사용 되므로 다음과 같은 호출을 수행 할 수 있습니다 task.schedule.next_suggestion.

요약 : 달력 표시에는 하나, 평면에는 하나, 기능에는 하나는 두 가지 모델을 사용합니다.


나는 당신이 생각 해낸 것을보고 싶어 할 것입니다. git / blog / of of concept 개념이 있습니까? 감사!
montrealmike

나는 비슷한 일을하고 있습니다. 귀하의 구현을보고
싶습니다


5
  1. 반복 규칙을 추적하십시오 (@ Kris K. 당 iCalendar를 기반으로 함 ). 여기에는 패턴과 범위가 포함됩니다 (세 번째 화요일마다 10 회 발생).
  2. 특정 발생을 편집 / 삭제하려는 경우 위의 반복 규칙에 대한 예외 날짜 ( 규칙이 지정한대로 이벤트 발생 하지 않는 날짜)를 추적 하십시오.
  3. 삭제 한 경우, 필요한 경우 편집 한 경우 다른 이벤트를 작성하고 기본 이벤트로 설정된 상위 ID를 제공하십시오. 이 레코드에 모든 기본 이벤트 정보를 포함할지 또는 변경 사항 만 보유하고 변경되지 않는 모든 것을 상속할지 여부를 선택할 수 있습니다.

끝나지 않는 반복 규칙을 허용하는 경우 이제 무한한 양의 정보를 표시하는 방법에 대해 생각해야합니다.

희망이 도움이됩니다!


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
이것은 매우 유연하지 않습니다. 되풀이되는 이벤트 모델은 종종 반복 기간 (시간별, 주별, 격주 등)을 지정해야합니다. 또한 재발은 총 수에 의해 자격이 부여되지 않을 수 있습니다. 대신 마지막 발생에 대한 종료 날짜
Bo Jeanes

"반복 이벤트는 [..] 일반적으로 하루의 요일입니다.", 이것은 하나의 제한된 사용 사례 일
뿐이며

3

이 답변들로부터 해결책을 찾아 냈습니다. 나는 링크 개념의 아이디어를 정말로 좋아한다. 되풀이되는 이벤트는 꼬리가 반복 규칙을 알고있는 연결된 목록 일 수 있습니다. 링크가 그대로 유지되고 이벤트를 삭제하기도 쉽기 때문에 하나의 이벤트를 쉽게 변경할 수 있습니다. 이벤트를 연결 해제하고 삭제 한 다음 이벤트 전후에 다시 연결하면됩니다. 누군가가 달력에서 이전에 보지 않은 새로운 기간을 볼 때마다 되풀이 이벤트를 쿼리해야하지만 그렇지 않은 경우에는 매우 깨끗합니다.


2

이벤트를 반복으로 저장하고 특정 인스턴스를 편집 한 경우 동일한 이벤트 ID로 새 이벤트를 작성하십시오. 그런 다음 이벤트를 찾을 때 동일한 이벤트 ID를 가진 모든 이벤트를 검색하여 모든 정보를 얻으십시오. 자신의 이벤트 라이브러리를 롤링했는지 또는 기존 라이브러리를 사용 중인지 확실하지 않으므로 잘 모르겠습니다.


이 솔루션을 한 번 사용했습니다. 나는 수정 된 인스턴스를 그것의 엄마가 누구인지 아는 새로운 일회성 이벤트로 저장하는 원리를 좋아한다. 이렇게하면 자식 이벤트와 다른 필드를 제외한 모든 필드를 비워 둘 수 있습니다. 편집중인이 어머니의 자식을 지정하는 추가 필드가 있어야합니다.
Wytze


1

자바 스크립트에서 :

반복 일정 처리 : http://bunkat.github.io/later/

해당 일정 간의 복잡한 이벤트 및 종속성 처리 : http://bunkat.github.io/schedule/

기본적으로 규칙을 만든 다음 lib에 다음 N 개의 반복 이벤트 (날짜 범위 지정 여부)를 계산하도록 요청합니다. 규칙을 모델에 저장하기 위해 파싱 / 직렬화 할 수 있습니다.

되풀이 이벤트가 있고 한 번의 반복 만 수정하려면 except () 함수를 사용하여 특정 날짜를 닫은 다음이 항목에 대해 수정 된 새 이벤트를 추가 할 수 있습니다.

lib는 매우 복잡한 패턴, 시간대 및 croning 이벤트를 지원합니다.


0

이벤트를 반복으로 저장하고 동적으로 표시하지만 반복 이벤트에 특정 날짜의 기본 정보를 대체 할 수있는 특정 이벤트 목록을 포함 할 수 있습니다.

되풀이 이벤트를 쿼리하면 해당 날짜의 특정 재정의를 확인할 수 있습니다.

사용자가 변경하면 모든 인스턴스 (기본 세부 사항) 또는 해당 날짜에 대한 업데이트를 원하는지 (새 특정 이벤트를 작성하여 목록에 추가) 요청할 수 있습니다.

사용자가이 이벤트의 모든 반복 항목을 삭제하도록 요청하면 처리 할 세부 사항 목록도 있으며 쉽게 제거 할 수 있습니다.

사용자가이 이벤트와 모든 향후 이벤트를 업데이트하려는 경우 문제가 될 수 있습니다. 이 경우 되풀이 이벤트를 2 개로 분할해야합니다. 이 시점에서 되풀이되는 이벤트를 어떤 방식 으로든 연결하여 모두 삭제할 수 있습니다.


0

라이센스 비용을 지불 할 준비가 된 .NET 프로그래머의 경우 Aspose.Network가 유용 할 것입니다. 여기에는 되풀이 약속을위한 iCalendar 호환 라이브러리가 포함되어 있습니다.


0

이벤트를 iCalendar 형식으로 직접 저장하면 개방형 반복, 시간대 지역화 등이 가능합니다.

이를 CalDAV 서버에 저장 한 다음 이벤트를 표시하려는 경우 CalDAV에 정의 된 보고서 옵션을 사용하여 서버가 조회 기간 동안 반복되는 이벤트를 확장하도록 요청할 수 있습니다.

또는 PUT / GET / REPORT를 사용하여 백엔드 CalDAV 서버와 통신 할 필요없이 데이터베이스에 직접 저장하고 iCalendar 파싱 라이브러리를 사용하여 확장을 수행 할 수 있습니다. 이것은 아마도 더 많은 작업 일 것입니다-CalDAV 서버는 어딘가에 복잡성을 숨길 것이라고 확신합니다.

iCalendar 형식의 이벤트가 있으면 사람들은 항상 다른 소프트웨어를 사용하기 위해 내보내기를 원하기 때문에 장기적으로 훨씬 간단 해집니다.


0

이 기능을 간단히 구현했습니다! 논리는 다음과 같습니다. 먼저 두 개의 테이블이 필요합니다. RuleTable은 일반 또는 재활용 부계 이벤트를 저장합니다. ItemTable은 저장된주기 이벤트입니다. 예를 들어, 주기적 이벤트를 작성할 때 2015 년 11 월 6 일의 시작 시간, 12 월 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.

이제 11 월 20 일부터 12 월 20 일까지 데이터를 쿼리하려고합니다. 시작 및 종료 시간, WeekLy를 기반으로 RecurringEventBE (long start, long end) 함수를 작성할 수 있습니다. <cycleA11.20, cycleA 11.27, cycleA 12.4 ......> 원하는 콜렉션을 계산할 수 있습니다. 11 월 6 일 외에 나머지는 가상 이벤트라고 불렀습니다. 사용자가 가상 ​​이벤트 이름 (예 : cycleA11.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 (long start, long end) 함수에서 가상 이벤트 (cycleB11.27)를 다루는이 데이터를 사용하여 영어에 대해 죄송합니다. 시도했습니다.

이것이 나의 되풀이 이벤트입니다 BE :

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의 교훈을 얼마나 빨리 잊어 버릴까요 ... :)
Ian Mercer

10
각각 하루에 두 번의 이벤트가있는 1000 명의 사용자가 있다고 가정 해 봅시다. 3 개의 이벤트 × 1000 명의 사용자 × 365 일 × (2100-2011 = 89 년) = 9,750 만 건. 3000 "계획"대신. Um ...
Andy Mikhaylenko
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.