Cách tốt nhất để mô hình các sự kiện định kỳ trong ứng dụng lịch là gì?


225

Tôi đang xây dựng một ứng dụng lịch nhóm cần hỗ trợ các sự kiện định kỳ, nhưng tất cả các giải pháp tôi đưa ra để xử lý các sự kiện này có vẻ như là một hack. Tôi có thể giới hạn khoảng cách phía trước người ta có thể nhìn, và sau đó tạo ra tất cả các sự kiện cùng một lúc. Hoặc tôi có thể lưu trữ các sự kiện như lặp lại và hiển thị động chúng khi chúng nhìn về phía trước trên lịch, nhưng tôi sẽ phải chuyển đổi chúng thành một sự kiện bình thường nếu ai đó muốn thay đổi chi tiết về một sự kiện cụ thể của sự kiện.

Tôi chắc chắn có một cách tốt hơn để làm điều này, nhưng tôi chưa tìm thấy nó. Cách tốt nhất để mô hình hóa các sự kiện định kỳ, nơi bạn có thể thay đổi chi tiết hoặc xóa các trường hợp sự kiện cụ thể?

(Tôi đang sử dụng Ruby, nhưng xin đừng để điều đó làm hạn chế câu trả lời của bạn. Tuy nhiên, nếu có thư viện dành riêng cho Ruby hoặc một cái gì đó, thì đó là điều tốt để biết.)

Câu trả lời:


93

Tôi sẽ sử dụng khái niệm 'liên kết' cho tất cả các sự kiện định kỳ trong tương lai. Chúng được hiển thị động trong lịch và liên kết lại với một đối tượng tham chiếu duy nhất. Khi các sự kiện đã diễn ra, liên kết bị hỏng và sự kiện trở thành một trường hợp độc lập. Nếu bạn cố gắng chỉnh sửa một sự kiện định kỳ thì hãy nhắc thay đổi tất cả các mục trong tương lai (nghĩa là thay đổi tham chiếu được liên kết đơn) hoặc thay đổi chỉ trường hợp đó (trong trường hợp đó chuyển đổi nó thành một thể hiện độc lập và sau đó thực hiện thay đổi). Trường hợp thứ hai hơi có vấn đề vì bạn cần theo dõi trong danh sách định kỳ của mình về tất cả các sự kiện trong tương lai đã được chuyển đổi thành một thể hiện duy nhất. Nhưng, điều này là hoàn toàn có thể làm được.

Vì vậy, về bản chất, có 2 lớp sự kiện - trường hợp đơn và sự kiện định kỳ.


Thực sự thích ý tưởng của bạn về liên kết và chuyển đổi các sự kiện thành độc lập sau khi chúng đã qua. Hai câu hỏi: - Tại sao lại chuyển đổi chúng thành các trường hợp cố định độc lập? Tại sao không để chúng hoàn toàn năng động? - Bạn có thể chia sẻ tài liệu tham khảo cho khái niệm liên kết được đề xuất! Cảm ơn trước!
rtindru

@rtindru một trường hợp sử dụng tôi tìm thấy để chuyển đổi các sự kiện thành độc lập là khi bạn phải sử dụng mô hình sự kiện với các mô hình khác trong cơ sở dữ liệu của bạn. Ví dụ: để kiểm tra tham dự một sự kiện, bạn sẽ muốn liên kết người dùng với một sự kiện thực sự đã xảy ra (hoặc sẽ xảy ra).
Clinton Yeboah


33

Có thể có nhiều vấn đề với các sự kiện định kỳ, hãy để tôi nhấn mạnh một vài vấn đề mà tôi biết.

Giải pháp 1 - không có trường hợp

Lưu trữ cuộc hẹn ban đầu + dữ liệu lặp lại, không lưu trữ tất cả các trường hợp.

Các vấn đề:

  • Bạn sẽ phải tính toán tất cả các trường hợp trong một cửa sổ ngày khi bạn cần chúng, tốn kém
  • Không thể xử lý các trường hợp ngoại lệ (ví dụ: bạn xóa một trong các trường hợp hoặc di chuyển nó, hoặc đúng hơn, bạn không thể làm điều này với giải pháp này)

