diff --git a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java
index e45eccaf8a4a2ba1f16f30845373114b55f5c3f4..d66a926ffeea30b9d68dd0a430544f75ea25baab 100644
--- a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java
+++ b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManager.java
@@ -51,8 +51,22 @@ public interface UserCourseInformationsManager {
 
 	public Map<Long,Date> getRecentLaunchDates(Long courseResourceId, List<Identity> identities);
 	
+	/**
+	 * Return the initial launch dates of a list of users.
+	 * @param courseResourceId
+	 * @param identities
+	 * @return
+	 */
 	public Map<Long,Date> getInitialLaunchDates(Long courseResourceId, List<Identity> identities);
 	
+	/**
+	 * Return all initial launch dates of a course.
+	 * 
+	 * @param courseResourceId
+	 * @return
+	 */
+	public Map<Long,Date> getInitialLaunchDates(Long courseResourceId);
+	
 	public int deleteUserCourseInformations(RepositoryEntry entry);
 
 }
diff --git a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java
index 55252b5f5edc29e24d9343aaa5112fddce53d6f1..0cdb16e5cbbcdab18e7c95ada7d3a352a1af4836 100644
--- a/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java
+++ b/src/main/java/org/olat/course/assessment/manager/UserCourseInformationsManagerImpl.java
@@ -338,7 +338,40 @@ public class UserCourseInformationsManagerImpl implements UserCourseInformations
 			return Collections.emptyMap();
 		}
 	}
+	
+	/**
+	 * Return a map of identity keys to initial launch date.
+	 * 
+	 * @param courseResourceId The course resourceable id
+	 * @return
+	 */
+	@Override
+	public Map<Long,Date> getInitialLaunchDates(Long courseResourceId) {
+		try {
+
+			StringBuilder sb = new StringBuilder();
+			sb.append("select infos.identity.key, infos.initialLaunch from ").append(UserCourseInfosImpl.class.getName()).append(" as infos ")
+			  .append(" inner join infos.resource as resource")
+			  .append(" where resource.resId=:resId and resource.resName='CourseModule'");
+
+			TypedQuery<Object[]> query = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Object[].class)
+					.setParameter("resId", courseResourceId);
 
+			List<Object[]> infoList = query.getResultList();
+			Map<Long,Date> dateMap = new HashMap<Long,Date>();
+			for(Object[] infos:infoList) {
+				Long identityKey = (Long)infos[0];
+				Date initialLaunch = (Date)infos[1];
+				if(identityKey != null && initialLaunch != null) {
+					dateMap.put(identityKey, initialLaunch);
+				}
+			}
+			return dateMap;
+		} catch (Exception e) {
+			log.error("Cannot retrieve course informations for: " + courseResourceId, e);
+			return Collections.emptyMap();
+		}
+	}
 
 	/**
 	 * Return a map of identity keys to initial launch date.
diff --git a/src/main/java/org/olat/course/nodes/GTACourseNode.java b/src/main/java/org/olat/course/nodes/GTACourseNode.java
index caefb37813fdd7ff32738971a08f9262a60ca89c..ecd475adf1c86915fa7cade2d7b194c9047a8eab 100644
--- a/src/main/java/org/olat/course/nodes/GTACourseNode.java
+++ b/src/main/java/org/olat/course/nodes/GTACourseNode.java
@@ -108,16 +108,24 @@ public class GTACourseNode extends AbstractAccessableCourseNode implements Asses
 	public static final String GTASK_AREAS = "grouptask.areas";
 	public static final String GTASK_ASSIGNMENT = "grouptask.assignement";
 	public static final String GTASK_ASSIGNMENT_DEADLINE = "grouptask.assignment.deadline";
+	public static final String GTASK_ASSIGNMENT_DEADLINE_RELATIVE = "grouptask.assignment.deadline.relative";
+	public static final String GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO = "grouptask.assignment.deadline.relative.to";
 	public static final String GTASK_SUBMIT = "grouptask.submit";
 	public static final String GTASK_SUBMIT_DEADLINE = "grouptask.submit.deadline";
+	public static final String GTASK_SUBMIT_DEADLINE_RELATIVE = "grouptask.submit.deadline.relative";
+	public static final String GTASK_SUBMIT_DEADLINE_RELATIVE_TO = "grouptask.submit.deadline.relative.to";
 	public static final String GTASK_REVIEW_AND_CORRECTION = "grouptask.review.and.correction";
 	public static final String GTASK_REVISION_PERIOD = "grouptask.revision.period";
 	public static final String GTASK_SAMPLE_SOLUTION = "grouptask.solution";
 	public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER = "grouptask.solution.visible.after";
+	public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE = "grouptask.solution.visible.after.relative";
+	public static final String GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO = "grouptask.solution.visible.after.relative.to";
 	public static final String GTASK_GRADING = "grouptask.grading";
 	
 	public static final String GTASK_TASKS = "grouptask.tasks";
 	
+	public static final String GTASK_RELATIVE_DATES = "grouptask.rel.dates";
+	
 	public static final String GTASK_ASSIGNEMENT_TYPE = "grouptask.assignement.type";
 	public static final String GTASK_ASSIGNEMENT_TYPE_AUTO = "auto";
 	public static final String GTASK_ASSIGNEMENT_TYPE_MANUAL = "manual";
diff --git a/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java b/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java
new file mode 100644
index 0000000000000000000000000000000000000000..4faf419755f4686d191b0976c8f9b98439438345
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/GTARelativeToDates.java
@@ -0,0 +1,34 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.course.nodes.gta;
+
+/**
+ * 
+ * Initial date: 08.05.2015<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public enum GTARelativeToDates {
+	
+	courseStart,// relative to course start defined by a life-cycle
+	courseLaunch,// relative to the course launch by a user
+	enrollment//relative to the enrollment date
+
+}
diff --git a/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java
index 868517dbdc2759013df742280930d318b870a16b..364de565118c75f16b2b508ca3eae448f7cca5e4 100644
--- a/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java
+++ b/src/main/java/org/olat/course/nodes/gta/rule/AbstractDueDateTaskRuleSPI.java
@@ -19,20 +19,28 @@
  */
 package org.olat.course.nodes.gta.rule;
 
+import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.olat.basesecurity.GroupRoles;
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.id.Identity;
+import org.olat.core.util.StringHelper;
 import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
+import org.olat.course.assessment.manager.UserCourseInformationsManager;
 import org.olat.course.nodes.CourseNode;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
+import org.olat.course.nodes.gta.GTARelativeToDates;
 import org.olat.course.nodes.gta.GTAType;
 import org.olat.course.nodes.gta.Task;
 import org.olat.course.nodes.gta.TaskList;
@@ -45,7 +53,9 @@ import org.olat.modules.reminder.model.ReminderRuleImpl;
 import org.olat.modules.reminder.rule.LaunchUnit;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryEntryRelationType;
+import org.olat.repository.RepositoryService;
 import org.olat.repository.manager.RepositoryEntryRelationDAO;
+import org.olat.repository.model.RepositoryEntryLifecycle;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -79,20 +89,21 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu
 		return identities == null ? Collections.<Identity>emptyList() : identities;
 	}
 	
-	protected List<Identity> evaluateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl r) {
+	protected List<Identity> evaluateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) {
 		List<Identity> identities = null;
-		Date dueDate = getDueDate(gtaNode);
-		if(dueDate != null) {
-			int value = Integer.parseInt(r.getRightOperand());
-			String unit = r.getRightUnit();
-			Date now = new Date();
-			if(near(dueDate, now, value, LaunchUnit.valueOf(unit))) {
+		if(gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES)) {
+			identities = evaluateRelativeDateRule(entry, gtaNode, rule);
+		} else {
+			Date dueDate = getDueDate(gtaNode);
+			if(dueDate != null && isNear(dueDate, now(), rule)) {
 				identities = getPeopleToRemind(entry, gtaNode);
 			}
 		}
 		return identities == null ? Collections.<Identity>emptyList() : identities;
 	}
 	
+	protected abstract List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl r);
+	
 	protected abstract Date getDueDate(GTACourseNode gtaNode);
 	
 	protected List<Identity> getPeopleToRemind(RepositoryEntry entry, GTACourseNode gtaNode) {
@@ -105,6 +116,95 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu
 		}
 	}
 	
