Apa cara terbaik untuk memodelkan acara berulang dalam aplikasi kalender?

225

Saya sedang membangun aplikasi kalender grup yang perlu mendukung acara yang berulang, tetapi semua solusi yang saya buat untuk menangani acara ini tampak seperti peretasan. Saya dapat membatasi seberapa jauh ke depan seseorang dapat melihat, dan kemudian menghasilkan semua acara sekaligus. Atau saya dapat menyimpan acara sebagai berulang dan menampilkannya secara dinamis ketika seseorang melihat ke depan pada kalender, tetapi saya harus mengubahnya menjadi acara normal jika seseorang ingin mengubah detail pada contoh acara tertentu.

Saya yakin ada cara yang lebih baik untuk melakukan ini, tetapi saya belum menemukannya. Apa cara terbaik untuk memodelkan acara berulang, di mana Anda dapat mengubah detail atau menghapus kejadian acara tertentu?

(Saya menggunakan Ruby, tapi tolong jangan biarkan itu membatasi jawaban Anda. Jika ada perpustakaan khusus-Ruby atau sesuatu, itu baik untuk diketahui.)

Clinton N. Dreisbach
sumber

Jawaban:

93

Saya akan menggunakan konsep 'tautan' untuk semua acara berulang di masa mendatang. Mereka ditampilkan secara dinamis di kalender dan tautan kembali ke objek referensi tunggal. Ketika peristiwa telah terjadi, tautan terputus dan acara tersebut menjadi contoh tersendiri. Jika Anda mencoba untuk mengedit acara berulang maka meminta untuk mengubah semua item masa depan (yaitu mengubah referensi tunggal yang ditautkan) atau mengubah hanya contoh itu (dalam hal ini mengubahnya menjadi contoh mandiri dan kemudian membuat perubahan). Cased yang terakhir sedikit bermasalah karena Anda perlu melacak daftar berulang semua peristiwa di masa depan yang dikonversi menjadi satu kejadian. Tapi, ini sepenuhnya bisa dilakukan.

Jadi, pada dasarnya, memiliki 2 kelas acara - kejadian tunggal dan acara berulang.

pengguna16068
sumber
Sangat menyukai ide Anda untuk menautkan dan mengonversi acara menjadi mandiri setelah acara berlalu. Dua pertanyaan: - Mengapa mengubahnya menjadi contoh yang berdiri sendiri? Mengapa tidak membiarkannya sepenuhnya dinamis? - Dapatkah Anda membagikan referensi untuk konsep tautan yang diusulkan! Terima kasih sebelumnya!
rtindru
@rtindru use case yang saya temukan untuk mengonversi acara menjadi mandiri adalah ketika Anda harus menggunakan model acara dengan model lain di database Anda. Misalnya untuk memeriksa kehadiran untuk suatu acara, Anda ingin mengaitkan pengguna dengan peristiwa nyata yang terjadi (atau akan terjadi).
Clinton Yeboah
60

Martin Fowler - Acara Berulang untuk Kalender berisi beberapa wawasan dan pola yang menarik.

Permata keriting menerapkan pola ini.

Daniel Maurić
sumber
Beberapa contoh kode yang lebih baik akan menyenangkan, ada yang tahu proyek yang mengimplementasikan ini?
Tadeu Maia
33

Mungkin ada banyak masalah dengan acara yang berulang, izinkan saya menyoroti beberapa yang saya ketahui.

Solusi 1 - tidak ada contoh

Menyimpan data janji + pengulangan asli, jangan menyimpan semua instance.

Masalah:

  • Anda harus menghitung semua instance di jendela tanggal ketika Anda membutuhkannya, mahal
  • Tidak dapat menangani pengecualian (mis. Anda menghapus salah satu instance, atau memindahkannya, atau lebih tepatnya, Anda tidak dapat melakukan ini dengan solusi ini)

Solusi 2 - instans toko

Simpan semuanya mulai dari 1, tetapi juga semua instance, ditautkan kembali ke janji temu yang asli.