Giải pháp 2 - lưu trữ trường hợp

Lưu trữ mọi thứ từ 1, nhưng tất cả các trường hợp, được liên kết trở lại cuộc hẹn ban đầu.

Các vấn đề:

  • Chiếm rất nhiều không gian (nhưng không gian thì rẻ, rất nhỏ)
  • Các ngoại lệ phải được xử lý một cách duyên dáng, đặc biệt nếu bạn quay lại và chỉnh sửa cuộc hẹn ban đầu sau khi thực hiện một ngoại lệ. Chẳng hạn, nếu bạn di chuyển phiên bản thứ ba về phía trước một ngày, điều gì sẽ xảy ra nếu bạn quay lại và chỉnh sửa thời gian của cuộc hẹn ban đầu, chèn lại một lần nữa vào ngày ban đầu và rời khỏi cuộc hẹn đã di chuyển? Bỏ liên kết di chuyển một? Cố gắng thay đổi di chuyển một cách thích hợp?

Tất nhiên, nếu bạn sẽ không làm ngoại lệ, thì một trong hai giải pháp sẽ ổn và về cơ bản bạn chọn từ kịch bản đánh đổi không gian / thời gian.


36
Điều gì nếu bạn có một cuộc hẹn định kỳ không có ngày kết thúc? Rẻ như không gian, bạn không có không gian vô hạn, vì vậy Giải pháp 2 là không bắt đầu ở đó ...
Shaul Behr

13
Giải pháp số 1 thực sự có thể xử lý các trường hợp ngoại lệ. Ví dụ: RFC5545 gợi ý rằng chúng được lưu trữ dưới dạng: a) danh sách các ngày bị loại trừ (khi bạn xóa một sự cố); b) các lần xuất hiện "cụ thể hóa" với các tham chiếu đến nguyên mẫu (khi bạn di chuyển một lần xuất hiện).
Andy Mikhaylenko

@Andy, một số bổ sung thú vị cho câu trả lời của Lasse. Gonna cho những người thử.
Jonathan Wilson

1
@Shaul: Tôi không nghĩ đó là một người không bắt đầu. John Skeet, người rất được kính trọng trên SO, đề nghị lưu trữ các trường hợp được tạo trong câu trả lời của anh ấy cho cùng một câu hỏi: stackoverflow.com/a/10151804/155268
Người dùng

1
@ Người dùng - thừa nhận, cảm ơn bạn. Điều đó thật kỳ lạ - tôi đã đưa ra nhận xét của mình hơn 4 năm trước và tôi thực sự không cần thiết phải giải quyết vấn đề này kể từ đó. Mới hôm qua tôi đã thiết kế một mô-đun mới liên quan đến các cuộc hẹn định kỳ và tôi đã tự hỏi làm thế nào để xử lý chúng. Và sau đó - tôi nhận được thông báo SO về nhận xét của bạn sáng nay. Nghiêm túc ma quái! Nhưng cảm ơn bạn! :-)
Shaul Behr

21

Tôi đã phát triển nhiều ứng dụng dựa trên lịch và cũng là tác giả của một bộ các thành phần lịch JavaScript có thể sử dụng lại hỗ trợ tái phát. Tôi đã viết lên một cái nhìn tổng quan về cách thiết kế cho sự tái phát có thể hữu ích cho ai đó. Mặc dù có một vài bit dành riêng cho thư viện tôi đã viết, nhưng phần lớn lời khuyên được đưa ra là chung cho bất kỳ triển khai lịch nào.

