Повторяющаяся логика событий
Я работаю над приложением Groovy/Java calendar-type, которое позволяет пользователю вводить события с датой начала и необязательным повторением. Если это повторяющееся событие, оно может повторяться:
- ежемесячно в день месяца, который соответствует дате начала.
- еженедельно в день недели, соответствующем дате начала.
- каждые 2 недели в день недели, который соответствует дате начала.
- и др.
Я изначально планировал использовать API календаря Google для выполнения всей логики повторения, но это оказалось огромной PITA, по причинам, которые я буду обсуждать дальше, если кто-то заботится.
Итак, теперь я решил опрокинуть свое решение. Учитывая дату, я хочу выяснить, происходит ли повторное событие в эту дату. Моя логика (в псевдокоде) будет следующей:
public boolean occursOnDate(def date, def event) {
def firstDate = event.startDate
if (firstDate > date) {
return false;
} else if (event.isWeekly()) {
return event.dayOfWeek() == date.dayOfWeek()
} else if (event.isMonthly()) {
return event.dayOfMonth() == date.dayOfMonth()
} else {
// At this point we know the event occurs every X weeks where X > 1
// Increment firstDate by adding X weeks to it as many times as possible, without
// going past date
return firstDate == date
}
}
Эта логика кажется разумной, но на самом деле будет довольно много усилий для реализации, если вы рассмотрите все странные случаи кросс (например, как обрабатывать ежемесячное повторяющееся событие в феврале, первое появление которого - 31 января).
Есть ли библиотека, которая может помочь мне реализовать это? Некоторые особенности будут высоко оценены (например, ни один кредит не будет присуждаться за "Использовать время Джоды" ).
Спасибо,
Дон
Ответы
Ответ 1
Соответствующие правила повторения, которые вы хотите, достаточно хорошо указаны в RFC-2445 (в основном, спецификация iCal). Получение мелочей этого правильного может быть довольно привлекательным. Я бы предложил использовать библиотеку google-rfc-2445 для этого или другую реализацию этой спецификации, например iCal4J.
Ответ 2
Я ничего не знаю о Groovy, и моим первым предложением будет Джода, но вы знаете об этом.
Я знаю, что это может показаться излишним для вас и, возможно, даже неприменимо, но Quartz Scheduler отлично справляется со всеми этими правилами повторения и событиями, Вы не могли использовать свои возможности планирования и просто использовать классы Trigger (например, CronTrigger), чтобы рассчитать даты событий для вас.
В приведенной выше ссылке CronTrigger показаны некоторые примеры выражений, которые вы могли бы использовать для обработки ваших событий, например, эта особенно неприятная ситуация:
"0 0 12 L *?" - инициировать событие в середине дня каждый последний день месяца (без головных болей с високосными годами и т.д.)
Проблемы с летним временем также обрабатываются.
Что касается кода, создайте триггер с желаемым повторением, а затем вы можете извлечь все время стрельбы, которое вы хотите:
Date firstFireTime = myTrigger.getNextFireTime();
...
while (...) {
Date nextFireTime = myTrigger.getFireTimeAfter(previousFireTime);
...
}
Надеюсь, это может быть полезно.
Ответ 3
Я не очень хорошо знаком с библиотекой Groovy, но поскольку Groovy работает на JVM, мы предполагаем, что вы также сможете использовать библиотеку Java/Scala.
Здесь вам нужна профессиональная библиотека генерации расписания, такая как Lamma (http://lamma.io) вместо библиотеки времени времени общего назначения, такой как Joda.
// monthly on a date of the month that corresponds to the start date
// output: [Date(2014,6,10), Date(2014,7,10), Date(2014,8,10), Date(2014,9,10), Date(2014,10,10)]
System.out.println(Dates.from(2014, 6, 10).to(2014, 10, 10).byMonth().build());
// weekly on a day of the week of that corresponds to the start date
// output: [Date(2014,6,10), Date(2014,6,17), Date(2014,6,24), Date(2014,7,1), Date(2014,7,8)]
System.out.println(Dates.from(2014, 6, 10).to(2014, 7, 10).byWeek().build());
// every 2 weeks on a day of the week of that corresponds to the start date
// output: [Date(2014,6,10), Date(2014,6,24), Date(2014,7,8)]
System.out.println(Dates.from(2014, 6, 10).to(2014, 7, 10).byWeeks(2).build());
// edge cases are handled properly, for example, leap day
// output: [Date(2012,2,29), Date(2013,2,28), Date(2014,2,28), Date(2015,2,28), Date(2016,2,29)]
System.out.println(Dates.from(2012, 2, 29).to(2016, 2, 29).byYear().build());