Masalah:

  • Membutuhkan banyak ruang (tetapi ruang murah, sangat kecil)
  • Pengecualian harus ditangani dengan anggun, terutama jika Anda kembali dan mengedit janji temu setelah membuat pengecualian. Misalnya, jika Anda memindahkan instance ketiga satu hari ke depan, bagaimana jika Anda kembali dan mengedit waktu janji temu yang asli, memasukkan kembali yang lain pada hari yang asli dan meninggalkan yang dipindahkan? Putuskan tautan yang dipindahkan? Coba ubah yang dipindahkan dengan tepat?

Tentu saja, jika Anda tidak akan melakukan pengecualian, maka salah satu solusi harus baik-baik saja, dan Anda pada dasarnya memilih dari skenario pertukaran waktu / ruang.

Lasse V. Karlsen
sumber
36
Bagaimana jika Anda memiliki janji temu berulang tanpa tanggal akhir? Semurah ruang, Anda tidak memiliki ruang terbatas, sehingga Solusi 2 adalah non-starter ada ...
Shaul Behr
13
Solusi # 1 sebenarnya dapat menangani pengecualian. Misalnya, RFC5545 menyarankan agar mereka disimpan sebagai: a) daftar tanggal yang dikecualikan (ketika Anda menghapus suatu kejadian); b) kejadian "terwujud" dengan referensi ke prototipe (ketika Anda memindahkan suatu kejadian).
Andy Mikhaylenko
@Andy, beberapa tambahan menarik untuk jawaban Lasse. Akan mencobanya.
Jonathan Wilson
1
@ Saul: Saya tidak berpikir itu bukan starter. John Skeet, yang cukup dihormati di SO, menyarankan untuk menyimpan instance yang dihasilkan dalam jawabannya untuk pertanyaan yang sama: stackoverflow.com/a/10151804/155268
Pengguna
1
@ Pengguna - diakui, terima kasih. Ini sangat aneh - saya membuat komentar saya lebih dari 4 tahun yang lalu, dan saya benar-benar tidak perlu berurusan dengan masalah ini sejak itu. Baru kemarin saya merancang modul baru yang melibatkan janji temu berulang, dan saya bertanya-tanya bagaimana cara menanganinya. Dan kemudian - Saya mendapat pemberitahuan SO atas komentar Anda pagi ini. Serius seram! Tapi terima kasih! :-)
Shaul Behr
21

Saya telah mengembangkan beberapa aplikasi berbasis kalender, dan juga menulis satu set komponen kalender JavaScript yang dapat digunakan kembali yang mendukung pengulangan. Saya menulis tinjauan umum tentang bagaimana merancang untuk pengulangan yang mungkin bermanfaat bagi seseorang. Meskipun ada beberapa bit yang khusus untuk perpustakaan yang saya tulis, sebagian besar saran yang ditawarkan bersifat umum untuk setiap implementasi kalender.

Beberapa poin kunci:

  • Simpan perulangan menggunakan format iCal RRULE - itu satu roda yang Anda benar-benar tidak ingin kembalikan
  • JANGAN menyimpan instance acara berulang individual sebagai baris di basis data Anda! Selalu simpan pola perulangan.
  • Ada banyak cara untuk merancang skema acara / pengecualian Anda, tetapi contoh titik awal dasar disediakan
  • Semua nilai tanggal / waktu harus disimpan dalam UTC dan dikonversi ke lokal untuk tampilan
  • Tanggal akhir yang disimpan untuk acara berulang harus selalu menjadi tanggal akhir rentang perulangan (atau "tanggal maks" platform Anda jika berulang "selamanya") dan durasi acara harus disimpan secara terpisah. Ini untuk memastikan cara yang wajar untuk menanyakan acara nanti.
  • Beberapa diskusi seputar pembuatan instance acara dan strategi pengulangan perulangan disertakan

Ini adalah topik yang sangat rumit dengan banyak, banyak pendekatan yang valid untuk mengimplementasikannya. Saya akan mengatakan bahwa saya telah benar-benar menerapkan pengulangan beberapa kali dengan sukses, dan saya akan berhati-hati dalam mengambil saran mengenai hal ini dari siapa pun yang belum benar-benar melakukannya.