+	protected List<Identity> getPeopleToRemindRelativeTo(RepositoryEntry entry, GTACourseNode gtaNode,
+			int numOfDays, String relativeTo, ReminderRuleImpl rule) {
+		List<Identity> identities = null;
+		if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+			GTARelativeToDates rel = GTARelativeToDates.valueOf(relativeTo);
+			switch(rel) {
+				case courseStart: {
+					RepositoryEntryLifecycle lifecycle = entry.getLifecycle();
+					if(lifecycle != null && lifecycle.getValidFrom() != null) {
+						Date referenceDate = getDate(lifecycle.getValidFrom(),  numOfDays);
+						if(isNear(referenceDate, now(), rule)) {
+							identities = getPeopleToRemind(entry, gtaNode);
+						}
+					}
+					break;
+				}
+	
+				case courseLaunch: {
+					UserCourseInformationsManager userCourseInformationsManager = CoreSpringFactory.getImpl(UserCourseInformationsManager.class);
+					Map<Long,Date> initialLaunchDates = userCourseInformationsManager.getInitialLaunchDates(entry.getOlatResource().getResourceableId());
+					Map<Long,Date> dueDates = getDueDates(initialLaunchDates, numOfDays);
+					identities = getPeopleToRemindRelativeTo(entry, gtaNode, dueDates, rule);
+					break;
+				}
+				case enrollment: {
+					RepositoryService repositoryService = CoreSpringFactory.getImpl(RepositoryService.class);
+					Map<Long,Date> enrollmentDates = repositoryService.getEnrollmentDates(entry);
+					Map<Long,Date> dueDates = getDueDates(enrollmentDates, numOfDays);
+					identities = getPeopleToRemindRelativeTo(entry, gtaNode, dueDates, rule);
+					break;
+				}
+			}	
+		}
+		return identities;
+	}
+	
+	protected List<Identity> getPeopleToRemindRelativeTo(RepositoryEntry entry, GTACourseNode gtaNode,
+			Map<Long,Date> dates, ReminderRuleImpl rule) {
+		
+		Date now = now();
+		Set<Long> potentialidentityKeys = new HashSet<>();
+		for(Map.Entry<Long, Date> entryDate:dates.entrySet()) {
+			Long identityKey = entryDate.getKey();
+			Date date = entryDate.getValue();
+			if(isNear(date, now, rule)) {
+				potentialidentityKeys.add(identityKey);
+			}	
+		}
+
+		List<Identity> identities = null;
+		if(potentialidentityKeys.size() > 0) {
+			List<Identity> allIdentities = getPeopleToRemind(entry, gtaNode);
+			identities = new ArrayList<>();
+			for(Identity identity:allIdentities) {
+				if(potentialidentityKeys.contains(identity.getKey())) {
+					identities.add(identity);
+				}
+			}
+		}
+		return identities;
+	}
+	
+	private Map<Long,Date> getDueDates(Map<Long,Date> referenceDates, int numOfDays) {
+		Map<Long, Date> dueDates = new HashMap<>();
+		if(referenceDates != null && referenceDates.size() > 0) {
+			Calendar cal = Calendar.getInstance();
+			for(Map.Entry<Long, Date> referenceEntry:referenceDates.entrySet()) {
+				Long identityKey = referenceEntry.getKey();
+				cal.setTime(referenceEntry.getValue());
+				cal.add(Calendar.DATE, numOfDays);
+				dueDates.put(identityKey, cal.getTime());
+			}
+		}
+		return dueDates;
+	}
+	
+	private Date getDate(Date referenceDate, int numOfDays) {
+		Date date = null;
+		if(referenceDate != null) {
+			Calendar cal = Calendar.getInstance();
+			cal.setTime(referenceDate);
+			cal.add(Calendar.DATE, numOfDays);
+			cal.set(Calendar.SECOND, 0);
+			cal.set(Calendar.MILLISECOND, 0);
+			date = cal.getTime();
+		}
+		return date;
+	}
+	
 	protected List<Identity> getGroupsToRemind(TaskList taskList, GTACourseNode gtaNode) {
 		List<Task> tasks = gtaManager.getTasks(taskList);
 		Set<BusinessGroup> doneTasks = new HashSet<BusinessGroup>();
@@ -143,6 +243,18 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu
 		return identities;
 	}
 	
+	protected Date now() {
+		Calendar cal = Calendar.getInstance();
+		cal.set(Calendar.SECOND, 0);
+		cal.set(Calendar.MILLISECOND, 0);
+		return cal.getTime();
+	}
+	
+	protected boolean isNear(Date dueDate, Date now, ReminderRuleImpl r) {
+		int value = Integer.parseInt(r.getRightOperand());
+		String unit = r.getRightUnit();
+		return near(dueDate, now, value, LaunchUnit.valueOf(unit));
+	}
 	
 	private boolean near(Date date, Date now, int distance, LaunchUnit unit) {
 		double between = -1;
@@ -160,7 +272,8 @@ public abstract class AbstractDueDateTaskRuleSPI implements IdentitiesProviderRu
 				between = yearsBetween(now, date);
 				break;
 		}
-		return  between <= distance || between < 0.0;
+		// 0.1 to let +- 2 hours to match
+		return  between <= distance || between - 0.1 <= distance || between < 0.0;
 	}
 	
 	private double daysBetween(Date d1, Date d2) {
diff --git a/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java
index d5ac5d5595776589fb81c92b3840b0433e8f85df..6d8feebbe2720f62d3ef3a14ab120dba887b9d6b 100644
--- a/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java
+++ b/src/main/java/org/olat/course/nodes/gta/rule/AssignTaskRuleSPI.java
@@ -20,12 +20,16 @@
 package org.olat.course.nodes.gta.rule;
 
 import java.util.Date;
+import java.util.List;
 
+import org.olat.core.id.Identity;
+import org.olat.core.util.StringHelper;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.ui.BeforeDateTaskRuleEditor;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.reminder.ReminderRule;
 import org.olat.modules.reminder.RuleEditorFragment;
+import org.olat.modules.reminder.model.ReminderRuleImpl;
 import org.olat.repository.RepositoryEntry;
 import org.springframework.stereotype.Service;
 
@@ -63,4 +67,15 @@ public class AssignTaskRuleSPI extends AbstractDueDateTaskRuleSPI {
 		}
 		return dueDate;
 	}
+
+	@Override
+	protected List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) {
+		List<Identity> identities = null;
+		int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1);
+		String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO);
+		if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+			identities = getPeopleToRemindRelativeTo(entry, gtaNode, numOfDays, relativeTo, rule);
+		}
+		return identities;
+	}
 }
diff --git a/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java b/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java
index a22e6eae58692835b78043362bc2a98d3b1de155..326c05d16b11bf6e5ab381fc550396ab354dcaa5 100644
--- a/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java
+++ b/src/main/java/org/olat/course/nodes/gta/rule/SubmissionTaskRuleSPI.java
@@ -20,7 +20,10 @@
 package org.olat.course.nodes.gta.rule;
 
 import java.util.Date;
+import java.util.List;
 
+import org.olat.core.id.Identity;
+import org.olat.core.util.StringHelper;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
 import org.olat.course.nodes.gta.ui.BeforeDateTaskRuleEditor;
@@ -28,6 +31,7 @@ import org.olat.group.BusinessGroupService;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.modules.reminder.ReminderRule;
 import org.olat.modules.reminder.RuleEditorFragment;
+import org.olat.modules.reminder.model.ReminderRuleImpl;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.manager.RepositoryEntryRelationDAO;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -74,4 +78,15 @@ public class SubmissionTaskRuleSPI extends AbstractDueDateTaskRuleSPI {
 		}
 		return dueDate;
 	}
+	
+	@Override
+	protected List<Identity> evaluateRelativeDateRule(RepositoryEntry entry, GTACourseNode gtaNode, ReminderRuleImpl rule) {
+		List<Identity> identities = null;
+		int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1);
+		String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO);
+		if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+			identities = getPeopleToRemindRelativeTo(entry, gtaNode, numOfDays, relativeTo, rule);
+		}
+		return identities;
+	}
 }
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java
index 9df3055f6a36f1cd09d161ad433493ba9000f1fa..5e4350d42fd5a568c6e383f951a9dfd68b52e5b9 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAAbstractController.java
@@ -19,6 +19,7 @@
  */
 package org.olat.course.nodes.gta.ui;
 
+import java.util.Calendar;
 import java.util.Date;
 
 import org.olat.core.commons.services.notifications.PublisherData;
@@ -33,8 +34,10 @@ import org.olat.core.gui.control.controller.BasicController;
 import org.olat.core.id.Identity;
 import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
+import org.olat.course.assessment.manager.UserCourseInformationsManager;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
+import org.olat.course.nodes.gta.GTARelativeToDates;
 import org.olat.course.nodes.gta.GTAType;
 import org.olat.course.nodes.gta.Task;
 import org.olat.course.nodes.gta.TaskList;
@@ -44,6 +47,8 @@ import org.olat.course.run.userview.UserCourseEnvironment;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
 import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryService;