Một số điểm chính:

  • Lưu trữ tái phát bằng định dạng iCal RRULE - đó là một bánh xe bạn thực sự không muốn phát minh lại
  • ĐỪNG lưu trữ cá nhân định kỳ sự kiện trường hợp như hàng trong cơ sở dữ liệu của bạn! Luôn luôn lưu trữ một mô hình tái phát.
  • Có nhiều cách để thiết kế lược đồ sự kiện / ngoại lệ của bạn, nhưng một ví dụ về điểm bắt đầu cơ bản được cung cấp
  • Tất cả các giá trị ngày / giờ nên được lưu trữ trong UTC và được chuyển đổi thành cục bộ để hiển thị
  • Ngày kết thúc được lưu trữ cho một sự kiện định kỳ phải luôn luôn là ngày kết thúc của phạm vi lặp lại (hoặc "ngày tối đa" của nền tảng của bạn nếu định kỳ "mãi mãi") và thời lượng sự kiện nên được lưu trữ riêng. Điều này là để đảm bảo một cách truy vấn lành mạnh cho các sự kiện sau này.
  • Một số thảo luận về việc tạo các trường hợp sự kiện và chiến lược chỉnh sửa lặp lại được bao gồm

Đây là một chủ đề thực sự phức tạp với nhiều, nhiều cách tiếp cận hợp lệ để thực hiện nó. Tôi sẽ nói rằng tôi thực sự đã thực hiện tái phát nhiều lần thành công và tôi sẽ cảnh giác khi nhận lời khuyên về chủ đề này từ bất kỳ ai chưa thực sự làm điều đó.


Có lẽ lưu trữ các đợt tái phát dưới dạng sự kiện khi chúng xảy ra để lịch sử lịch của bạn là chính xác
Richard Haven

@RichardHaven Tôi sẽ không bao giờ làm điều đó. Bạn phải luôn tạo các thể hiện từ các mẫu RRULE một cách nhất quán, quá khứ, hiện tại hoặc tương lai. Sẽ không có lý do để làm một cái gì đó khác nhau cho các sự kiện lịch sử. Logic của bạn chỉ cần đánh giá RRULE theo bất kỳ phạm vi ngày tùy ý và trả về các trường hợp sự kiện phù hợp.
Brian Moeskau 7/2/18

@BrianMoeskau tổng quan tốt đẹp và hữu ích!
Przemek Nowak

@BrianMoeskau Nhưng sau đó, các lượt xem trong lịch của bạn sẽ hiển thị thông tin không chính xác khi ai đó chỉnh sửa RRULE sau khi một số lần xuất hiện đã xảy ra? Hoặc có thể trong trường hợp đó, bạn sẽ "phân nhánh" RRULE và giữ các phiên bản sửa đổi của các mẫu RRULE thể hiện chính xác các lần xuất hiện thực tế trong quá khứ?
christian

1
@christian Khi bạn cập nhật quy tắc lặp lại trong hầu hết các lịch, họ thường nhắc nhở như "chỉnh sửa tất cả các sự kiện hoặc chỉ sự kiện này hoặc chỉ trong tương lai" cho phép người dùng chọn hành vi. Trong hầu hết các trường hợp, người dùng có thể có nghĩa là "thay đổi trong tương lai" nhưng một lần nữa, tùy bạn quyết định cách thức phần mềm của bạn hoạt động và những tùy chọn bạn cung cấp cho người dùng.
Brian Moeskau

19

Bạn có thể muốn xem triển khai phần mềm iCalWiki hoặc bản thân tiêu chuẩn ( RFC 2445 RFC 5545 ). Những ý tưởng nhanh chóng xuất hiện là các dự án Mozilla http://www.mozilla.org/projects/calWiki/ Một tìm kiếm nhanh cũng cho thấy http://icalWiki.rubyforge.org/ .

Các tùy chọn khác có thể được xem xét tùy thuộc vào cách bạn sẽ lưu trữ các sự kiện. Bạn đang xây dựng lược đồ cơ sở dữ liệu của riêng bạn? Sử dụng một cái gì đó dựa trên iCalWiki, v.v.?


nếu bạn chỉ có thể cung cấp một liên kết đến một trong những bài đăng này thì bài viết của bạn sẽ hoàn hảo
Jean

