cs151/hw1/src/Event.java
2025-02-19 23:54:50 -08:00

218 lines
7.7 KiB
Java

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* Represents an event that occurs on a specific date or a range of dates.
* <p>
* The event has a time interval and a description.
* <p>
* The event can be recurring on specific days of the week.
*
* @author Yuri Tatishchev
* @version 0.1 2025-02-08
*/
public class Event implements Comparable<Event> {
private final LocalDate startDate;
private final LocalDate endDate;
private final TimeInterval timeInterval;
private final String name;
private final SortedSet<DayOfWeek> recurs;
private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("M/d/yyyy");
private static final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("H:mm");
private static final Map<Character, DayOfWeek> charToDayOfWeek = Map.of(
'M', DayOfWeek.MONDAY,
'T', DayOfWeek.TUESDAY,
'W', DayOfWeek.WEDNESDAY,
'R', DayOfWeek.THURSDAY,
'F', DayOfWeek.FRIDAY,
'S', DayOfWeek.SATURDAY,
'U', DayOfWeek.SUNDAY
);
private static final Map<DayOfWeek, Character> dayOfWeekToChar = charToDayOfWeek.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
/**
* Constructs a one-time event with the specified name, date, and time interval.
*/
public Event(String name, LocalDate date, TimeInterval timeInterval) {
this.name = name;
this.startDate = date;
this.endDate = date;
this.timeInterval = timeInterval;
this.recurs = null;
}
/**
* Constructs a recurring event with the specified name, start date, time interval, and recurring days.
*
* @throws IllegalArgumentException if the start date is after the end date
*/
public Event(String name, LocalDate startDate, LocalDate endDate, TimeInterval timeInterval, SortedSet<DayOfWeek> recurs) {
if (startDate.isAfter(endDate)) {
throw new IllegalArgumentException("Start date must be before or equal to end date");
}
this.name = name;
this.startDate = startDate;
this.endDate = endDate;
this.timeInterval = timeInterval;
this.recurs = recurs;
}
public LocalDate getStartDate() {
return startDate;
}
public LocalDate getEndDate() {
return endDate;
}
public TimeInterval getTimeInterval() {
return timeInterval;
}
public String getName() {
return name;
}
public boolean isRecurring() {
return recurs != null;
}
public SortedSet<DayOfWeek> getRecurs() {
return recurs;
}
/**
* Checks if the event occurs on a specific date.
*
* @param date the date to check
* @return true if the event occurs on the date, false otherwise
*/
public boolean occursOnDate(LocalDate date) {
if (date.isBefore(startDate) || date.isAfter(endDate)) {
return false;
}
if (recurs == null) {
return true;
}
for (DayOfWeek day : recurs) {
if (date.getDayOfWeek() == day) {
return true;
}
}
return false;
}
/**
* Checks if this event overlaps with another event.
* <p>
* Two events overlap if their dates overlap and their time intervals overlap.
*
* @param other the other event
* @return true if the events overlap, false otherwise
*/
public boolean overlapsWith(Event other) {
// Dates do not overlap
if (this.endDate.isBefore(other.startDate) || this.startDate.isAfter(other.endDate)) {
return false;
// Dates overlap, but time intervals do not
} else if (!this.timeInterval.overlapsWith(other.timeInterval)) {
return false;
// Dates and time intervals overlap, but possibly not on the same day
} else if (this.isRecurring()) {
// this is recurring, but other is not
if (!other.isRecurring()) {
return this.recurs.contains(other.getStartDate().getDayOfWeek());
}
// Both are recurring, check if any of the recurring days overlap
// TODO: check if this is correct.
// I suspect there are edge cases where the days that overlap are not in the overlapping date range.
return this.recurs.stream().anyMatch(other.recurs::contains);
// other is recurring, but this is not
} else if (other.isRecurring()) {
return other.recurs.contains(this.startDate.getDayOfWeek());
}
return true;
}
public int compareTo(Event other) {
if (this.startDate.isBefore(other.startDate)) {
return -1;
} else if (this.startDate.isAfter(other.startDate)) {
return 1;
} else {
return this.timeInterval.getStart().compareTo(other.timeInterval.getStart());
}
}
/**
* Returns a string representation of the event.
*
* @return a string representation of the event
* <p>
* Example output: <br>
* <br> One event name
* <br> 1/23/2025 9:00 10:15
* <br>
* <br> Recurring event name
* <br> TR 10:30 11:45 1/27/2025 5/12/2025
*/
public String toString() {
return String.format("%s\n%s\n", name, scheduleString());
}
/**
* Returns a string representation of the event's schedule.
*
* @return a string representation of the event's schedule
* <p>
* Example output: <br>
* <br> 1/23/2025 9:00 10:15
* <br> TR 10:30 11:45 1/27/2025 5/12/2025
*/
public String scheduleString() {
if (recurs == null) return String.format("%s %s", dateFormat.format(startDate), timeInterval);
String recursString = recurs.stream().map(dayOfWeekToChar::get).map(String::valueOf).collect(Collectors.joining());
return String.format("%s %s %s %s", recursString, timeInterval, dateFormat.format(startDate), dateFormat.format(endDate));
}
/**
* Parses a schedule string and returns an event.
*
* @param name the name of the event
* @param schedule the schedule string
* @return an event
* <p>
* Example schedule strings:
* <br> TR 9:00 10:15 1/23/2025 5/8/2025
* <br> MW 10:30 11:45 1/27/2025 5/12/2025
* <br> 4/18/2025 9:30 11:30
* <br> 6/3/2025 16:15 17:00
* <br> F 18:30 20:30 1/24/2025 5/9/2025
*/
public static Event fromScheduleString(String name, String schedule) {
String[] parts = schedule.split("\\s+");
if (parts.length == 3) {
LocalDate date = LocalDate.parse(parts[0], dateFormat);
LocalTime start = LocalTime.parse(parts[1], timeFormat);
LocalTime end = LocalTime.parse(parts[2], timeFormat);
TimeInterval timeInterval = new TimeInterval(start, end);
return new Event(name, date, timeInterval);
} else {
LocalTime start = LocalTime.parse(parts[1], timeFormat);
LocalTime end = LocalTime.parse(parts[2], timeFormat);
LocalDate startDate = LocalDate.parse(parts[3], dateFormat);
LocalDate endDate = LocalDate.parse(parts[4], dateFormat);
TimeInterval timeInterval = new TimeInterval(start, end);
SortedSet<DayOfWeek> recurs = parts[0].chars().mapToObj(c -> charToDayOfWeek.get((char) c)).collect(Collectors.toCollection(TreeSet::new));
return new Event(name, startDate, endDate, timeInterval, recurs);
}
}
}