+import org.olat.repository.model.RepositoryEntryLifecycle;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -78,8 +83,16 @@ public abstract class GTAAbstractController extends BasicController {
 	
 	private ContextualSubscriptionController contextualSubscriptionCtr;
 	
+	private Date assignmentDueDate;
+	private Date submissionDueDate;
+	private Date solutionDueDate;
+	
 	@Autowired
 	protected GTAManager gtaManager;
+	@Autowired
+	protected RepositoryService repositoryService;
+	@Autowired
+	protected UserCourseInformationsManager userCourseInformationsManager;
 	
 	public GTAAbstractController(UserRequest ureq, WindowControl wControl,
 			GTACourseNode gtaNode, CourseEnvironment courseEnv, boolean withTitle, boolean withGrading) {
@@ -224,7 +237,7 @@ public abstract class GTAAbstractController extends BasicController {
 	}
 	
 	protected Task stepAssignment(@SuppressWarnings("unused") UserRequest ureq, Task assignedTask) {
-		Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
+		Date dueDate = getAssignementDueDate();
 		if(dueDate != null) {
 			String date = Formatter.getInstance(getLocale()).formatDateAndTime(dueDate);
 			mainVC.contextPut("assignmentDueDate", date);
@@ -238,8 +251,66 @@ public abstract class GTAAbstractController extends BasicController {
 		return assignedTask;
 	}
 	
+	protected void resetDueDates() {
+		assignmentDueDate = null;
+		submissionDueDate = null;
+		solutionDueDate = null;
+	}
+	
+	protected Date getAssignementDueDate() {
+		if(assignmentDueDate == null) {
+			Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
+			boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
+			if(relativeDate) {
+				int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1);
+				String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO);
+				if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+					assignmentDueDate = getReferenceDate(numOfDays, relativeTo);
+				}
+			} else if(dueDate != null) {
+				assignmentDueDate = dueDate;
+			}
+		}
+		return assignmentDueDate;
+	}
+	
+	protected Date getReferenceDate(int numOfDays, String relativeTo) {
+		Date dueDate = null;
+		if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+			GTARelativeToDates rel = GTARelativeToDates.valueOf(relativeTo);
+			Date referenceDate = null;
+			switch(rel) {
+				case courseStart: {
+					RepositoryEntryLifecycle lifecycle = courseEntry.getLifecycle();
+					if(lifecycle != null && lifecycle.getValidFrom() != null) {
+						referenceDate = lifecycle.getValidFrom();
+					}
+					break;
+				}
+				case courseLaunch: {
+					referenceDate = userCourseInformationsManager
+							.getInitialLaunchDate(courseEnv.getCourseResourceableId(), assessedIdentity);
+					break;
+				}
+				case enrollment: {
+					referenceDate = repositoryService
+							.getEnrollmentDate(courseEntry, assessedIdentity);
+					break;
+				}
+			}
+			
+			if(referenceDate != null) {
+				Calendar cal = Calendar.getInstance();
+				cal.setTime(referenceDate);
+				cal.add(Calendar.DATE, numOfDays);
+				dueDate = cal.getTime();
+			}
+		}
+		return dueDate;
+	}
+	
 	protected Task stepSubmit(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) {
-		Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE);
+		Date dueDate = getSubmissionDueDate();
 		if(dueDate != null) {
 			String date = Formatter.getInstance(getLocale()).formatDateAndTime(dueDate);
 			mainVC.contextPut("submitDueDate", date);
@@ -254,6 +325,23 @@ public abstract class GTAAbstractController extends BasicController {
 		return assignedTask;
 	}
 	
+	protected Date getSubmissionDueDate() {
+		if(submissionDueDate == null) {
+			Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE);
+			boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
+			if(relativeDate) {
+				int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1);
+				String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO);
+				if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+					submissionDueDate = getReferenceDate(numOfDays, relativeTo);
+				}
+			} else if(dueDate != null) {
+				submissionDueDate = dueDate;
+			}
+		}
+		return submissionDueDate;
+	}
+	
 	protected Task stepReviewAndCorrection(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) {
 		return assignedTask;
 	}