7
Có vẻ như RFC2445 đã bị RFC5545 lỗi thời ( tools.ietf.org/html/rfc5545 )
Eric Freese

16

Tôi đang làm việc với những điều sau đây:

và một viên ngọc đang trong quá trình mở rộng formtastic với một kiểu đầu vào: recurring ( form.schedule :as => :recurring), biểu hiện giao diện giống như iCal và một lần nữa before_filternối tiếp khung nhìn vào một IceCubeđối tượng, ghetto-ly.

Ý tưởng của tôi là làm cho việc dễ dàng thêm các thuộc tính định kỳ vào mô hình và kết nối nó dễ dàng trong chế độ xem. Tất cả trong một vài dòng.


Vì vậy, những gì này mang lại cho tôi? Thuộc tính được lập chỉ mục, có thể chỉnh sửa, định kỳ.

eventslưu trữ một cá thể một ngày và được sử dụng trong chế độ xem lịch / người trợ giúp nói task.schedulelưu trữ IceCubeđối tượng yaml'd , vì vậy bạn có thể thực hiện các cuộc gọi như : task.schedule.next_suggestion.

Tóm tắt: Tôi sử dụng hai mô hình, một mô hình phẳng, cho màn hình lịch và một thuộc tính cho chức năng.


Tôi sẽ quan tâm để xem những gì bạn đã đưa ra. Bạn có git / blog / bằng chứng về khái niệm ở bất cứ đâu không? Cảm ơn!
montrealmike

Tôi đang làm việc trên một cái gì đó tương tự là tốt. Rất thích xem triển khai của bạn
thinkpunch


5
  1. Theo dõi quy tắc lặp lại (có thể dựa trên iCalWiki, per @ Kris K. ). Điều này sẽ bao gồm một mô hình và một phạm vi (Mỗi thứ ba thứ ba, trong 10 lần xuất hiện).
  2. Vì khi bạn muốn chỉnh sửa / xóa một sự cố cụ thể, hãy theo dõi ngày ngoại lệ cho quy tắc lặp lại ở trên (ngày mà sự kiện không xảy ra như quy tắc chỉ định).
  3. Nếu bạn đã xóa, đó là tất cả những gì bạn cần, nếu bạn chỉnh sửa, tạo một sự kiện khác và cung cấp cho nó một ID gốc được đặt thành sự kiện chính. Bạn có thể chọn có bao gồm tất cả thông tin của sự kiện chính trong hồ sơ này hay không, nếu nó chỉ giữ các thay đổi và kế thừa mọi thứ không thay đổi.

Lưu ý rằng nếu bạn cho phép các quy tắc lặp lại không kết thúc, bạn phải suy nghĩ về cách hiển thị lượng thông tin vô hạn hiện tại của mình.

Mong rằng sẽ giúp!


4

Tôi khuyên bạn nên sử dụng sức mạnh của thư viện ngày và ngữ nghĩa của mô-đun phạm vi của ruby. Một sự kiện định kỳ thực sự là một thời gian, một phạm vi ngày (bắt đầu và kết thúc) và thường là một ngày trong tuần. Sử dụng ngày & phạm vi bạn có thể trả lời bất kỳ câu hỏi:

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

Sản xuất tất cả các ngày của sự kiện, bao gồm cả năm nhuận!

# =>"[\"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
Điều này không linh hoạt lắm. Một mô hình sự kiện định kỳ thường sẽ yêu cầu chỉ định thời gian lặp lại (hàng giờ, hàng tuần, hai tuần, v.v.). Ngoài ra, sự tái phát có thể không đủ điều kiện theo tổng số, thay vào đó là ngày kết thúc cho lần xuất hiện cuối cùng
Bo Jeanes

"Một sự kiện định kỳ là [..] thường là một ngày duy nhất trong tuần", đây chỉ là một trường hợp sử dụng hạn chế và không xử lý nhiều người khác như 'Ngày mùng 5 hàng tháng" vv
theraven

3