Brian Moeskau
sumber
Mungkin menyimpan perulangan sebagai peristiwa ketika itu terjadi sehingga riwayat kalender Anda akurat
Richard Haven
@ RichardHaven saya tidak akan pernah melakukan itu. Anda harus selalu menghasilkan instance dari pola RRULE secara konsisten, masa lalu, sekarang atau masa depan. Tidak akan ada alasan untuk melakukan sesuatu yang berbeda untuk peristiwa sejarah. Logika Anda harus mengevaluasi RRULE terhadap rentang tanggal sembarang dan mengembalikan instance acara yang cocok.
Brian Moeskau
@BrianMoeskau ikhtisar yang bagus dan bermanfaat!
Przemek Nowak
@BrianMoeskau Tetapi apakah pandangan masa lalu dari kalender Anda tidak akan menampilkan informasi yang tidak akurat ketika seseorang mengedit RRULE setelah beberapa kejadian sudah terjadi? Atau mungkin dalam kasus itu Anda akan "bercabang" RRULE dan menyimpan versi modifikasi dari pola RRULE yang mewakili persis kejadian masa lalu yang sebenarnya?
christian
1
@christian Ketika Anda memperbarui aturan perulangan di sebagian besar kalender, mereka biasanya meminta seperti "edit semua acara, atau hanya ini, atau hanya di masa mendatang" yang memungkinkan pengguna memilih perilaku. Dalam kebanyakan kasus, pengguna mungkin berarti "mengubahnya ke depan" tetapi sekali lagi, terserah Anda untuk memutuskan bagaimana perangkat lunak Anda bekerja dan opsi apa yang Anda berikan kepada pengguna.
Brian Moeskau
19

Anda mungkin ingin melihat implementasi perangkat lunak iCalendar atau standarnya sendiri ( RFC 2445 RFC 5545 ). Satu- satunya yang terlintas dalam pikiran adalah proyek Mozilla http://www.mozilla.org/projects/calendar/ Sebuah pencarian cepat mengungkapkan http://icalendar.rubyforge.org/ juga.

Opsi lain dapat dipertimbangkan tergantung pada bagaimana Anda akan menyimpan acara tersebut. Apakah Anda membangun skema basis data Anda sendiri? Menggunakan sesuatu berbasis iCalendar, dll?

Kris Kumler
sumber
jika Anda bisa memberikan tautan ke salah satu dari ini, pos Anda akan sempurna
Jean
7
Sepertinya RFC2445 telah usang oleh RFC5545 ( tools.ietf.org/html/rfc5545 )
Eric Freese
16

Saya bekerja dengan yang berikut ini:

dan permata dalam proses yang memperluas formtastic dengan tipe input: recurring ( form.schedule :as => :recurring), yang membuat antarmuka seperti iCal dan before_filteruntuk membuat serialisasi tampilan menjadi IceCubeobjek lagi, ghetto-ly.

Ide saya adalah membuatnya mudah untuk menambahkan atribut berulang ke model dan menghubungkannya dengan mudah dalam tampilan. Semua dalam beberapa baris.


Jadi apa yang saya berikan? Atribut yang diindeks, dapat diedit, berulang.

eventstoko satu hari misalnya, dan digunakan dalam tampilan kalender / penolong mengatakan task.schedulemenyimpan yaml'd IceCubeobjek, sehingga Anda dapat melakukan panggilan seperti: task.schedule.next_suggestion.

Rekap: Saya menggunakan dua model, satu flat, untuk tampilan kalender, dan satu atribut untuk fungsi.

Vee
sumber
Saya akan tertarik melihat apa yang Anda pikirkan. Apakah Anda memiliki git / blog / bukti konsep di mana saja? Terima kasih!
montrealmike
Saya sedang mengerjakan sesuatu yang serupa juga.
Senang
5
  1. Melacak aturan perulangan (mungkin berdasarkan iCalendar, per @ Kris K. ). Ini akan mencakup pola dan kisaran (Setiap Selasa ketiga, untuk 10 kejadian).
  2. Untuk saat Anda ingin mengedit / menghapus kejadian tertentu, catat tanggal pengecualian untuk aturan perulangan di atas (tanggal di mana acara tidak terjadi sesuai aturan yang ditentukan).
  3. Jika Anda menghapus, itu saja yang Anda butuhkan, jika Anda diedit, buat acara lain, dan berikan ID induknya ke acara utama. Anda dapat memilih apakah akan memasukkan semua informasi acara utama dalam catatan ini, atau jika hanya menyimpan perubahan dan mewarisi segala sesuatu yang tidak berubah.

