001/* 002 * Copyright 2015 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.workspaces.calendars.helper; 017 018import java.time.LocalDate; 019import java.time.ZoneId; 020import java.time.ZonedDateTime; 021import java.time.temporal.ChronoUnit; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.List; 025 026import org.ametys.plugins.messagingconnector.EventRecurrenceTypeEnum; 027import org.ametys.runtime.config.Config; 028 029/** 030 * Helper for recurrent event operation 031 */ 032public final class RecurrentEventHelper 033{ 034 private RecurrentEventHelper() 035 { 036 // Helper class 037 } 038 039 /** 040 * Get the next occurrence date 041 * @param recurrenceType recurrence type 042 * @param eventStartDate the event start date 043 * @param date the occurrence date 044 * @return the next occurrence date 045 */ 046 public static ZonedDateTime getNextDate(EventRecurrenceTypeEnum recurrenceType, ZonedDateTime eventStartDate, ZonedDateTime date) 047 { 048 ZonedDateTime nextDate = null; 049 switch (recurrenceType) 050 { 051 case ALL_DAY: 052 nextDate = _nextAllDayDate(date); 053 break; 054 case ALL_WORKING_DAY: 055 nextDate = _nextAllWorkingDayDate(date); 056 break; 057 case WEEKLY: 058 nextDate = _nextWeeklyDate(date); 059 break; 060 case BIWEEKLY: 061 nextDate = _nextBiweeklyDate(date); 062 break; 063 case MONTHLY: 064 nextDate = _nextMonthlyDate(date, eventStartDate); 065 break; 066 default: 067 break; 068 } 069 070 return nextDate; 071 } 072 073 private static ZonedDateTime _nextAllDayDate(ZonedDateTime date) 074 { 075 return date.plusDays(1); 076 } 077 078 /** 079 * get the list of working days 080 * @return a list of integer, matching {@link java.util.Calendar} days 081 */ 082 public static List<Integer> getWorkingDays() 083 { 084 List<Integer> result = new ArrayList<>(); 085 String workingDayAsString = Config.getInstance().getValue("workspaces.calendar.event.working.day"); 086 List<String> workingDaysStrings = Arrays.asList(workingDayAsString.split(",")); 087 for (String workkingDayString : workingDaysStrings) 088 { 089 result.add(Integer.valueOf(workkingDayString)); 090 } 091 return result; 092 } 093 094 private static ZonedDateTime _nextAllWorkingDayDate(ZonedDateTime date) 095 { 096 String workingDayAsString = Config.getInstance().getValue("workspaces.calendar.event.working.day"); 097 098 int dayId = date.getDayOfWeek().getValue() + 1; 099 100 int nbDay = 1; 101 int nextDayId = (dayId % 7) + 1; 102 List<String> workingDay = Arrays.asList(workingDayAsString.split(",")); 103 while (!workingDay.contains(String.valueOf(nextDayId))) 104 { 105 nextDayId = (nextDayId % 7) + 1; 106 nbDay++; 107 } 108 109 return date.plusDays(nbDay); 110 } 111 112 private static ZonedDateTime _nextWeeklyDate(ZonedDateTime date) 113 { 114 return date.plusWeeks(1); 115 } 116 117 private static ZonedDateTime _nextBiweeklyDate(ZonedDateTime date) 118 { 119 return date.plusWeeks(2); 120 } 121 122 private static ZonedDateTime _nextMonthlyDate(ZonedDateTime date, ZonedDateTime eventStartDate) 123 { 124 int dayOfMonth = eventStartDate.getDayOfMonth(); 125 126 ZonedDateTime nextDate = date.plusMonths(1); 127 int nextDayOfMonth = nextDate.getDayOfMonth(); 128 LocalDate nextDateAsLocal = nextDate.toLocalDate(); 129 int nextDayMaxOfMonth = nextDateAsLocal.lengthOfMonth(); 130 131 if (nextDayOfMonth < dayOfMonth) 132 { 133 if (dayOfMonth < nextDayMaxOfMonth) 134 { 135 nextDate = nextDate.withDayOfMonth(dayOfMonth); 136 } 137 else 138 { 139 nextDate = nextDate.withDayOfMonth(nextDayMaxOfMonth); 140 } 141 } 142 143 return nextDate; 144 } 145 146 /** 147 * Compute the occurrence of an event 148 * @param startDate the start of the range to compute occurrences 149 * @param endDate the end of the range to compute occurrences 150 * @param eventStartDate the start date of the occurrence 151 * @param originalOccurrenceStartDate the original start date of the occurrence (different as the eventStartDate when editing an occurrence) 152 * @param recurrenceType the recurrence type 153 * @param excludedOccurences the occurrences to exclude 154 * @param zoneId the zoneId used for the dates 155 * @param untilDate until date of the recurring event 156 * @return a list of occurrence start dates 157 */ 158 public static List<ZonedDateTime> getOccurrences(ZonedDateTime startDate, ZonedDateTime endDate, ZonedDateTime eventStartDate, ZonedDateTime originalOccurrenceStartDate, EventRecurrenceTypeEnum recurrenceType, List<ZonedDateTime> excludedOccurences, ZoneId zoneId, ZonedDateTime untilDate) 159 { 160 long diffInSeconds = ChronoUnit.SECONDS.between(originalOccurrenceStartDate, eventStartDate); 161 List<ZonedDateTime> occurences = new ArrayList<>(); 162 163 ZonedDateTime firstDate = startDate.equals(eventStartDate) ? startDate : startDate.plusSeconds(diffInSeconds); 164 firstDate = firstDate.withZoneSameInstant(zoneId); 165 166 if (firstDate.isAfter(endDate)) 167 { 168 return occurences; 169 } 170 171 ZonedDateTime firstDateCalendar = firstDate.withZoneSameInstant(zoneId).truncatedTo(ChronoUnit.DAYS); 172 173 if (excludedOccurences.stream().noneMatch(excludedOccurrence -> excludedOccurrence.isEqual(firstDateCalendar))) 174 { 175 occurences.add(firstDate); 176 } 177 178 ZonedDateTime nextDate = getNextDate(recurrenceType, eventStartDate.withZoneSameInstant(zoneId), firstDate.withZoneSameInstant(zoneId)); 179 while (nextDate != null && nextDate.isBefore(endDate) && (untilDate == null || untilDate.isAfter(nextDate))) 180 { 181 ZonedDateTime nextDateCalendar = nextDate.truncatedTo(ChronoUnit.DAYS); 182 183 if (excludedOccurences.stream().noneMatch(excludedOccurrence -> excludedOccurrence.isEqual(nextDateCalendar))) 184 { 185 occurences.add(nextDate); 186 } 187 nextDate = getNextDate(recurrenceType, eventStartDate.withZoneSameInstant(zoneId), nextDate.withZoneSameInstant(zoneId)); 188 } 189 190 return occurences; 191 } 192}