Từ những câu trả lời này, tôi đã tìm ra một giải pháp. Tôi thực sự thích ý tưởng của khái niệm liên kết. Các sự kiện định kỳ có thể là một danh sách được liên kết, với phần đuôi biết quy tắc lặp lại của nó. Thay đổi một sự kiện sau đó sẽ dễ dàng, bởi vì các liên kết giữ nguyên vị trí và xóa một sự kiện cũng dễ dàng - bạn chỉ cần hủy liên kết một sự kiện, xóa nó và liên kết lại sự kiện trước và sau sự kiện đó. Bạn vẫn phải truy vấn các sự kiện định kỳ mỗi khi ai đó nhìn vào một khoảng thời gian mới chưa từng được xem trước đó trên lịch, nhưng nếu không thì điều này khá sạch sẽ.


2

Bạn có thể lưu trữ các sự kiện như lặp lại và nếu một trường hợp cụ thể được chỉnh sửa, hãy tạo một sự kiện mới có cùng ID sự kiện. Sau đó, khi tìm kiếm sự kiện, tìm kiếm tất cả các sự kiện có cùng ID sự kiện để có được tất cả thông tin. Tôi không chắc chắn nếu bạn cuộn thư viện sự kiện của riêng bạn, hoặc nếu bạn đang sử dụng một thư viện hiện có để có thể không thể.


Tôi đã sử dụng giải pháp này một lần. Tôi thích nguyên tắc lưu trữ một thể hiện sửa đổi như một sự kiện một lần mới để biết mẹ của nó là ai. Bằng cách đó, bạn có thể để trống tất cả các trường ngoại trừ các trường khác nhau cho sự kiện con. Lưu ý rằng bạn sẽ phải có thêm một trường xác định con nào của người mẹ này bạn đang chỉnh sửa.
Wytze


1

Trong javascript:

Xử lý lịch định kỳ: http://bunkat.github.io/later/

Xử lý các sự kiện và phụ thuộc phức tạp giữa các lịch trình đó: http://bunkat.github.io/schedule/

Về cơ bản, bạn tạo các quy tắc sau đó bạn yêu cầu lib tính toán N sự kiện định kỳ tiếp theo (chỉ định phạm vi ngày hay không). Các quy tắc có thể được phân tích cú pháp / tuần tự để lưu chúng vào mô hình của bạn.

Nếu bạn có một sự kiện định kỳ và chỉ muốn sửa đổi một lần lặp lại, bạn có thể sử dụng hàm trừ () để loại bỏ một ngày cụ thể và sau đó thêm một sự kiện được sửa đổi mới cho mục này.

Các lib hỗ trợ các mẫu rất phức tạp, múi giờ và thậm chí các sự kiện định kỳ.


0

Lưu trữ các sự kiện dưới dạng lặp lại và hiển thị động chúng, tuy nhiên cho phép sự kiện định kỳ chứa danh sách các sự kiện cụ thể có thể ghi đè thông tin mặc định vào một ngày cụ thể.

Khi bạn truy vấn sự kiện định kỳ, nó có thể kiểm tra ghi đè cụ thể cho ngày hôm đó.

Nếu người dùng thực hiện thay đổi, thì bạn có thể hỏi liệu anh ta có muốn cập nhật cho tất cả các trường hợp (chi tiết mặc định) hoặc chỉ trong ngày hôm đó (tạo một sự kiện cụ thể mới và thêm nó vào danh sách).

Nếu người dùng yêu cầu xóa tất cả các đợt tái diễn của sự kiện này, bạn cũng có danh sách cụ thể để xử lý và có thể xóa chúng dễ dàng.

Trường hợp duy nhất có vấn đề sẽ là nếu người dùng muốn cập nhật sự kiện này và tất cả các sự kiện trong tương lai. Trong trường hợp đó, bạn sẽ phải chia sự kiện định kỳ thành hai. Tại thời điểm này, bạn có thể muốn xem xét liên kết các sự kiện định kỳ theo một cách nào đó để bạn có thể xóa tất cả chúng.


0

