diff --git a/pom.xml b/pom.xml
index 7c4d303b56c9a43bade46407c6a4cd662b215bf7..36df83377143f075ac58f87b23d360648ee9c41a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1741,6 +1741,17 @@
 				</exclusion>
 			</exclusions>
 		</dependency>
+		<dependency>
+			<groupId>org.mnode.ical4j</groupId>
+			<artifactId>ical4j-zoneinfo-outlook</artifactId>
+			<version>1.0.4</version>
+			<exclusions>
+				<exclusion>
+					<groupId>commons-logging</groupId>
+					<artifactId>commons-logging</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
 		<dependency><!-- Velocity dependency -->
 			<groupId>oro</groupId>
 			<artifactId>oro</artifactId>
diff --git a/src/main/java/org/olat/commons/calendar/ICalServlet.java b/src/main/java/org/olat/commons/calendar/ICalServlet.java
index 8bd448aa93306dc0d2cd920a9b5a29e14ea5c9d3..77e2a552c482f27f78c0addea319aa906f6e81df 100644
--- a/src/main/java/org/olat/commons/calendar/ICalServlet.java
+++ b/src/main/java/org/olat/commons/calendar/ICalServlet.java
@@ -29,9 +29,15 @@ package org.olat.commons.calendar;
 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;
+import java.util.Set;
 import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -46,17 +52,27 @@ import org.olat.core.id.Identity;
 import org.olat.core.logging.OLATRuntimeException;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
 import org.olat.core.util.i18n.I18nManager;
 
+import net.fortuna.ical4j.data.CalendarBuilder;
 import net.fortuna.ical4j.data.CalendarOutputter;
+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.Parameter;
 import net.fortuna.ical4j.model.PropertyList;
 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.property.CalScale;
 import net.fortuna.ical4j.model.property.Uid;
 import net.fortuna.ical4j.model.property.Url;
+import net.fortuna.ical4j.model.property.Version;
 import net.fortuna.ical4j.model.property.XProperty;
+import net.fortuna.ical4j.util.ResourceLoader;
 import net.fortuna.ical4j.util.Strings;
 
 
