218 lines
7.7 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|