Perhatikan bahwa jika Anda mengizinkan aturan perulangan yang tidak berakhir, Anda harus memikirkan cara menampilkan informasi Anda yang sekarang jumlahnya tak terbatas.

Semoga itu bisa membantu!

bdukes
sumber
4

Saya akan merekomendasikan menggunakan kekuatan perpustakaan tanggal dan semantik dari berbagai modul ruby. Peristiwa berulang benar-benar waktu, rentang tanggal (awal & akhir) dan biasanya satu hari dalam seminggu. Menggunakan tanggal & rentang Anda dapat menjawab pertanyaan apa pun:

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

Hasilkan semua hari acara, termasuk tahun kabisat!

# =>"[\"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
sumber
2
Ini tidak terlalu fleksibel. Model peristiwa yang berulang sering membutuhkan penetapan periode pengulangan (jam, mingguan, dua minggu, dll). Selain itu pengulangan mungkin tidak memenuhi syarat dengan jumlah total, melainkan tanggal akhir untuk kejadian terakhir
Bo Jeanes
"Peristiwa berulang adalah [..] biasanya satu hari dalam seminggu", ini hanya satu kasus penggunaan terbatas dan tidak menangani banyak hal lain seperti 'Hari ke-5 setiap bulan "dll.
theraven
3

Dari jawaban ini, saya semacam menyortir solusi. Saya sangat menyukai ide konsep tautan. Peristiwa berulang bisa menjadi daftar terkait, dengan ekor mengetahui aturan perulangannya. Mengubah satu peristiwa akan menjadi mudah, karena tautannya tetap di tempatnya, dan menghapus acara juga mudah - Anda cukup memutuskan tautan suatu acara, menghapusnya, dan menautkan kembali acara tersebut sebelum dan sesudahnya. Anda masih harus menanyakan acara berulang setiap kali seseorang melihat periode waktu baru yang belum pernah dilihat sebelumnya di kalender, tetapi jika tidak, ini cukup bersih.

Clinton N. Dreisbach
sumber
2

Anda bisa menyimpan acara sebagai berulang, dan jika instance tertentu diedit, buat acara baru dengan ID acara yang sama. Kemudian saat mencari acara, cari semua acara dengan ID acara yang sama untuk mendapatkan semua informasi. Saya tidak yakin apakah Anda menggulung perpustakaan acara Anda sendiri, atau jika Anda menggunakan yang sudah ada sehingga tidak mungkin.

Vincent McNabb
sumber
Saya menggunakan solusi ini sekali. Saya suka prinsip menyimpan contoh yang dimodifikasi sebagai acara baru yang tahu siapa mamanya. Dengan begitu Anda bisa membiarkan semua bidang kosong kecuali yang berbeda untuk acara anak. Perhatikan bahwa Anda harus memiliki bidang tambahan yang menentukan anak ibu mana yang Anda edit.
Wytze
1

Dalam javascript:

Menangani jadwal berulang: http://bunkat.github.io/later/

Menangani acara dan ketergantungan yang kompleks antara jadwal tersebut: http://bunkat.github.io/schedule/

Pada dasarnya, Anda membuat aturan maka Anda meminta lib untuk menghitung peristiwa berulang N berikutnya (menentukan rentang tanggal atau tidak). Aturan dapat diuraikan / diserialisasi untuk menyimpannya ke dalam model Anda.

Jika Anda memiliki acara berulang dan hanya ingin memodifikasi satu pengulangan, Anda dapat menggunakan fungsi exception () untuk mengabaikan hari tertentu dan kemudian menambahkan acara yang dimodifikasi baru untuk entri ini.

Lib mendukung pola yang sangat kompleks, zona waktu, dan bahkan peristiwa kroning.

Flavien Volken
sumber
0

Menyimpan acara sebagai berulang dan menampilkannya secara dinamis, namun memungkinkan acara berulang untuk berisi daftar acara tertentu yang dapat menimpa informasi default pada hari tertentu.

Saat Anda menanyakan acara berulang, ia dapat memeriksa penimpaan spesifik untuk hari itu.

Jika pengguna melakukan perubahan, maka Anda dapat bertanya apakah dia ingin memperbarui untuk semua instance (detail default) atau hanya hari itu (membuat acara spesifik baru dan menambahkannya ke daftar).