@@ -73,6 +89,9 @@ public class ICalServlet extends HttpServlet {
 	private static final long serialVersionUID = -155266285395912535L;
 	private static final OLog log = Tracing.createLoggerFor(ICalServlet.class);
 	
+	private final int cacheAge = 60 * 60 * 12;
+	private static final ConcurrentMap<String,VTimeZone> outlookVTimeZones = new ConcurrentHashMap<>();
+	
 	/** collection of iCal feed prefixs **/
 	public static final String[] SUPPORTED_PREFIX = {
 			CalendarManager.ICAL_PREFIX_AGGREGATED,
@@ -113,7 +132,7 @@ public class ICalServlet extends HttpServlet {
 				return; // error
 			}
 
-			getIcalDocument(requestUrl, response);
+			getIcalDocument(requestUrl, request, response);
 		} catch (ValidationException e) {
 			log.warn("Validation Error when generate iCal stream for path::" + request.getPathInfo(), e);
 			response.sendError(HttpServletResponse.SC_CONFLICT, requestUrl);
@@ -138,7 +157,7 @@ public class ICalServlet extends HttpServlet {
 	 * @param pathInfo
 	 * @return Calendar
 	 */
-	private void getIcalDocument(String requestUrl, HttpServletResponse response)
+	private void getIcalDocument(String requestUrl, HttpServletRequest request, HttpServletResponse response)
 	throws ValidationException, IOException {
 		// get the individual path tokens
 		String pathInfo;
@@ -182,6 +201,7 @@ public class ICalServlet extends HttpServlet {
 		
 		try {
 			response.setCharacterEncoding("UTF-8");
+			setCacheControl(response);
 		} catch (Exception e) {
 			e.printStackTrace();
 		}
@@ -195,7 +215,7 @@ public class ICalServlet extends HttpServlet {
 				log.warn("Authenticity Check failed for the ical feed path: " + pathInfo);
 				response.sendError(HttpServletResponse.SC_UNAUTHORIZED, requestUrl);
 			} else {
-				generateAggregatedCalendar(config.getIdentity(), response);
+				generateAggregatedCalendar(config.getIdentity(), request, response);
 			}
 		} else if (calendarManager.calendarExists(calendarType, calendarID)) {
 			// check the authentication token
@@ -215,23 +235,36 @@ public class ICalServlet extends HttpServlet {
 		}
 	}
 	
-	private void generateAggregatedCalendar(Identity identity, HttpServletResponse response) throws IOException {
+	private void setCacheControl(HttpServletResponse httpResponse) {
+		long expiry = new Date().getTime() + cacheAge * 1000;
+	    httpResponse.setDateHeader("Expires", expiry);
+	    httpResponse.setHeader("Cache-Control", "max-age="+ cacheAge);
+	}
+	
+	private void generateAggregatedCalendar(Identity identity, HttpServletRequest request, HttpServletResponse response) throws IOException {
 		PersonalCalendarManager homeCalendarManager = CoreSpringFactory.getImpl(PersonalCalendarManager.class);
 		if(identity == null) {
 			response.sendError(HttpServletResponse.SC_NOT_FOUND);
 		} else {
 			List<CalendarFileInfos> iCalFiles = homeCalendarManager.getListOfCalendarsFiles(identity);
 			DBFactory.getInstance().commitAndCloseSession();
+			boolean outlook = isOutlook(request);
 			
 			Writer out = response.getWriter();
 			out.write(Calendar.BEGIN);
 			out.write(':');
 			out.write(Calendar.VCALENDAR);
 			out.write(Strings.LINE_SEPARATOR);
+			out.write(Version.VERSION_2_0.toString());
+			out.write(CalScale.GREGORIAN.toString());
 
+			Set<String> timezoneIds = new HashSet<>();
 			int numOfFiles = iCalFiles.size();
 			for(int i=0; i<numOfFiles; i++) {
-				outputCalendar(iCalFiles.get(i), out);
+				outputCalendar(iCalFiles.get(i), out, outlook, timezoneIds);
+			}
+			if(outlook) {
+				outputTimeZoneForOutlook(timezoneIds, out);
 			}
 			
 			out.write(Calendar.END);
@@ -240,7 +273,31 @@ public class ICalServlet extends HttpServlet {
 		}
 	}
 	
-	private void outputCalendar(CalendarFileInfos fileInfos, Writer out) throws IOException {
+	private boolean isOutlook(HttpServletRequest request) {
+		String userAgent = request.getHeader("User-Agent");
+		if(userAgent != null && userAgent.indexOf("Microsoft Outlook") >= 0) {
+			return true;
+		}
+		return false;
+	}
+	
+	private void outputTimeZoneForOutlook(Set<String> timezoneIds,  Writer out) {
+		for(String timezoneId:timezoneIds) {
+			if(StringHelper.containsNonWhitespace(timezoneId)) {
+				try {
+					VTimeZone vTimeZone = getOutlookVTimeZone(timezoneId);
+					if(vTimeZone != null) {
+						out.write(vTimeZone.toString());
+					}
+				} catch (IOException | ParserException e) {
+					log.error("", e);
+				}
+			}
+		}
+	}
+	
+	private void outputCalendar(CalendarFileInfos fileInfos, Writer out, boolean outlook, Set<String> timezoneIds)
+	throws IOException {
 		try {
 			CalendarManager calendarManager = CoreSpringFactory.getImpl(CalendarManager.class);
 			Calendar calendar = calendarManager.readCalendar(fileInfos.getCalendarFile());
@@ -251,7 +308,11 @@ public class ICalServlet extends HttpServlet {
 			
 			ComponentList events = calendar.getComponents();
 			for (final Iterator<?> i = events.iterator(); i.hasNext();) {
-				String event = i.next().toString();
+				Object comp = i.next();
+				String event = comp.toString();
+				if (outlook && comp instanceof VEvent) {
+					event = quoteTimeZone(event, (VEvent)comp, timezoneIds);
+				}
 				out.write(event);
 			}
 		} catch (IOException | OLATRuntimeException e) {
@@ -259,6 +320,19 @@ public class ICalServlet extends HttpServlet {
 		}
 	}
 	
+	private String quoteTimeZone(String event, VEvent vEvent, Set<String> timezoneIds) {
+		if(vEvent == null || vEvent.getStartDate().getTimeZone() == null
+				|| vEvent.getStartDate().getTimeZone().getVTimeZone() == null) {
+			return event;
+		}
+
+		String timezoneId = vEvent.getStartDate().getTimeZone().getID();
+		timezoneIds.add(timezoneId);
+		TzId tzId = (TzId)vEvent.getStartDate().getParameter(Parameter.TZID);
+		String tzidReplacement = "TZID=\"" + timezoneId + "\"";	
+		return event.replace(tzId.toString(), tzidReplacement);
+	}
+	
 	private void updateUUID(Calendar calendar, String prefix) {
 		for (Iterator<?> eventIter = calendar.getComponents().iterator(); eventIter.hasNext();) {
 			Object comp = eventIter.next();
@@ -312,4 +386,23 @@ public class ICalServlet extends HttpServlet {
 			}
 		}
 	}
+	
+	/**
+     * Load the VTimeZone for Outlook. ical4j use a static map to reuse the TimeZone objects, we need to load
+     * and save our specialized TimeZone in a separate map.
+     */
+    private VTimeZone getOutlookVTimeZone(final String id) throws IOException, ParserException {
+    	return outlookVTimeZones.computeIfAbsent(id, (timeZoneId) -> {
+        	try {
+				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;
+			} catch (Exception e) {
+				log.error("", e);
+				return null;
+			}
+    	});
+    }
 }
\ No newline at end of file