001/* 002 * Copyright 2012 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.calendar.events; 017 018import java.time.LocalDate; 019import java.time.ZoneId; 020import java.time.ZonedDateTime; 021import java.time.format.DateTimeFormatter; 022import java.util.Date; 023 024import org.apache.avalon.framework.logger.LogEnabled; 025import org.apache.avalon.framework.logger.Logger; 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.avalon.framework.service.Serviceable; 029import org.apache.commons.lang.StringUtils; 030 031import org.ametys.cms.contenttype.ContentType; 032import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 033import org.ametys.cms.repository.Content; 034import org.ametys.core.util.DateUtils; 035import org.ametys.plugins.repository.AmetysObjectResolver; 036import org.ametys.runtime.model.ElementDefinition; 037import org.ametys.runtime.model.ModelItem; 038import org.ametys.runtime.model.type.ElementType; 039import org.ametys.runtime.model.type.ModelItemTypeConstants; 040 041/** 042 * Helper class that provides a method to check if a date is between two others. 043 */ 044public final class EventHelper implements LogEnabled, Serviceable 045{ 046 /** The Ametys object resolver */ 047 protected static AmetysObjectResolver _resolver; 048 /** The extension point for content types */ 049 protected static ContentTypeExtensionPoint _contentTypeEP; 050 051 private static Logger _logger; 052 053 @Override 054 public void enableLogging(Logger logger) 055 { 056 _logger = logger; 057 } 058 059 @Override 060 public void service(ServiceManager manager) throws ServiceException 061 { 062 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 063 _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 064 } 065 066 /** 067 * Tests if the given date is either: 068 * - equal to the first, if there is no end date. 069 * - between the two dates, if there is a start and end date. 070 * @param dateStr the date to test. 071 * @param startStr start of the interval. 072 * @param endStr end of the interval. 073 * @return true if the date is either equal to the start date or between the start and end date. 074 */ 075 public static boolean isBetween(String dateStr, String startStr, String endStr) 076 { 077 boolean between = false; 078 LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE); 079 LocalDate start = LocalDate.parse(startStr, DateTimeFormatter.ISO_LOCAL_DATE); 080 081 if (StringUtils.isBlank(endStr)) 082 { 083 between = date.equals(start); 084 } 085 else 086 { 087 LocalDate end = LocalDate.parse(endStr, DateTimeFormatter.ISO_LOCAL_DATE); 088 between = (date.isAfter(start) || date.equals(start)) && (date.isBefore(end) || date.equals(end)); 089 } 090 091 return between; 092 } 093 094 /** 095 * Tests if the given event occurs in the given month. 096 * @param monthStartStr The month first day, cannot be null or blank. 097 * @param eventStartStr The event start date, can be null or blank only if the end date is set. 098 * @param eventEndStr The event end date, can be null or blank only if the end date is set. 099 * @return true if the event occurs in the given month. 100 */ 101 public static boolean inMonth(String monthStartStr, String eventStartStr, String eventEndStr) 102 { 103 boolean overlaps = false; 104 105 Date myDate = DateUtils.parse(monthStartStr); 106 ZonedDateTime monthStart = myDate.toInstant().atZone(ZoneId.systemDefault()); 107 ZonedDateTime monthEnd = monthStart.toLocalDate().atStartOfDay(monthStart.getZone()).plusMonths(1); 108 109 if (StringUtils.isNotBlank(eventStartStr) && StringUtils.isNotBlank(eventEndStr)) 110 { 111 ZonedDateTime eventStart = DateUtils.parse(eventStartStr).toInstant().atZone(ZoneId.systemDefault()); 112 ZonedDateTime eventEnd = DateUtils.parse(eventEndStr).toInstant().atZone(ZoneId.systemDefault()); 113 114 // check that start is before end to avoid an unwanted exception 115 try 116 { 117 // If eventStart equals to eventEnd and equals to monthStart, month#overlaps(Interval) will return false. In our case, we want to consider it actually does. 118 overlaps = eventStartStr.equals(eventEndStr) ? _contains(monthStart, monthEnd, eventStart) 119 : _overlaps(monthStart, monthEnd, eventStart, eventEnd); 120 } 121 catch (IllegalArgumentException e) 122 { 123 // The end is before the start 124 overlaps = false; 125 _logger.error(String.format("Invalid dates of event: the end date (%s) must be greater or equal to the start date (%s). The event will be ignored in calendar view.", eventEndStr, eventStartStr), e); 126 } 127 } 128 else if (StringUtils.isNotBlank(eventStartStr)) 129 { 130 ZonedDateTime eventStart = (DateUtils.parse(eventStartStr)).toInstant().atZone(ZoneId.systemDefault()); 131 overlaps = _contains(monthStart, monthEnd, eventStart); 132 } 133 else if (StringUtils.isNotBlank(eventEndStr)) 134 { 135 ZonedDateTime eventEnd = (DateUtils.parse(eventEndStr)).toInstant().atZone(ZoneId.systemDefault()); 136 overlaps = _contains(monthStart, monthEnd, eventEnd); 137 } 138 139 return overlaps; 140 } 141 142 /** 143 * Test if an event is between 2 dates 144 * @param start start date 145 * @param end end date 146 * @param event event to check 147 * @return true if the event is between both dates 148 */ 149 private static boolean _contains(ZonedDateTime start, ZonedDateTime end, ZonedDateTime event) 150 { 151 ZonedDateTime realStart; 152 ZonedDateTime realEnd; 153 if (start.isBefore(end)) 154 { 155 realStart = start; 156 realEnd = end; 157 } 158 else 159 { 160 realStart = end; 161 realEnd = start; 162 } 163 return (realStart.isBefore(event) || realStart.isEqual(event)) && realEnd.isAfter(event); 164 } 165 166 /** 167 * Test if 2 period of time overlap 168 * @param start1 start of 1st period 169 * @param end1 end of 1st period 170 * @param start2 start of 2nd period 171 * @param end2 end of 2nd period 172 * @return true if both period overlaps 173 */ 174 private static boolean _overlaps(ZonedDateTime start1, ZonedDateTime end1, ZonedDateTime start2, ZonedDateTime end2) 175 { 176 ZonedDateTime realStart1; 177 ZonedDateTime realEnd1; 178 if (start1.isBefore(end1)) 179 { 180 realStart1 = start1; 181 realEnd1 = end1; 182 } 183 else 184 { 185 realStart1 = end1; 186 realEnd1 = start1; 187 } 188 ZonedDateTime realStart2; 189 ZonedDateTime realEnd2; 190 if (start2.isBefore(end2)) 191 { 192 realStart2 = start2; 193 realEnd2 = end2; 194 } 195 else 196 { 197 realStart2 = end2; 198 realEnd2 = start2; 199 } 200 return (realStart1.isBefore(realEnd2) || realStart1.isEqual(realEnd2)) && realEnd1.isAfter(realStart2); 201 } 202 203 /** 204 * Get the next day 205 * @param dateStr the zoned date time 206 * @return the next day as iso zoned date time 207 */ 208 public static String nextDay(String dateStr) 209 { 210 ZonedDateTime zonedDateTime = DateUtils.parseZonedDateTime(dateStr); 211 zonedDateTime = zonedDateTime.plusDays(1); 212 213 return DateUtils.zonedDateTimeToString(zonedDateTime); 214 } 215 216 /** 217 * Get the next day 218 * @param dateStr the zoned date time 219 * @param format the output format: "date" for ISO local date or "basicDate" to basic ISO date. 220 * @return the next day at requested output format 221 */ 222 public static String nextDay(String dateStr, String format) 223 { 224 String nextDayStr = ""; 225 226 ZonedDateTime date = ZonedDateTime.parse(dateStr, DateUtils.getISODateTimeFormatter()); 227 228 ZonedDateTime nextDay = date.plusDays(1); 229 230 if ("basicDate".equals(format)) 231 { 232 nextDayStr = DateTimeFormatter.BASIC_ISO_DATE.format(nextDay); 233 } 234 else 235 { 236 nextDayStr = DateTimeFormatter.ISO_LOCAL_DATE.format(nextDay); 237 } 238 239 return nextDayStr; 240 } 241 242 /** 243 * Tests if the given attribute for the given content is of type datetime 244 * @param contentId The id of the content 245 * @param attributeName The attribute name 246 * @return true if the given attribute for the given content is of type datetime 247 */ 248 public static boolean isDatetime(String contentId, String attributeName) 249 { 250 Content content = _resolver.resolveById(contentId); 251 String[] contentTypeIds = content.getTypes(); 252 253 for (String contentTypeId : contentTypeIds) 254 { 255 ContentType contentType = _contentTypeEP.getExtension(contentTypeId); 256 257 if (contentType.hasModelItem(attributeName)) 258 { 259 ModelItem definition = contentType.getModelItem(attributeName); 260 if (definition instanceof ElementDefinition) 261 { 262 ElementType type = ((ElementDefinition) definition).getType(); 263 return ModelItemTypeConstants.DATETIME_TYPE_ID.equals(type.getId()); 264 } 265 else 266 { 267 // The given attribute is a group item 268 return false; 269 } 270 } 271 } 272 273 return false; 274 } 275 276}