Jika seorang pengguna meminta untuk menghapus semua perulangan dari acara ini Anda juga memiliki daftar spesifik untuk diberikan dan dapat menghapusnya dengan mudah.

Satu-satunya kasus yang bermasalah adalah jika pengguna ingin memperbarui acara ini dan semua acara mendatang. Dalam hal ini Anda harus membagi acara berulang menjadi dua. Pada titik ini Anda mungkin ingin mempertimbangkan menautkan acara yang berulang dengan cara tertentu sehingga Anda dapat menghapus semuanya.

Andrew Johnson
sumber
0

Untuk .NET programmer yang siap membayar beberapa biaya lisensi, Anda mungkin menemukan Aspose.Network bermanfaat ... ini mencakup perpustakaan yang kompatibel dengan iCalendar untuk janji temu berulang.

Shaul Behr
sumber
0

Anda menyimpan acara dalam format iCalendar secara langsung, yang memungkinkan untuk pengulangan terbuka, lokalisasi zona waktu dan sebagainya.

Anda bisa menyimpan ini di server CalDAV dan kemudian ketika Anda ingin menampilkan acara, Anda bisa menggunakan opsi laporan yang ditentukan di CalDAV untuk meminta server melakukan perluasan acara berulang di seluruh periode yang dilihat.

Atau Anda dapat menyimpannya dalam database sendiri dan menggunakan semacam parsing pustaka iCalendar untuk melakukan ekspansi, tanpa perlu PUT / GET / LAPORAN untuk berbicara dengan server CalDAV backend. Ini mungkin lebih banyak pekerjaan - saya yakin server CalDAV menyembunyikan kompleksitas di suatu tempat.

Memiliki acara dalam format iCalendar mungkin akan membuat hal-hal yang lebih sederhana dalam jangka panjang karena orang akan selalu ingin mereka diekspor untuk dimasukkan ke perangkat lunak lain.

karora
sumber
0

Saya telah Cukup menerapkan fitur ini! Logikanya adalah sebagai berikut, pertama Anda perlu dua tabel. Toko RuleTable umum atau mendaur ulang acara ayah. ItemTable adalah peristiwa siklus yang disimpan. Misalnya, ketika Anda membuat acara siklik, waktu mulai untuk 6 November 2015, waktu akhir untuk 6 Desember (atau selamanya), siklus selama satu minggu. Anda memasukkan data ke dalam RuleTable, bidang adalah sebagai berikut:

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.

Sekarang Anda ingin menanyakan data 20 November hingga 20 Desember. Anda dapat menulis fungsi RecurringEventBE (awal, akhir), berdasarkan waktu mulai dan berakhir, WeekLy, Anda dapat menghitung koleksi yang Anda inginkan, <cycleA11.20, cycleA 11.27, cycleA 12.4 ......>. Selain 6 November, dan sisanya saya memanggilnya acara virtual. Ketika pengguna mengubah nama peristiwa virtual 'setelah (cycleA11.27 misalnya), Anda memasukkan data ke dalam ItemTable. Fields adalah sebagai berikut:

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

Dalam fungsi RecurringEventBE (awal, akhir), Anda menggunakan data ini yang meliputi peristiwa virtual (cycleB11.27) maaf tentang bahasa Inggris saya, saya mencoba.

Ini adalah RecurringEventBE saya :

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
sumber
-5

Bagaimana jika Anda memiliki janji temu berulang tanpa tanggal akhir? Semurah ruang, Anda tidak memiliki ruang tanpa batas, jadi Solusi 2 adalah non-starter di sana ...

Bolehkah saya menyarankan bahwa "tidak ada tanggal akhir" dapat diselesaikan ke tanggal akhir pada akhir abad ini. Bahkan untuk acara harian jumlah ruang tetap murah.

poumtatalia
sumber
7
Betapa cepatnya kita melupakan pelajaran y2k ... :)
Ian Mercer
10
Anggaplah kita memiliki 1000 pengguna, masing-masing dengan beberapa acara harian. 3 peristiwa × 1000 pengguna × 365 hari × (2100-2011 = 89 tahun) = 97,5 juta catatan. Alih-alih 3000 "rencana". Um ...
Andy Mikhaylenko