Đối với các lập trình viên .NET đã sẵn sàng trả một số phí cấp phép, bạn có thể thấy Aspose.Network hữu ích ... nó bao gồm một thư viện tương thích iCalWiki cho các cuộc hẹn định kỳ.


0

Bạn lưu trữ trực tiếp các sự kiện ở định dạng iCalWiki, cho phép lặp lại kết thúc mở, nội địa hóa múi giờ và vv.

Bạn có thể lưu trữ chúng trong máy chủ CalDAV và sau đó khi bạn muốn hiển thị các sự kiện, bạn có thể sử dụng tùy chọn báo cáo được xác định trong CalDAV để yêu cầu máy chủ thực hiện việc mở rộng các sự kiện định kỳ trong khoảng thời gian đã xem.

Hoặc bạn có thể tự lưu trữ chúng trong cơ sở dữ liệu và sử dụng một số loại thư viện phân tích cú pháp iCalWiki để thực hiện việc mở rộng mà không cần PUT / GET / BÁO CÁO để nói chuyện với máy chủ CalDAV phụ trợ. Đây có thể là công việc nhiều hơn - Tôi chắc chắn rằng các máy chủ CalDAV ẩn sự phức tạp ở đâu đó.

Có các sự kiện ở định dạng iCalWiki có thể sẽ giúp mọi thứ đơn giản hơn về lâu dài vì mọi người sẽ luôn muốn chúng được xuất để đưa vào phần mềm khác.


0

Tôi chỉ đơn giản là thực hiện tính năng này! Logic như sau, đầu tiên bạn cần hai bảng. RuleTable lưu trữ chung hoặc tái chế các sự kiện gia đình. ItemTable được lưu trữ các sự kiện chu kỳ. Ví dụ: khi bạn tạo một sự kiện theo chu kỳ, thời gian bắt đầu vào ngày 6 tháng 11 năm 2015, thời gian kết thúc cho ngày 6 tháng 12 (hoặc mãi mãi), chu kỳ trong một tuần. Bạn chèn dữ liệu vào RuleTable, các trường như sau:

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.

Bây giờ bạn muốn truy vấn dữ liệu ngày 20 tháng 11 đến ngày 20 tháng 12. Bạn có thể viết một hàm RecurringEventBE (bắt đầu dài, kết thúc dài), dựa trên thời gian bắt đầu và kết thúc, WeekLy, bạn có thể tính toán bộ sưu tập bạn muốn, <chu kỳA11.20, chu kỳA 11,27, chu kỳ 12,4 ......>. Ngoài ngày 6 tháng 11, và phần còn lại tôi gọi anh ta là một sự kiện ảo. Khi người dùng thay đổi tên của một sự kiện ảo sau (chu kỳ chẳng hạn 27,27), bạn chèn dữ liệu vào ItemTable. Các lĩnh vực như sau:

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

Trong chức năng RecurringEventBE (bắt đầu dài, kết thúc dài), bạn sử dụng dữ liệu này bao gồm sự kiện ảo (chu kỳB11.27) xin lỗi về tiếng Anh của tôi, tôi đã thử.

Đây là định kỳ của tôi

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

Điều gì nếu bạn có một cuộc hẹn định kỳ không có ngày kết thúc? Rẻ như không gian, bạn không có không gian vô hạn, vì vậy Giải pháp 2 là không bắt đầu ở đó ...

Tôi có thể đề nghị rằng "không có ngày kết thúc" có thể được giải quyết đến ngày kết thúc vào cuối thế kỷ. Ngay cả đối với một sự kiện hàng ngày, số lượng không gian vẫn còn rẻ.


7
Làm thế nào sớm chúng ta quên những bài học của y2k ... :)
Ian Mercer

10
Giả sử chúng ta có 1000 người dùng, mỗi người có một vài sự kiện hàng ngày. 3 sự kiện × 1000 người dùng × 365 ngày × (2100-2011 = 89 năm) = 97,5 triệu hồ sơ. Thay vì 3000 "kế hoạch". Ừm ...
Andy Mikhaylenko
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.