From 7669fa905ea57e2bc47ac8233d8fadd6ff9ac153 Mon Sep 17 00:00:00 2001 From: srosse <stephane.rosse@frentix.com> Date: Fri, 8 Mar 2019 09:58:02 +0100 Subject: [PATCH] OO-3952: rewrite EXDATE to match standard in iCal feed --- .../olat/commons/calendar/CalendarUtils.java | 23 ++++- .../olat/commons/calendar/ICalServlet.java | 95 ++++++++++++++++--- 2 files changed, 98 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/olat/commons/calendar/CalendarUtils.java b/src/main/java/org/olat/commons/calendar/CalendarUtils.java index d5da7238248..c98006f9699 100644 --- a/src/main/java/org/olat/commons/calendar/CalendarUtils.java +++ b/src/main/java/org/olat/commons/calendar/CalendarUtils.java @@ -45,7 +45,8 @@ import net.fortuna.ical4j.model.property.ExDate; public class CalendarUtils { private static final OLog log = Tracing.createLoggerFor(CalendarUtils.class); - private static final SimpleDateFormat ical4jFormatter = new SimpleDateFormat("yyyyMMdd"); + private static final SimpleDateFormat ical4jDateFormatter = new SimpleDateFormat("yyyyMMdd"); + private static final SimpleDateFormat ical4jDateTimeFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); private static final SimpleDateFormat occurenceDateTimeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); public static String getTimeAsString(Date date, Locale locale) { @@ -187,8 +188,8 @@ public class CalendarUtils { public static net.fortuna.ical4j.model.Date createDate(Date date) { try { String toString; - synchronized(ical4jFormatter) {//cluster_OK only to optimize memory/speed - toString = ical4jFormatter.format(date); + synchronized(ical4jDateFormatter) {//cluster_OK only to optimize memory/speed + toString = ical4jDateFormatter.format(date); } return new net.fortuna.ical4j.model.Date(toString); } catch (ParseException e) { @@ -196,12 +197,24 @@ public class CalendarUtils { } } + public static net.fortuna.ical4j.model.DateTime createDateTime(Date date) { + try { + String toString; + synchronized(ical4jDateTimeFormatter) {//cluster_OK only to optimize memory/speed + toString = ical4jDateTimeFormatter.format(date); + } + return new net.fortuna.ical4j.model.DateTime(toString); + } catch (ParseException e) { + return null; + } + } + public static String formatRecurrenceDate(Date date, boolean allDay) { try { String toString; if(allDay) { - synchronized(ical4jFormatter) {//cluster_OK only to optimize memory/speed - toString = ical4jFormatter.format(date); + synchronized(ical4jDateFormatter) {//cluster_OK only to optimize memory/speed + toString = ical4jDateFormatter.format(date); } } else { synchronized(occurenceDateTimeFormat) {//cluster_OK only to optimize memory/speed diff --git a/src/main/java/org/olat/commons/calendar/ICalServlet.java b/src/main/java/org/olat/commons/calendar/ICalServlet.java index c042501f34e..7302ef9c62e 100644 --- a/src/main/java/org/olat/commons/calendar/ICalServlet.java +++ b/src/main/java/org/olat/commons/calendar/ICalServlet.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.io.Writer; import java.net.URISyntaxException; import java.net.URL; +import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -59,14 +60,19 @@ import net.fortuna.ical4j.data.ParserException; import net.fortuna.ical4j.model.Calendar; import net.fortuna.ical4j.model.Component; import net.fortuna.ical4j.model.ComponentList; +import net.fortuna.ical4j.model.DateList; import net.fortuna.ical4j.model.Parameter; import net.fortuna.ical4j.model.Property; import net.fortuna.ical4j.model.PropertyList; +import net.fortuna.ical4j.model.TimeZone; import net.fortuna.ical4j.model.ValidationException; import net.fortuna.ical4j.model.component.VEvent; import net.fortuna.ical4j.model.component.VTimeZone; import net.fortuna.ical4j.model.parameter.TzId; +import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.model.property.CalScale; +import net.fortuna.ical4j.model.property.DtStart; +import net.fortuna.ical4j.model.property.ExDate; import net.fortuna.ical4j.model.property.Uid; import net.fortuna.ical4j.model.property.Url; import net.fortuna.ical4j.model.property.Version; @@ -346,6 +352,8 @@ public class ICalServlet extends HttpServlet { return Agent.googleCalendar; } else if(userAgent.startsWith("Java/1.")) { return Agent.java; + } else if(userAgent.indexOf("CalendarAgent/") >= 0) { + return Agent.calendar; } return Agent.unkown; } @@ -404,25 +412,82 @@ public class ICalServlet extends HttpServlet { try { ComponentList events = calendar.getComponents(); for (final Iterator<?> i = events.iterator(); i.hasNext();) { - Object comp = i.next(); - String event = comp.toString(); - if (agent == Agent.outlook && comp instanceof VEvent) { - event = quoteTimeZone(event, (VEvent)comp, timezoneIds); - } - if(agent == Agent.googleCalendar) { - event = event.replace("CLASS:PRIVATE" + Strings.LINE_SEPARATOR, ""); - event = event.replace("X-OLAT-MANAGED:all" + Strings.LINE_SEPARATOR, ""); - event = event.replace("DESCRIPTION:" + Strings.LINE_SEPARATOR, ""); - event = event.replace("LOCATION:" + Strings.LINE_SEPARATOR, ""); - } - - out.write(event); + outputCalendarComponent(i.next(), out, agent, timezoneIds); } } catch (IOException | OLATRuntimeException e) { log.error("", e); } } + private void outputCalendarComponent(Object component, Writer out, Agent agent, Set<String> timezoneIds) throws IOException { + if (component instanceof VEvent) { + rewriteExDate((VEvent)component); + } + + String event = component.toString(); + if (agent == Agent.outlook && component instanceof VEvent) { + event = quoteTimeZone(event, (VEvent)component, timezoneIds); + } + if(agent == Agent.googleCalendar) { + event = event.replace("CLASS:PRIVATE" + Strings.LINE_SEPARATOR, ""); + event = event.replace("X-OLAT-MANAGED:all" + Strings.LINE_SEPARATOR, ""); + event = event.replace("DESCRIPTION:" + Strings.LINE_SEPARATOR, ""); + event = event.replace("LOCATION:" + Strings.LINE_SEPARATOR, ""); + } + + out.write(event); + } + + /** + * + * @param event The event to rewrite + */ + private void rewriteExDate(VEvent event) { + DtStart start = event.getStartDate(); + ExDate exDate = (ExDate)event.getProperties().getProperty(Property.EXDATE); + + if(exDate != null && start != null) { + Date startDate = start.getDate(); + java.util.Calendar startCal = java.util.Calendar.getInstance(); + startCal.setTime(startDate); + + TimeZone startZone = start.getTimeZone(); + TimeZone exZone = exDate.getTimeZone(); + DateList dateList = exDate.getDates(); + + java.util.Calendar excCal = java.util.Calendar.getInstance(); + + Parameter dateParameter = event.getProperties().getProperty(Property.DTSTART) + .getParameters().getParameter(Value.DATE.getName()); + boolean dateOnly = dateParameter != null; + + DateList newDateList = dateOnly ? new DateList(Value.DATE) : new DateList(); + for(Object obj:dateList) { + Date d = (Date)obj; + if(dateOnly) { + newDateList.add(CalendarUtils.createDate(d)); + } else { + excCal.setTime(d); + if(excCal.get(java.util.Calendar.HOUR_OF_DAY) != startCal.get(java.util.Calendar.HOUR_OF_DAY)) { + excCal.set(java.util.Calendar.HOUR_OF_DAY, startCal.get(java.util.Calendar.HOUR_OF_DAY)); + excCal.set(java.util.Calendar.MINUTE, startCal.get(java.util.Calendar.MINUTE)); + excCal.set(java.util.Calendar.SECOND, startCal.get(java.util.Calendar.SECOND)); + d = excCal.getTime(); + } + newDateList.add(CalendarUtils.createDateTime(d)); + } + } + + ExDate newExDate = new ExDate(newDateList); + if(exZone == null && startZone != null) { + newExDate.setTimeZone(startZone); + } + + event.getProperties().remove(exDate); + event.getProperties().add(newExDate); + } + } + private String quoteTimeZone(String event, VEvent vEvent, Set<String> timezoneIds) { if(vEvent == null || vEvent.getStartDate().getTimeZone() == null || vEvent.getStartDate().getTimeZone().getVTimeZone() == null) { @@ -502,8 +567,7 @@ public class ICalServlet extends HttpServlet { URL resource = ResourceLoader.getResource("zoneinfo-outlook/" + id + ".ics"); CalendarBuilder builder = new CalendarBuilder(); Calendar calendar = builder.build(resource.openStream()); - VTimeZone vTimeZone = (VTimeZone)calendar.getComponent(Component.VTIMEZONE); - return vTimeZone; + return (VTimeZone)calendar.getComponent(Component.VTIMEZONE); } catch (Exception e) { log.error("", e); return null; @@ -515,6 +579,7 @@ public class ICalServlet extends HttpServlet { unkown, outlook, googleCalendar, + calendar,// macos, iOS java } } \ No newline at end of file -- GitLab