@@ -263,7 +351,7 @@ public abstract class GTAAbstractController extends BasicController {
 	}
 	
 	protected Task stepSolution(@SuppressWarnings("unused")UserRequest ureq, Task assignedTask) {
-		Date availableDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER);
+		Date availableDate = getSolutionDueDate();
 		if(availableDate != null) {
 			String date = Formatter.getInstance(getLocale()).formatDateAndTime(availableDate);
 			mainVC.contextPut("solutionAvailableDate", date);
@@ -271,6 +359,23 @@ public abstract class GTAAbstractController extends BasicController {
 		return assignedTask;
 	}
 	
+	protected Date getSolutionDueDate() {
+		if(solutionDueDate == null) {
+			boolean relativeDate = gtaNode.getModuleConfiguration().getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
+			Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER);
+			if(relativeDate) {
+				int numOfDays = gtaNode.getModuleConfiguration().getIntegerSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, -1);
+				String relativeTo = gtaNode.getModuleConfiguration().getStringValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO);
+				if(numOfDays >= 0 && StringHelper.containsNonWhitespace(relativeTo)) {
+					solutionDueDate = getReferenceDate(numOfDays, relativeTo);
+				}
+			} else if(dueDate != null) {
+				solutionDueDate = dueDate;
+			}
+		}
+		return solutionDueDate;
+	}
+	
 	protected Task stepGrading(@SuppressWarnings("unused") UserRequest ureq, Task assignedTask) {
 		if(businessGroupTask) {
 			String groupLog = courseEnv.getAuditManager().getUserNodeLog(gtaNode, assessedGroup);
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java
index 47ebbeb9fd294af5e1b11d405cfcf40a74335cb3..42d13b40627b5ad4d3676c69e9d092bbefbcf676 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAParticipantController.java
@@ -144,7 +144,7 @@ public class GTAParticipantController extends GTAAbstractController {
 			mainVC.contextPut("assignmentCssClass", "o_active");
 			
 			//assignment open?
-			Date dueDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
+			Date dueDate = getAssignementDueDate();
 			if(dueDate != null && dueDate.compareTo(new Date()) < 0) {
 				//assignment is closed
 				mainVC.contextPut("assignmentClosed", Boolean.TRUE);
@@ -450,7 +450,7 @@ public class GTAParticipantController extends GTAAbstractController {
 	}
 	
 	private void setSolutions(UserRequest ureq) {
-		Date availableDate = gtaNode.getModuleConfiguration().getDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER);
+		Date availableDate = getSolutionDueDate();
 		boolean visible = availableDate == null || availableDate.compareTo(new Date()) <= 0;
 		if(visible) {
 			File documentsDir = gtaManager.getSolutionsDirectory(courseEnv, gtaNode);
@@ -570,6 +570,7 @@ public class GTAParticipantController extends GTAAbstractController {
 		} else if(businessGroupChooserCtrl == source) {
 			if(event == Event.DONE_EVENT && businessGroupChooserCtrl.getSelectGroup() != null) {
 				cleanUpProcess();
+				resetDueDates();
 				assessedGroup = businessGroupChooserCtrl.getSelectGroup();
 				process(ureq);
 			}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java
index 2d5bd0c88893af2b2930ca85ea340864ec098c56..c0c79a17687270728a1855db663c4018381aa9b3 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTAWorkflowEditController.java
@@ -31,6 +31,7 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink;
 import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement;
 import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
 import org.olat.core.gui.components.form.flexible.elements.StaticTextElement;
+import org.olat.core.gui.components.form.flexible.elements.TextElement;
 import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
 import org.olat.core.gui.components.form.flexible.impl.FormEvent;
 import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
@@ -44,6 +45,7 @@ import org.olat.course.condition.AreaSelectionController;
 import org.olat.course.condition.GroupSelectionController;
 import org.olat.course.editor.CourseEditorEnv;
 import org.olat.course.nodes.GTACourseNode;
+import org.olat.course.nodes.gta.GTARelativeToDates;
 import org.olat.course.nodes.gta.GTAType;
 import org.olat.group.BusinessGroupService;
 import org.olat.group.BusinessGroupShort;
@@ -62,6 +64,11 @@ public class GTAWorkflowEditController extends FormBasicController {
 	
 	private static final String[] keys = new String[]{ "on" };
 	private static final String[] executionKeys = new String[]{ GTAType.group.name(), GTAType.individual.name() };
+	private static final String[] relativeDatesKeys = new String[] {
+		GTARelativeToDates.courseStart.name(), GTARelativeToDates.courseLaunch.name(),
+		GTARelativeToDates.enrollment.name()
+	};
+	private final String[] relativeDatesValues;
 	
 	private CloseableModalController cmc;
 	private AreaSelectionController areaSelectionCtrl;
@@ -71,8 +78,10 @@ public class GTAWorkflowEditController extends FormBasicController {
 	private FormLink chooseGroupButton, chooseAreaButton;
 	private StaticTextElement groupListEl, areaListEl;
 	private DateChooser assignmentDeadlineEl, submissionDeadlineEl, solutionVisibleAfterEl;
-	private MultipleSelectionElement taskAssignmentEl, submissionEl, reviewEl, revisionEl, sampleEl, gradingEl;
-	private FormLayoutContainer stepsCont;
+	private MultipleSelectionElement relativeDatesEl, taskAssignmentEl, submissionEl, reviewEl, revisionEl, sampleEl, gradingEl;
+	private FormLayoutContainer stepsCont, assignmentRelDeadlineCont, submissionRelDeadlineCont, solutionVisibleRelCont;
+	private TextElement assignementDeadlineDaysEl, submissionDeadlineDaysEl, solutionVisibleRelDaysEl;
+	private SingleSelection assignementDeadlineRelToEl, submissionDeadlineRelToEl, solutionVisibleRelToEl;
 	
 	private final GTACourseNode gtaNode;
 	private final ModuleConfiguration config;
@@ -90,15 +99,20 @@ public class GTAWorkflowEditController extends FormBasicController {
 		this.gtaNode = gtaNode;
 		this.config = gtaNode.getModuleConfiguration();
 		this.courseEditorEnv = courseEditorEnv;
+		
+		relativeDatesValues = new String[] {
+			translate("relative.to.course.start"),
+			translate("relative.to.course.launch"),
+			translate("relative.to.enrollment")
+		};
+		
 		initForm(ureq);
 	}
 
 	@Override
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
-		
 		String type = config.getStringValue(GTACourseNode.GTASK_TYPE);
 		
-
 		FormLayoutContainer typeCont = FormLayoutContainer.createDefaultFormLayout("type", getTranslator());
 		typeCont.setFormTitle(translate("task.type.title"));
 		typeCont.setFormDescription(translate("task.type.description"));
@@ -175,6 +189,11 @@ public class GTAWorkflowEditController extends FormBasicController {
 		stepsCont.setFormDescription(translate("task.steps.description"));
 		stepsCont.setRootForm(mainForm);
 		formLayout.add(stepsCont);
+
+		relativeDatesEl = uifactory.addCheckboxesHorizontal("relative.dates", "relative.dates", stepsCont, keys, new String[]{ "" });
+		relativeDatesEl.addActionListener(FormEvent.ONCHANGE);
+		boolean useRelativeDates = config.getBooleanSafe(GTACourseNode.GTASK_RELATIVE_DATES);
+		relativeDatesEl.select(keys[0], useRelativeDates);
 		
 		//assignment
 		String[] assignmentValues = new String[] { translate("enabled") };
@@ -186,7 +205,37 @@ public class GTAWorkflowEditController extends FormBasicController {
 		Date assignmentDeadline = config.getDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
 		assignmentDeadlineEl = uifactory.addDateChooser("assignementdeadline", "assignment.deadline", assignmentDeadline, stepsCont);
 		assignmentDeadlineEl.setDateChooserTimeEnabled(true);
-		assignmentDeadlineEl.setVisible(assignement);
+		assignmentDeadlineEl.setVisible(assignement && !useRelativeDates);
+		
+		String relativeDatePage = velocity_root + "/assignment_relative_date.html";
+		assignmentRelDeadlineCont = FormLayoutContainer.createCustomFormLayout("assignmentRelativeDeadline", getTranslator(), relativeDatePage);
+		assignmentRelDeadlineCont.setRootForm(mainForm);
+		assignmentRelDeadlineCont.setLabel("assignment.deadline", null);
+		assignmentRelDeadlineCont.setVisible(assignement && useRelativeDates);
+		stepsCont.add(assignmentRelDeadlineCont);
+		
+		int numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, -1);
+		String assignmentNumOfDays = numOfDays >= 0 ? Integer.toString(numOfDays) : "";
+		String assignmentRelativeTo = config.getStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO);
+		assignementDeadlineDaysEl = uifactory.addTextElement("assignment.numOfDays", null, 4, assignmentNumOfDays, assignmentRelDeadlineCont);
+		assignementDeadlineDaysEl.setDisplaySize(4);
+		assignementDeadlineDaysEl.setDomReplacementWrapperRequired(false);
+		assignementDeadlineRelToEl = uifactory
+				.addDropdownSingleselect("assignmentrelativeto", "assignment.relative.to", null, assignmentRelDeadlineCont, relativeDatesKeys, relativeDatesValues, null);
+		assignementDeadlineRelToEl.setDomReplacementWrapperRequired(false);
+		
+		boolean found = false;
+		if(StringHelper.containsNonWhitespace(assignmentRelativeTo)) {
+			for(String relativeDatesKey:relativeDatesKeys) {
+				if(relativeDatesKey.equals(assignmentRelativeTo)) {
+					assignementDeadlineRelToEl.select(relativeDatesKey, true);
+					found = true;
+				}
+			}
+		}
+		if(!found) {
+			assignementDeadlineRelToEl.select(relativeDatesKeys[0], true);
+		}
 		
 		//turning in
 		String[] submissionValues = new String[] { translate("submission.enabled") };
@@ -198,7 +247,37 @@ public class GTAWorkflowEditController extends FormBasicController {
 		Date submissionDeadline = config.getDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE);
 		submissionDeadlineEl = uifactory.addDateChooser("submitdeadline", "submit.deadline", submissionDeadline, stepsCont);
 		submissionDeadlineEl.setDateChooserTimeEnabled(true);
-		submissionDeadlineEl.setVisible(submit);
+		submissionDeadlineEl.setVisible(submit && !useRelativeDates);
+
+		//relative deadline
+		String submitPage = velocity_root + "/submit_relative_date.html";
+		submissionRelDeadlineCont = FormLayoutContainer.createCustomFormLayout("submitRelativeDeadline", getTranslator(), submitPage);
+		submissionRelDeadlineCont.setRootForm(mainForm);
+		submissionRelDeadlineCont.setLabel("submit.deadline", null);
+		submissionRelDeadlineCont.setVisible(submit && useRelativeDates);
+		stepsCont.add(submissionRelDeadlineCont);
+		
+		numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, -1);
+		String submitRelDays = numOfDays >= 0 ? Integer.toString(numOfDays) : "";
+		String submitRelTo = config.getStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO);
+		submissionDeadlineDaysEl = uifactory.addTextElement("submit.numOfDays", null, 4, submitRelDays, submissionRelDeadlineCont);
+		submissionDeadlineDaysEl.setDomReplacementWrapperRequired(false);
+		submissionDeadlineDaysEl.setDisplaySize(4);
+		submissionDeadlineRelToEl = uifactory
+				.addDropdownSingleselect("submitrelativeto", "submit.relative.to", null, submissionRelDeadlineCont, relativeDatesKeys, relativeDatesValues, null);
+		submissionDeadlineRelToEl.setDomReplacementWrapperRequired(false);
+		found = false;
+		if(StringHelper.containsNonWhitespace(submitRelTo)) {
+			for(String relativeDatesKey:relativeDatesKeys) {
+				if(relativeDatesKey.equals(submitRelTo)) {
+					submissionDeadlineRelToEl.select(relativeDatesKey, true);
+					found = true;
+				}
+			}
+		}
+		if(!found) {
+			submissionDeadlineRelToEl.select(relativeDatesKeys[0], true);
+		}
 		
 		//review and correction
 		String[] reviewValues = new String[] { translate("review.enabled") };
@@ -225,6 +304,36 @@ public class GTAWorkflowEditController extends FormBasicController {
 		solutionVisibleAfterEl = uifactory.addDateChooser("visibleafter", "sample.solution.visible.after", solutionVisibleAfter, stepsCont);
 		solutionVisibleAfterEl.setDateChooserTimeEnabled(true);
 		solutionVisibleAfterEl.setVisible(sample);
+
+		//relative deadline
+		String solutionPage = velocity_root + "/solution_relative_date.html";
+		solutionVisibleRelCont = FormLayoutContainer.createCustomFormLayout("solutionRelativeDeadline", getTranslator(), solutionPage);
+		solutionVisibleRelCont.setRootForm(mainForm);
+		solutionVisibleRelCont.setLabel("sample.solution.visible.after", null);
+		solutionVisibleRelCont.setVisible(submit && useRelativeDates);
+		stepsCont.add(solutionVisibleRelCont);
+		
+		numOfDays = config.getIntegerSafe(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE, -1);
+		String solutionRelDays = numOfDays >= 0 ? Integer.toString(numOfDays) : "";
+		String solutionRelTo = config.getStringValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO);
+		solutionVisibleRelDaysEl = uifactory.addTextElement("solution.numOfDays", null, 4, solutionRelDays, solutionVisibleRelCont);
+		solutionVisibleRelDaysEl.setDisplaySize(4);
+		solutionVisibleRelDaysEl.setDomReplacementWrapperRequired(false);
+		solutionVisibleRelToEl = uifactory
+				.addDropdownSingleselect("solutionrelativeto", "solution.relative.to", null, solutionVisibleRelCont, relativeDatesKeys, relativeDatesValues, null);
+		solutionVisibleRelToEl.setDomReplacementWrapperRequired(false);
+		found = false;
+		if(StringHelper.containsNonWhitespace(solutionRelTo)) {
+			for(String relativeDatesKey:relativeDatesKeys) {
+				if(relativeDatesKey.equals(solutionRelTo)) {
+					solutionVisibleRelToEl.select(relativeDatesKey, true);
+					found = true;
+				}
+			}
+		}
+		if(!found) {
+			solutionVisibleRelToEl.select(relativeDatesKeys[0], true);
+		}
 		
 		//grading
 		String[] gradingValues = new String[] { translate("enabled") };
@@ -263,6 +372,22 @@ public class GTAWorkflowEditController extends FormBasicController {
 			}
 		}
 		
+		boolean relativeDates = relativeDatesEl.isAtLeastSelected(1);
+		assignementDeadlineDaysEl.clearError();
+		if(relativeDates && taskAssignmentEl.isAtLeastSelected(1)) {
+			allOk &= validateIntegerOrEmpty(assignementDeadlineDaysEl);
+		}
+		
+		submissionDeadlineDaysEl.clearError();
+		if(relativeDates && submissionEl.isAtLeastSelected(1)) {
+			allOk &= validateIntegerOrEmpty(submissionDeadlineDaysEl);
+		}
+		
+		solutionVisibleRelDaysEl.clearError();
+		if(relativeDates && sampleEl.isAtLeastSelected(1)) {
+			allOk &= validateIntegerOrEmpty(solutionVisibleRelDaysEl);
+		}
+		
 		taskAssignmentEl.clearError();
 		if(!taskAssignmentEl.isAtLeastSelected(1) && !submissionEl.isAtLeastSelected(1)
 				&& !reviewEl.isAtLeastSelected(1) && !revisionEl.isAtLeastSelected(1)
@@ -274,6 +399,21 @@ public class GTAWorkflowEditController extends FormBasicController {
 
 		return allOk & super.validateFormLogic(ureq);
 	}
+	
+	private boolean validateIntegerOrEmpty(TextElement textEl) {
+		boolean allOk = true;
+		textEl.clearError();
+		String val = textEl.getValue();
+		if(StringHelper.containsNonWhitespace(val)) {
+			if(StringHelper.isLong(val)) {
+				
+			} else {
+				textEl.setErrorKey("integer.element.int.error", null);
+				allOk &= false;
+			}
+		}
+		return allOk;
+	}
 
 	@Override
 	protected void formOK(UserRequest ureq) {
@@ -287,18 +427,31 @@ public class GTAWorkflowEditController extends FormBasicController {
 			config.setList(GTACourseNode.GTASK_GROUPS, new ArrayList<Long>(0));
 		}
 		
+		boolean relativeDates = relativeDatesEl.isAtLeastSelected(1);
+		config.setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, relativeDates);
+		
 		boolean assignment = taskAssignmentEl.isAtLeastSelected(1);
 		config.setBooleanEntry(GTACourseNode.GTASK_ASSIGNMENT, assignment);
-		if(assignment && assignmentDeadlineEl.getDate() != null) {
-			config.setDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE, assignmentDeadlineEl.getDate());
+		if(assignment) {
+			if(relativeDates) {
+				setRelativeDates(assignementDeadlineDaysEl, GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE,
+						assignementDeadlineRelToEl, GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO);
+			} else if(assignmentDeadlineEl.getDate() != null) {
+				config.setDateValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE, assignmentDeadlineEl.getDate());
+			}
 		} else {
 			config.remove(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE);
 		}
 		
 		boolean turningIn = submissionEl.isAtLeastSelected(1);
 		config.setBooleanEntry(GTACourseNode.GTASK_SUBMIT, turningIn);
-		if(turningIn && submissionDeadlineEl.getDate() != null) {
-			config.setDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE, submissionDeadlineEl.getDate());
+		if(turningIn) {
+			if(relativeDates) {
+				setRelativeDates(submissionDeadlineDaysEl, GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE,
+						submissionDeadlineRelToEl, GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO);
+			} else {
+				config.setDateValue(GTACourseNode.GTASK_SUBMIT_DEADLINE, submissionDeadlineEl.getDate());
+			}
 		} else {
 			config.remove(GTACourseNode.GTASK_SUBMIT_DEADLINE);
 		}
@@ -308,25 +461,47 @@ public class GTAWorkflowEditController extends FormBasicController {
 		
 		boolean sample = sampleEl.isAtLeastSelected(1);
 		config.setBooleanEntry(GTACourseNode.GTASK_SAMPLE_SOLUTION, sample);
-		if(sample && solutionVisibleAfterEl.getDate() != null) {
-			config.setDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER, solutionVisibleAfterEl.getDate());
+		if(sample) {
+			if(relativeDates) {
+				setRelativeDates(solutionVisibleRelDaysEl, GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE,
+						solutionVisibleRelToEl, GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER_RELATIVE_TO);
+			} else if(solutionVisibleAfterEl.getDate() != null) {
+				config.setDateValue(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER, solutionVisibleAfterEl.getDate());
+			}
 		} else {
 			config.remove(GTACourseNode.GTASK_SAMPLE_SOLUTION_VISIBLE_AFTER);
 		}
 
 		config.setBooleanEntry(GTACourseNode.GTASK_GRADING, gradingEl.isAtLeastSelected(1));
-		
 		fireEvent(ureq, Event.DONE_EVENT);
 	}
+	
+	private void setRelativeDates(TextElement daysEl, String daysKey, SingleSelection relativeToEl, String relativeToKey) {
+		String val = daysEl.getValue();
+		if(StringHelper.isLong(val)) {
+			try {
+				config.setIntValue(daysKey,  Integer.parseInt(val));
+			} catch (NumberFormatException e) {
+				logWarn("", e);
+			}
+			
+			String relativeTo = relativeToEl.getSelectedKey();
+			config.setStringValue(relativeToKey, relativeTo);
+		}
+	}
 
 	@Override
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
 		if(submissionEl == source) {
-			submissionDeadlineEl.setVisible(submissionEl.isAtLeastSelected(1));
+			updateSubmissionDeadline();
 		} else if(taskAssignmentEl == source) {
-			assignmentDeadlineEl.setVisible(taskAssignmentEl.isAtLeastSelected(1));
+			updateAssignmentDeadline();
 		} else if(sampleEl == source) {
-			solutionVisibleAfterEl.setVisible(sampleEl.isAtLeastSelected(1));
+			updateSolutionDeadline();
+		} else if(relativeDatesEl == source) {
+			updateAssignmentDeadline();
+			updateSubmissionDeadline();
+			updateSolutionDeadline();
 		} else if(chooseGroupButton == source) {
 			doChooseGroup(ureq);
 		} else if(chooseAreaButton == source) {
@@ -336,6 +511,27 @@ public class GTAWorkflowEditController extends FormBasicController {
 		super.formInnerEvent(ureq, source, event);
 	}
 	
+	private void updateAssignmentDeadline() {
+		boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1);
+		boolean assignment = taskAssignmentEl.isAtLeastSelected(1);
+		assignmentDeadlineEl.setVisible(assignment && !userRelativeDate);
+		assignmentRelDeadlineCont.setVisible(assignment && userRelativeDate);
+	}
+	
+	private void updateSubmissionDeadline() {
+		boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1);
+		boolean submit = submissionEl.isAtLeastSelected(1);
+		submissionDeadlineEl.setVisible(submit && !userRelativeDate);
+		submissionRelDeadlineCont.setVisible(submit && userRelativeDate);
+	}
+	
+	private void updateSolutionDeadline() {
+		boolean userRelativeDate = relativeDatesEl.isAtLeastSelected(1);
+		boolean solution = sampleEl.isAtLeastSelected(1);
+		solutionVisibleAfterEl.setVisible(solution && !userRelativeDate);
+		solutionVisibleRelCont.setVisible(solution && userRelativeDate);
+	}
+	
 	@Override
 	protected void event(UserRequest ureq, Controller source, Event event) {
 		if(groupSelectionCtrl == source) {
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html
new file mode 100644
index 0000000000000000000000000000000000000000..f1b4c29ad6f7ab910720bcb188a19b4619154d1c
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/assignment_relative_date.html
@@ -0,0 +1,6 @@
+<div class="form-inline">$r.render("assignment.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("assignment.relative.to")</div>
+#if($f.hasError("assignment.numOfDays"))
+	<div class="form-inline">
+		$r.render("assignment.numOfDays_ERROR")
+	</div>
+#end
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html
new file mode 100644
index 0000000000000000000000000000000000000000..0e6f0ac5a33db349489cc823976f1827e10d1d3a
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/solution_relative_date.html
@@ -0,0 +1,6 @@
+<div class="form-inline">$r.render("solution.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("solution.relative.to")</div>
+#if($f.hasError("solution.numOfDays"))
+	<div class="form-inline">
+		$r.render("solution.numOfDays_ERROR")
+	</div>
+#end
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html b/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html
new file mode 100644
index 0000000000000000000000000000000000000000..e190568d69f4ef1a172ff311f52e92cdf04213c5
--- /dev/null
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_content/submit_relative_date.html
@@ -0,0 +1,6 @@
+<div class="form-inline">$r.render("submit.numOfDays") <span class="form-control-static">$r.translate("days.after")</span> $r.render("submit.relative.to")</div>
+#if($f.hasError("submit.numOfDays"))
+	<div class="form-inline">
+		$r.render("submit.numOfDays_ERROR")
+	</div>
+#end
\ No newline at end of file
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
index 0f24f1f1a1c837a25a9e36eb8aaa87efefc28dff..baf1a7fb5d8d634f327274ee315673d50c6b2ba3 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_de.properties
@@ -33,6 +33,7 @@ confirm.delete.solution.title=L\u00F6sung l\u00F6schen
 confirmation.title=Abgabe Best\u00E4tigung
 create.areas=Lernbereich erstellen
 create.groups=Gruppe erstellen
+days.after=Tage nach
 document=Abgegebene Dokumente
 document.date=Datum
 download.task=Aufgabe herunterladen
@@ -85,6 +86,10 @@ pane.tab.workflow=Workflow
 preview=$org.olat.course.nodes.ta\:form.task.preview
 preview.disabled=$org.olat.course.nodes.ta\:form.task.without.preview
 preview.enabled=$org.olat.course.nodes.ta\:form.task.with.preview
+relative.dates=Relative Datum
+relative.to.course.start=Kurs Beginn
+relative.to.course.launch=Kurs erstes Besuch
+relative.to.enrollment=Enrollment
 replace=Austauschen
 replace.document=Dokument austauschen
 review.and.correction=\u00DCberarbeitung und Korrektur
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
index 28491b9983253e3806f151b7c2d1d684bf1d07e3..ecb811532129fb2027dc0cc7893d6d00d679001f 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
+++ b/src/main/java/org/olat/course/nodes/gta/ui/_i18n/LocalStrings_en.properties
@@ -33,6 +33,7 @@ confirm.delete.solution.title=Delete submitted solution
 confirmation.title=Submit confirmation
 create.areas=Create learning area
 create.groups=Create group
+days.after=Days after
 document=Submitted documents
 document.date=Date
 download.task=Download task
@@ -85,6 +86,10 @@ pane.tab.workflow=Workflow
 preview=$org.olat.course.nodes.ta\:form.task.preview
 preview.disabled=$org.olat.course.nodes.ta\:form.task.without.preview
 preview.enabled=$org.olat.course.nodes.ta\:form.task.with.preview
+relative.dates=Relative dates
+relative.to.course.start=Course begin
+relative.to.course.launch=Course first launch
+relative.to.enrollment=Enrollment
 replace=Replace
 replace.document=Replace document
 review.and.correction=Review and correction
diff --git a/src/main/java/org/olat/repository/RepositoryService.java b/src/main/java/org/olat/repository/RepositoryService.java
index a6705bf391d5f48d6c232db2cd303ef756140489..5bf667a94ae41a116a464c5cfc31068416dab289 100644
--- a/src/main/java/org/olat/repository/RepositoryService.java
+++ b/src/main/java/org/olat/repository/RepositoryService.java
@@ -20,8 +20,10 @@
 package org.olat.repository;
 
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 
 import org.olat.basesecurity.Group;
 import org.olat.basesecurity.IdentityRef;
@@ -110,6 +112,24 @@ public interface RepositoryService {
 	 */
 	public int countMembers(List<? extends RepositoryEntryRef> res);
 	
+	/**
+	 * Return the smallest enrollment date.
+	 * 
+	 * @param re
+	 * @param identity
+	 * @return
+	 */
+	public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles);
+	
+	/**
+	 * Return the smallest enrollment date.
+	 * 
+	 * @param re
+	 * @param identity
+	 * @return
+	 */
+	public Map<Long,Date> getEnrollmentDates(RepositoryEntryRef re, String... roles);
+	
 	/**
 	 * @param re The repository entry
 	 * @return True if the configuration allowed user to leave the entry right now
diff --git a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java
index 62757174ac1311c3d5c2f56cafb793d7d53b2a3e..ece81e4c76fc0d8e6d91adf54ee57ab4986e8bff 100644
--- a/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java
+++ b/src/main/java/org/olat/repository/manager/RepositoryEntryRelationDAO.java
@@ -22,9 +22,11 @@ package org.olat.repository.manager;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.persistence.EntityManager;
@@ -273,6 +275,81 @@ public class RepositoryEntryRelationDAO {
 		return count == null ? 0 : count.intValue();
 	}
 	
+	public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles) {
+		if(re == null || identity == null) return null;
+		
+		List<String> roleList = null;
+		if(roles != null && roles.length > 0 && roles[0] != null) {
+			roleList = new ArrayList<>(roles.length);
+			for(String role:roles) {
+				roleList.add(role);
+			}
+		}
+
+		StringBuilder sb = new StringBuilder();
+		sb.append("select min(members.creationDate) from ").append(RepositoryEntry.class.getName()).append(" as v")
+		  .append(" inner join v.groups as relGroup")
+		  .append(" inner join relGroup.group as baseGroup")
+		  .append(" inner join baseGroup.members as members")
+		  .append(" where v.key=:repoKey and members.identity.key=:identityKey");
+		if(roleList != null && roleList.size() > 0) {
+			sb.append(" and members.role in (:roles)");
+		}
+
+		TypedQuery<Date> datesQuery = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Date.class)
+				.setParameter("repoKey", re.getKey())
+				.setParameter("identityKey", identity.getKey());
+		if(roleList != null && roleList.size() > 0) {
+			datesQuery.setParameter("roles", roleList);
+		}
+		
+		List<Date> dates = datesQuery.getResultList();
+		return dates.isEmpty() ? null : dates.get(0);
+	}
+	
+	public Map<Long,Date> getEnrollmentDates(RepositoryEntryRef re, String... roles) {
+		if(re == null) return null;
+		
+		List<String> roleList = null;
+		if(roles != null && roles.length > 0 && roles[0] != null) {
+			roleList = new ArrayList<>(roles.length);
+			for(String role:roles) {
+				roleList.add(role);
+			}
+		}
+
+		StringBuilder sb = new StringBuilder();
+		sb.append("select members.identity.key, min(members.creationDate) from ").append(RepositoryEntry.class.getName()).append(" as v")
+		  .append(" inner join v.groups as relGroup")
+		  .append(" inner join relGroup.group as baseGroup")
+		  .append(" inner join baseGroup.members as members")
+		  .append(" where v.key=:repoKey");
+		if(roleList != null && roleList.size() > 0) {
+			sb.append(" and members.role in (:roles)");
+		}
+		sb.append(" group by members.identity.key");
+
+		TypedQuery<Object[]> datesQuery = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), Object[].class)
+				.setParameter("repoKey", re.getKey());
+		if(roleList != null && roleList.size() > 0) {
+			datesQuery.setParameter("roles", roleList);
+		}
+		
+		List<Object[]> dateList = datesQuery.getResultList();
+		Map<Long,Date> dateMap = new HashMap<>((dateList.size() * 2) + 1);
+		for(Object[] dateArr:dateList) {
+			Long key = (Long)dateArr[0];
+			Date date = (Date)dateArr[1];
+			if(key != null && date != null) {
+				dateMap.put(key, date);
+			}
+		}
+
+		return dateMap;
+	}
+	
 	
 	public List<Long> getAuthorKeys(RepositoryEntryRef re) {
 		
diff --git a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
index 8aaa4288bb276117df47a5926fa6bb90bba0122f..3978977bff08bfb96f009d2e0be31a2000fe6f75 100644
--- a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
+++ b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
@@ -26,6 +26,7 @@ import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 
 import org.olat.basesecurity.BaseSecurity;
@@ -440,6 +441,16 @@ public class RepositoryServiceImpl implements RepositoryService {
 	public int countMembers(List<? extends RepositoryEntryRef> res) {
 		return reToGroupDao.countMembers(res);
 	}
+	
+	@Override
+	public Date getEnrollmentDate(RepositoryEntryRef re, IdentityRef identity, String... roles) {
+		return reToGroupDao.getEnrollmentDate(re, identity, roles);
+	}
+
+	@Override
+	public Map<Long, Date> getEnrollmentDates(RepositoryEntryRef re, String... roles) {
+		return reToGroupDao.getEnrollmentDates(re, roles);
+	}
 
 	@Override
 	public List<Long> getAuthors(RepositoryEntryRef re) {
diff --git a/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java b/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java
index 8f0f97db9a2daf4467f334ea034f7e831d061999..0505275d38538fe477ea8873e532ec7d3f0f9ac8 100644
--- a/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java
+++ b/src/test/java/org/olat/course/assessment/manager/UserCourseInformationsManagerTest.java
@@ -145,6 +145,32 @@ public class UserCourseInformationsManagerTest extends OlatTestCase {
 		Assert.assertNotNull(launchDates.get(user2.getKey()));
 	}
 	
+	@Test
+	public void getInitialLaunchDates_noIdentites() {
+		Identity user1 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-7-");
+		Identity user2 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-8-");
+		Identity user3 = JunitTestHelper.createAndPersistIdentityAsRndUser("user-launch-9-");
+		ICourse course = CoursesWebService.createEmptyCourse(user1, "course-launch-dates", "course long name", null);
+		dbInstance.commitAndCloseSession();
+		
+		userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user1, true);
+		userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user2, true);
+		userCourseInformationsManager.updateUserCourseInformations(course.getResourceableId(), user3, true);
+		dbInstance.commitAndCloseSession();
+		
+		//get all launch dates
+		Map<Long,Date> launchDates = userCourseInformationsManager.getInitialLaunchDates(course.getResourceableId());
+		Assert.assertNotNull(launchDates);
+		Assert.assertEquals(3, launchDates.size());
+		Assert.assertTrue(launchDates.containsKey(user1.getKey()));
+		Assert.assertNotNull(launchDates.get(user1.getKey()));
+		Assert.assertTrue(launchDates.containsKey(user2.getKey()));
+		Assert.assertNotNull(launchDates.get(user2.getKey()));
+		Assert.assertTrue(launchDates.containsKey(user3.getKey()));
+		Assert.assertNotNull(launchDates.get(user3.getKey()));
+	}
+	
+	
 	
 	/**
 	 * This test is to analyze a red screen
diff --git a/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java b/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java
index 27de702b6b9fef27b7b3ffe6d32ae5af22075549..f482bfe454ad6afdd713ed23b7d0140c391855c9 100644
--- a/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java
+++ b/src/test/java/org/olat/course/nodes/gta/rule/GTAReminderRuleTest.java
@@ -22,15 +22,22 @@ package org.olat.course.nodes.gta.rule;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.Date;
 import java.util.List;
+import java.util.UUID;
 
 import org.junit.Assert;
 import org.junit.Test;
 import org.olat.basesecurity.GroupRoles;
+import org.olat.basesecurity.model.GroupMembershipImpl;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.Identity;
+import org.olat.course.ICourse;
+import org.olat.course.assessment.manager.UserCourseInformationsManager;
+import org.olat.course.assessment.model.UserCourseInfosImpl;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.AssignmentResponse;
+import org.olat.course.nodes.gta.GTARelativeToDates;
 import org.olat.course.nodes.gta.GTAType;
 import org.olat.course.nodes.gta.TaskList;
 import org.olat.course.nodes.gta.manager.GTAManagerImpl;
@@ -41,7 +48,11 @@ import org.olat.modules.reminder.model.ReminderRuleImpl;
 import org.olat.modules.reminder.rule.LaunchUnit;
 import org.olat.modules.vitero.model.GroupRole;
 import org.olat.repository.RepositoryEntry;
+import org.olat.repository.manager.RepositoryEntryLifecycleDAO;
 import org.olat.repository.manager.RepositoryEntryRelationDAO;
+import org.olat.repository.model.RepositoryEntryLifecycle;
+import org.olat.repository.model.RepositoryEntryToGroupRelation;
+import org.olat.restapi.repository.course.CoursesWebService;
 import org.olat.test.JunitTestHelper;
 import org.olat.test.OlatTestCase;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -61,9 +72,13 @@ public class GTAReminderRuleTest extends OlatTestCase {
 	@Autowired
 	private BusinessGroupDAO businessGroupDao;
 	@Autowired
+	private RepositoryEntryLifecycleDAO reLifeCycleDao;
+	@Autowired
 	private BusinessGroupRelationDAO businessGroupRelationDao;
 	@Autowired
 	private RepositoryEntryRelationDAO repositoryEntryRelationDao;
+	@Autowired
+	private UserCourseInformationsManager userCourseInformationsManager;
 	
 
 	@Autowired
@@ -211,6 +226,179 @@ public class GTAReminderRuleTest extends OlatTestCase {
 		Assert.assertTrue(toRemind.contains(participant4));
 	}
 	
+	@Test
+	public void assignTask_relativeToDateEnrollment() {
+		//prepare a course with a volatile task
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-2");
+		RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false);
+		addEnrollmentDate(re, participant1, GroupRoles.participant, -12, Calendar.DATE);
+		addEnrollmentDate(re, participant2, GroupRoles.participant, -5, Calendar.DATE);
+		dbInstance.commit();
+		
+		// create a fake node
+		GTACourseNode node = new GTACourseNode();
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name());
+		node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true);
+		node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, 15);
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO, GTARelativeToDates.enrollment.name());
+		
+		// need the task list
+		TaskList tasks = gtaManager.createIfNotExists(re, node);
+		Assert.assertNotNull(tasks);
+		dbInstance.commit();
+		
+		// participant 1 has still 3 days to choose a task
+		// participant 2 has still 10 days to choose a task
+		
+		{ // check before 1 day
+			ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.day);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(0, all.size());
+		}
+
+		{ // check before 5 days
+			ReminderRuleImpl rule = getAssignedTaskRules(5, LaunchUnit.day);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(1, all.size());
+			Assert.assertTrue(all.contains(participant1));
+		}
+		
+		{ // check before 1 week
+			ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.week);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(1, all.size());
+			Assert.assertTrue(all.contains(participant1));
+		}
+		
+		{ // check before 1 month
+			ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.month);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+	}
+	
+	private void addEnrollmentDate(RepositoryEntry entry, Identity id, GroupRoles role, int amount, int field) {
+		RepositoryEntryToGroupRelation rel = entry.getGroups().iterator().next();
+		rel.getGroup();
+		
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(new Date());
+		cal.add(field, amount);
+		
+		GroupMembershipImpl membership = new GroupMembershipImpl();
+		membership.setCreationDate(cal.getTime());
+		membership.setLastModified(cal.getTime());
+		membership.setGroup(rel.getGroup());
+		membership.setIdentity(id);
+		membership.setRole(role.name());
+		dbInstance.getCurrentEntityManager().persist(membership);
+		dbInstance.commit();
+	}
+	
+	@Test
+	public void assignTask_relativeToInitialLaunchDate() {
+		//create a course with 3 members
+		Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-1");
+		Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-2");
+		Identity id3 = JunitTestHelper.createAndPersistIdentityAsRndUser("initial-launch-3");
+
+		ICourse course = CoursesWebService.createEmptyCourse(null, "initial-launch-dates", "course long name", null);
+		RepositoryEntry re = course.getCourseEnvironment().getCourseGroupManager().getCourseEntry();
+		repositoryEntryRelationDao.addRole(id1, re, GroupRoles.participant.name());
+		repositoryEntryRelationDao.addRole(id2, re, GroupRoles.participant.name());
+		repositoryEntryRelationDao.addRole(id3, re, GroupRoles.participant.name());
+		dbInstance.commit();
+		
+		//create user course infos
+		Long courseResId = course.getCourseEnvironment().getCourseResourceableId();
+		userCourseInformationsManager.updateUserCourseInformations(courseResId, id1, true);
+		userCourseInformationsManager.updateUserCourseInformations(courseResId, id2, true);
+		userCourseInformationsManager.updateUserCourseInformations(courseResId, id3, true);
+		dbInstance.commit();
+		
+		//fake the date
+		updateInitialLaunchDate(courseResId, id1, -5, Calendar.DATE);
+		updateInitialLaunchDate(courseResId, id2, -35, Calendar.DATE);
+		updateInitialLaunchDate(courseResId, id3, -75, Calendar.DATE);
+		dbInstance.commitAndCloseSession();
+		
+		// create a fake node
+		GTACourseNode node = new GTACourseNode();
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name());
+		node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true);
+		node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE, 40);
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_ASSIGNMENT_DEADLINE_RELATIVE_TO, GTARelativeToDates.courseLaunch.name());
+		
+		// need the task list
+		TaskList tasks = gtaManager.createIfNotExists(re, node);
+		Assert.assertNotNull(tasks);
+		dbInstance.commit();		
+
+		{ // check 3 days
+			ReminderRuleImpl rule = getAssignedTaskRules(3, LaunchUnit.day);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(1, all.size());
+			Assert.assertTrue(all.contains(id3));
+		}
+		
+		{ // check 5 days
+			ReminderRuleImpl rule = getAssignedTaskRules(5, LaunchUnit.day);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(id2));
+			Assert.assertTrue(all.contains(id3));
+		}
+		
+		{ // check 1 week
+			ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.week);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(id2));
+			Assert.assertTrue(all.contains(id3));
+		}
+		
+		{ // check 1 month
+			ReminderRuleImpl rule = getAssignedTaskRules(1, LaunchUnit.month);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(id2));
+			Assert.assertTrue(all.contains(id3));
+		}
+		
+		{ // check 2 month
+			ReminderRuleImpl rule = getAssignedTaskRules(2, LaunchUnit.month);
+			List<Identity> all = assignTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(3, all.size());
+			Assert.assertTrue(all.contains(id1));
+			Assert.assertTrue(all.contains(id2));
+			Assert.assertTrue(all.contains(id3));
+		}
+
+	}
+	
+	private void updateInitialLaunchDate(Long courseResId, Identity id, int amount, int field) {
+		UserCourseInfosImpl userCourseInfos = (UserCourseInfosImpl)userCourseInformationsManager.getUserCourseInformations(courseResId, id);
+		Date initialLaunch = userCourseInfos.getInitialLaunch();
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(initialLaunch);
+		cal.add(field, amount);
+		userCourseInfos.setInitialLaunch(cal.getTime());
+		dbInstance.getCurrentEntityManager().merge(userCourseInfos);
+		dbInstance.commit();
+	}
+	
 	@Test
 	public void submitTask_individual() {
 		//prepare a course with a volatile task
@@ -297,6 +485,112 @@ public class GTAReminderRuleTest extends OlatTestCase {
 		}
 	}
 	
+	@Test
+	public void submitTask_relativeLifecycle() {
+		//prepare a course with a volatile task
+		Identity participant1 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-1");
+		Identity participant2 = JunitTestHelper.createAndPersistIdentityAsRndUser("gta-user-2");
+		RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry("", false);
+		repositoryEntryRelationDao.addRole(participant1, re, GroupRoles.participant.name());
+		repositoryEntryRelationDao.addRole(participant2, re, GroupRoles.participant.name());
+		dbInstance.commit();
+		
+		String label = "Life cycle for relative date";
+		String softKey = UUID.randomUUID().toString();
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(new Date());
+		cal.add(Calendar.DATE, -5);
+		Date from = cal.getTime();
+		cal.add(Calendar.DATE, 20);
+		Date to = cal.getTime();
+		RepositoryEntryLifecycle lifecycle = reLifeCycleDao.create(label, softKey, true, from, to);
+		re.setLifecycle(lifecycle);
+		re = dbInstance.getCurrentEntityManager().merge(re);
+		dbInstance.commit();
+		
+		//create a fake node with a relative submit deadline 15 days after the start of the course
+		GTACourseNode node = new GTACourseNode();
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_TYPE, GTAType.individual.name());
+		node.getModuleConfiguration().setBooleanEntry(GTACourseNode.GTASK_RELATIVE_DATES, true);
+		node.getModuleConfiguration().setIntValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE, 15);
+		node.getModuleConfiguration().setStringValue(GTACourseNode.GTASK_SUBMIT_DEADLINE_RELATIVE_TO, GTARelativeToDates.courseStart.name());
+
+		TaskList tasks = gtaManager.createIfNotExists(re, node);
+		Assert.assertNotNull(tasks);
+		dbInstance.commitAndCloseSession();
+		
+		//the course has start 5 days before, deadline is 15 days after it
+		//conclusion the deadline is 10 days from now
+		
+		{ // check before 5 days 
+			ReminderRuleImpl rule = getSubmitTaskRules(5, LaunchUnit.day);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(0, all.size());
+		}
+		
+		{ // check before 1 week 
+			ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.week);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(0, all.size());
+		}
+		
+		{ // check before 10 days 
+			ReminderRuleImpl rule = getSubmitTaskRules(10, LaunchUnit.day);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+		
+		{ // check before 2 days 
+			ReminderRuleImpl rule = getSubmitTaskRules(10, LaunchUnit.week);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+		
+		{ // check before 30 days 
+			ReminderRuleImpl rule = getSubmitTaskRules(30, LaunchUnit.day);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+		
+		{ // check before 1 months 
+			ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.month);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+		
+		{ // check before 5 months 
+			ReminderRuleImpl rule = getSubmitTaskRules(5, LaunchUnit.month);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+		
+		{ // check before 1 year 
+			ReminderRuleImpl rule = getSubmitTaskRules(1, LaunchUnit.year);
+			List<Identity> all = submissionTaskRuleSPI.evaluateRule(re, node, rule);
+			
+			Assert.assertEquals(2, all.size());
+			Assert.assertTrue(all.contains(participant1));
+			Assert.assertTrue(all.contains(participant2));
+		}
+	}
+	
 	private ReminderRuleImpl getSubmitTaskRules(int amount, LaunchUnit unit) {
 		ReminderRuleImpl rule = new ReminderRuleImpl();
 		rule.setType(SubmissionTaskRuleSPI.class.getSimpleName());
diff --git a/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java b/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java
index a37d6dcaa89455edda31e358b39143c490e10bcd..c4b576c8f6c9998cffae266d0f322cfc0e984eb7 100644
--- a/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java
+++ b/src/test/java/org/olat/repository/manager/RepositoryEntryRelationDAOTest.java
@@ -21,7 +21,9 @@ package org.olat.repository.manager;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
 
 import org.junit.Assert;
@@ -181,6 +183,65 @@ public class RepositoryEntryRelationDAOTest extends OlatTestCase {
 		Assert.assertEquals(4, numOfMembers);
 	}
 	
+	@Test
+	public void getEnrollmentDate() {
+		Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-1-");
+		Identity wid = JunitTestHelper.createAndPersistIdentityAsRndUser("not-enroll-date-1-");
+		RepositoryEntry re = repositoryService.create("Rei Ayanami", "rel", "rel", null, null);
+		dbInstance.commit();
+		repositoryEntryRelationDao.addRole(id, re, GroupRoles.owner.name());
+		repositoryEntryRelationDao.addRole(id, re, GroupRoles.participant.name());
+		dbInstance.commit();
+		
+		//enrollment date
+		Date enrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id);
+		Assert.assertNotNull(enrollmentDate);
+		
+		//this user isn't enrolled
+		Date withoutEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, wid);
+		Assert.assertNull(withoutEnrollmentDate);
+		
+		//as participant
+		Date participantEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.participant.name());
+		Assert.assertNotNull(participantEnrollmentDate);
+		//as owner
+		Date ownerEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.owner.name());
+		Assert.assertNotNull(ownerEnrollmentDate);
+		//is not enrolled as coached
+		Date coachEnrollmentDate = repositoryEntryRelationDao.getEnrollmentDate(re, id, GroupRoles.coach.name());
+		Assert.assertNull(coachEnrollmentDate);
+	}
+	
+	@Test
+	public void getEnrollmentDates() {
+		Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-2-");
+		Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("enroll-date-3-");
+		Identity wid = JunitTestHelper.createAndPersistIdentityAsRndUser("not-enroll-date-2-");
+		RepositoryEntry re = repositoryService.create("Rei Ayanami", "rel", "rel", null, null);
+		dbInstance.commit();
+		repositoryEntryRelationDao.addRole(id1, re, GroupRoles.owner.name());
+		repositoryEntryRelationDao.addRole(id2, re, GroupRoles.participant.name());
+		dbInstance.commit();
+		
+		//enrollment date
+		Map<Long,Date> enrollmentDates = repositoryEntryRelationDao.getEnrollmentDates(re);
+		Assert.assertNotNull(enrollmentDates);
+		Assert.assertEquals(2, enrollmentDates.size());
+		Assert.assertTrue(enrollmentDates.containsKey(id1.getKey()));
+		Assert.assertTrue(enrollmentDates.containsKey(id2.getKey()));
+		Assert.assertFalse(enrollmentDates.containsKey(wid.getKey()));
+	}
+
+	@Test
+	public void getEnrollmentDates_emptyCourse() {
+		//enrollment of an empty course
+		RepositoryEntry notEnrolledRe = repositoryService.create("Rei Ayanami", "rel", "rel", null, null);
+		dbInstance.commit();
+		Map<Long,Date> notEnrollmentDates = repositoryEntryRelationDao.getEnrollmentDates(notEnrolledRe);
+		Assert.assertNotNull(notEnrollmentDates);
+		Assert.assertEquals(0, notEnrollmentDates.size());
+	}
+	
 	@Test
 	public void getAuthorKeys() {
 		Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("auth-1-");