Ruby 什么';在日历应用程序中对重复事件建模的最佳方法是什么?
我正在构建一个需要支持重复事件的组日历应用程序,但我提出的所有处理这些事件的解决方案似乎都是一个漏洞。我可以限制一个人向前看的距离,然后一次生成所有事件。或者,我可以将事件存储为重复事件,并在人们查看日历时动态显示它们,但如果有人想更改事件特定实例的详细信息,我必须将它们转换为正常事件 我肯定有更好的方法,但我还没有找到。您可以更改或删除特定事件实例的详细信息,对重复事件建模的最佳方法是什么Ruby 什么';在日历应用程序中对重复事件建模的最佳方法是什么?,ruby,algorithm,calendar,data-modeling,recurrence,Ruby,Algorithm,Calendar,Data Modeling,Recurrence,我正在构建一个需要支持重复事件的组日历应用程序,但我提出的所有处理这些事件的解决方案似乎都是一个漏洞。我可以限制一个人向前看的距离,然后一次生成所有事件。或者,我可以将事件存储为重复事件,并在人们查看日历时动态显示它们,但如果有人想更改事件特定实例的详细信息,我必须将它们转换为正常事件 我肯定有更好的方法,但我还没有找到。您可以更改或删除特定事件实例的详细信息,对重复事件建模的最佳方法是什么 (我使用的是Ruby,但请不要因此而限制您的回答。不过,如果有Ruby特定的库或其他东西,很高兴知道。)
(我使用的是Ruby,但请不要因此而限制您的回答。不过,如果有Ruby特定的库或其他东西,很高兴知道。)您可能想看看iCalendar软件实现或标准本身(RFC 2445)。 人们很快就会想到Mozilla项目,快速搜索也会发现这些项目
根据存储事件的方式,可以考虑其他选项。您正在构建自己的数据库模式吗?使用基于iCalendar等的工具?可以将事件存储为重复事件,如果编辑了特定实例,则创建具有相同事件ID的新事件。然后在查找事件时,搜索具有相同事件ID的所有事件以获取所有信息。我不确定您是否使用了自己的事件库,或者是否正在使用现有的事件库,因此可能无法使用。我将对所有未来的重复事件使用“链接”概念。它们在日历中动态显示,并链接回单个引用对象。当事件发生时,链接断开,事件成为独立实例。如果试图编辑重复事件,则提示更改所有未来项目(即更改单链接引用)或仅更改该实例(在这种情况下,将其转换为独立实例,然后进行更改)。后一种情况有点问题,因为您需要在已转换为单个实例的所有未来事件的重复列表中进行跟踪。但是,这是完全可以做到的
因此,本质上有两类事件-单个实例和重复事件。将事件存储为重复事件并动态显示它们,但允许重复事件包含特定事件的列表,这些事件可以覆盖特定日期的默认信息 当您查询定期事件时,它可以检查当天的特定覆盖 如果用户进行了更改,那么您可以询问他是希望更新所有实例(默认详细信息)还是仅在当天更新(创建新的特定事件并将其添加到列表中) 如果用户要求删除此事件的所有重复事件,您还可以获得详细信息列表,并可以轻松删除它们 唯一有问题的情况是,如果用户希望更新此事件和所有未来事件。在这种情况下,您必须将重复发生的事件分成两部分。在这一点上,您可能想考虑以某种方式链接循环事件,以便可以删除它们。
希望有帮助 我建议使用日期库的强大功能和ruby范围模块的语义。重复事件实际上是一个时间、一个日期范围(开始和结束),通常是一周中的一天。使用日期和范围,您可以回答任何问题:
#!/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\"]"
从这些答案中,我筛选出了一个解决方案。我真的很喜欢链接概念的想法。重复发生的事件可以是一个链表,尾部知道它的重复发生规则。更改一个事件将很容易,因为链接保持不变,删除事件也很容易-您只需取消链接事件,删除它,然后在事件前后重新链接事件。每次有人查看日历上以前从未查看过的新时间段时,您仍然必须查询重复事件,但除此之外,这是非常干净的。对于准备支付一些许可费的.NET程序员,您可能会发现有用的。。。它包括一个与iCalendar兼容的库,用于定期约会。定期事件可能存在许多问题,让我重点介绍一些我知道的问题 解决方案1-无实例 存储原始约会+定期数据,不要存储所有实例 问题:
- 在需要时,您必须计算日期窗口中的所有实例,代价高昂
- 无法处理异常(即,您删除或移动其中一个实例,或者更确切地说,您无法使用此解决方案执行此操作)
- 占用大量空间(但空间很便宜,所以很小)
- 例外情况必须妥善处理,特别是在例外情况发生后返回并编辑原始约会时。例如,如果将第三个实例向前移动一天,如果返回并编辑
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.
TableID: 1 Name, cycleB StartTime, 27 November 2014 EndTime,November 6 2015 Cycletype, WeekLy Foreignkey, 1 (pointingto the table recycle paternal events).
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); //RuleTable,just 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; }