Problem Statement
Design a Google Calendar Scheduler. The scheduler must support creating user calendars, booking meetings (events), checking for scheduling conflicts (time overlaps) dynamically, and handling complex recurrence rules (like daily occurrences, or weekly repetitions on specific days) by expanding them into concrete event instances.
Design Decisions & Patterns Used
Representing calendar entries requires separating the **abstract event definition** (the rule) from its **concrete occurrences** (the event instances). When booking a recurring event, we must evaluate the rule and generate instances over a target date range. We can then check for conflicts by comparing these generated time ranges against existing instances in the user's calendar.
We will utilize the following Design Patterns:
- Strategy Pattern: Defining interchangeable algorithms (rules) to calculate event occurrences (e.g., Daily vs. Weekly recurrence rules).
- Factory Pattern: Instantiating recurrence rules dynamically based on rule properties.
Functional Requirements
- Create events with a start time, duration, and optional recurrence rules.
- Support one-time events and recurring events (e.g., Daily, or Weekly on specific days like Monday and Wednesday).
- Expand recurring events into concrete occurrence instances over a target date range.
- Check for scheduling conflicts (overlapping times) before committing a booking.
Objects Required
RecurrenceRule(Interface defining occurrence calculation contracts)NoRecurrence,DailyRecurrence,WeeklyRecurrence(Concrete rule algorithms)Event(Abstract definition representing the booked meeting rules)EventInstance(Value object tracking concrete start and end times)Calendar(User calendar holding booked events and checking overlaps)
RecurrenceRule Interface & Implementations
The RecurrenceRule interface defines the contract for generating occurrences. Applying the **Strategy Pattern** allows adding new recurrence behaviors without altering the core scheduler.
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public interface RecurrenceRule {
List<LocalDateTime> generateOccurrences(LocalDateTime start, LocalDateTime rangeEnd);
}
Let's implement the concrete recurrence algorithms:
public class NoRecurrence implements RecurrenceRule {
@Override
public List<LocalDateTime> generateOccurrences(LocalDateTime start, LocalDateTime rangeEnd) {
if (start.isAfter(rangeEnd)) return Collections.emptyList();
return Collections.singletonList(start);
}
}
The NoRecurrence rule represents a one-time event, returning only the start time.
public class DailyRecurrence implements RecurrenceRule {
@Override
public List<LocalDateTime> generateOccurrences(LocalDateTime start, LocalDateTime rangeEnd) {
List<LocalDateTime> occurrences = new ArrayList<>();
LocalDateTime current = start;
while (!current.isAfter(rangeEnd)) {
occurrences.add(current);
current = current.plusDays(1); // Increment by one day
}
return occurrences;
}
}
The DailyRecurrence rule increments the timestamp by one day sequentially until it reaches the range boundary.
import java.time.DayOfWeek;
public class WeeklyRecurrence implements RecurrenceRule {
private final Set<DayOfWeek> daysOfWeek;
public WeeklyRecurrence(Set<DayOfWeek> daysOfWeek) {
this.daysOfWeek = daysOfWeek;
}
@Override
public List<LocalDateTime> generateOccurrences(LocalDateTime start, LocalDateTime rangeEnd) {
List<LocalDateTime> occurrences = new ArrayList<>();
LocalDateTime current = start;
while (!current.isAfter(rangeEnd)) {
if (daysOfWeek.contains(current.getDayOfWeek())) {
occurrences.add(current);
}
current = current.plusDays(1);
}
return occurrences;
}
}
The WeeklyRecurrence rule checks if the current day of the week matches the configured set (e.g., Monday and Wednesday) before adding the occurrence.
Event & EventInstance Classes
The Event represents the template definition of a meeting, while the EventInstance tracks concrete occurrence intervals.
import java.time.LocalDateTime;
public class Event {
private final String id;
private final String title;
private final LocalDateTime start;
private final int durationMinutes;
private final RecurrenceRule recurrenceRule;
public Event(String id, String title, LocalDateTime start, int durationMinutes, RecurrenceRule recurrenceRule) {
this.id = id;
this.title = title;
this.start = start;
this.durationMinutes = durationMinutes;
this.recurrenceRule = recurrenceRule;
}
public String getId() { return id; }
public String getTitle() { return title; }
public LocalDateTime getStart() { return start; }
public int getDurationMinutes() { return durationMinutes; }
public RecurrenceRule getRecurrenceRule() { return recurrenceRule; }
}
The constructor configures the meeting properties. Let's define the EventInstance class:
import java.time.LocalDateTime;
public class EventInstance {
private final String eventId;
private final String title;
private final LocalDateTime startTime;
private final LocalDateTime endTime;
public EventInstance(String eventId, String title, LocalDateTime startTime, int durationMinutes) {
this.eventId = eventId;
this.title = title;
this.startTime = startTime;
this.endTime = startTime.plusMinutes(durationMinutes);
}
public boolean overlapsWith(EventInstance other) {
// Return true if intervals intersect: (StartA < EndB) AND (EndA > StartB)
return this.startTime.isBefore(other.endTime) && this.endTime.isAfter(other.startTime);
}
public String getEventId() { return eventId; }
public String getTitle() { return title; }
public LocalDateTime getStartTime() { return startTime; }
public LocalDateTime getEndTime() { return endTime; }
}
The constructor calculates the end time based on the duration. overlapsWith() checks for conflicts between time intervals.
Calendar Class
The Calendar class stores events, expands recurrence rules, and checks for scheduling conflicts before booking.
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
public class Calendar {
private final List<Event> events;
private final List<EventInstance> bookedInstances;
public Calendar() {
this.events = new ArrayList<>();
this.bookedInstances = new ArrayList<>();
}
public synchronized void bookEvent(Event event, LocalDateTime rangeEnd) {
// Expand the event's recurrence rule to generate instances
List<LocalDateTime> startTimes = event.getRecurrenceRule().generateOccurrences(event.getStart(), rangeEnd);
List<EventInstance> incomingInstances = new ArrayList<>();
for (LocalDateTime time : startTimes) {
incomingInstances.add(new EventInstance(event.getId(), event.getTitle(), time, event.getDurationMinutes()));
}
// Check for conflicts against existing instances
for (EventInstance incoming : incomingInstances) {
for (EventInstance existing : bookedInstances) {
if (incoming.overlapsWith(existing)) {
throw new IllegalStateException("Scheduling Conflict: '" + incoming.getTitle() + "' at " +
incoming.getStartTime() + " overlaps with '" + existing.getTitle() + "' at " + existing.getStartTime());
}
}
}
// Commit the booking if no conflicts are found
events.add(event);
bookedInstances.addAll(incomingInstances);
System.out.println("Booked Event: '" + event.getTitle() + "' (" + incomingInstances.size() + " instances)");
}
public synchronized List<EventInstance> getBookedInstances() {
return new ArrayList<>(bookedInstances);
}
}
Here is an explanation of the core operations in the Calendar class:
- The constructor initializes lists to store abstract events and concrete instances.
bookEvent()uses the event's recurrence rule to generate occurrences. It builds `EventInstance` instances and checks for conflicts against existing bookings. If a conflict is found, it rolls back and throws an exception; otherwise, it commits the booking.
Main Driver Class
This class tests our calendar scheduler. It books one-time and recurring events, validates conflict detection, and displays generated instances.
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.util.EnumSet;
public class Main {
public static void main(String[] args) {
Calendar calendar = new Calendar();
LocalDateTime rangeLimit = LocalDateTime.of(2026, 6, 14, 23, 59); // 1-week limit
System.out.println("==========================================");
System.out.println("Scenario 1: Booking Standup Meeting (Daily Recurrence)");
System.out.println("==========================================");
LocalDateTime standupStart = LocalDateTime.of(2026, 6, 8, 9, 0); // Monday 9:00 AM
Event dailyStandup = new Event("E1", "Daily Standup", standupStart, 30, new DailyRecurrence());
calendar.bookEvent(dailyStandup, rangeLimit);
System.out.println("\n==========================================");
System.out.println("Scenario 2: Booking One-time Event (No Conflict)");
System.out.println("==========================================");
// One-time meeting on Monday 10:00 AM (No conflict with 9:00 AM Standup)
LocalDateTime meetingStart = LocalDateTime.of(2026, 6, 8, 10, 0);
Event syncMeeting = new Event("E2", "Design Sync", meetingStart, 60, new NoRecurrence());
calendar.bookEvent(syncMeeting, rangeLimit);
System.out.println("\n==========================================");
System.out.println("Scenario 3: Booking Conflicting Event");
System.out.println("==========================================");
// Try booking a meeting that overlaps with Wednesday's Standup (9:15 AM - 9:45 AM)
LocalDateTime conflictStart = LocalDateTime.of(2026, 6, 10, 9, 15);
Event badMeeting = new Event("E3", "Ad-hoc Call", conflictStart, 30, new NoRecurrence());
try {
calendar.bookEvent(badMeeting, rangeLimit);
} catch (Exception e) {
System.out.println("Caught Expected Exception:\n" + e.getMessage());
}
System.out.println("\n==========================================");
System.out.println("Print Booked Instances for June 8, 2026");
System.out.println("==========================================");
for (EventInstance instance : calendar.getBookedInstances()) {
if (instance.getStartTime().getDayOfMonth() == 8) {
System.out.println("Meeting: " + instance.getTitle() + " | " +
instance.getStartTime().toLocalTime() + " - " + instance.getEndTime().toLocalTime());
}
}
}
}
The main() driver configures recurrence ranges, schedules daily and one-time events, verifies conflict checks, and prints the scheduled instances to confirm correct execution.
Comments
Post a Comment