Skip to content

Commit

Permalink
feat: shuttle next departure time
Browse files Browse the repository at this point in the history
  • Loading branch information
jcw1031 committed May 6, 2024
1 parent 3995f33 commit 6d14038
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
import lombok.Builder;

@Builder
public record ShuttleListResponse(String shuttleId, String shuttleName, String note) {
public record ShuttleListResponse(String shuttleId, String shuttleName, String note, String nextDepartureTime) {

private static final String ROUTE_NAME_DELIMITER = " → ";

public static ShuttleListResponse of(Shuttle shuttle) {
public static ShuttleListResponse of(Shuttle shuttle, String nextDepartureTime) {
String origin = shuttle.getOrigin();
String destination = shuttle.getDestination();
return ShuttleListResponse.builder()
.shuttleId(shuttle.getId())
.shuttleName(String.join(ROUTE_NAME_DELIMITER, origin, destination))
.note(shuttle.getNote())
.nextDepartureTime(nextDepartureTime)
.build();
}
}
38 changes: 38 additions & 0 deletions src/main/java/ac/knu/likeknu/converter/OperatingDaysConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ac.knu.likeknu.converter;

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;

import java.time.DayOfWeek;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.stream.Collectors;

@Converter
public class OperatingDaysConverter implements AttributeConverter<EnumSet<DayOfWeek>, String> {

private static final String SEPARATOR = ",";

@Override
public String convertToDatabaseColumn(EnumSet<DayOfWeek> attribute) {
if (attribute == null) {
return null;
}

return attribute.stream()
.map(Enum::name)
.collect(Collectors.joining(SEPARATOR));
}

@Override
public EnumSet<DayOfWeek> convertToEntityAttribute(String dbData) {
EnumSet<DayOfWeek> dayOfWeeks = EnumSet.noneOf(DayOfWeek.class);
if (dbData == null || dbData.isEmpty()) {
return dayOfWeeks;
}

Arrays.stream(dbData.split(SEPARATOR))
.forEach(s -> dayOfWeeks.add(DayOfWeek.valueOf(s)));
return dayOfWeeks;
}
}
48 changes: 47 additions & 1 deletion src/main/java/ac/knu/likeknu/domain/Shuttle.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package ac.knu.likeknu.domain;

import ac.knu.likeknu.converter.OperatingDaysConverter;
import ac.knu.likeknu.domain.constants.Campus;
import ac.knu.likeknu.domain.constants.ShuttleType;
import ac.knu.likeknu.exception.BusinessException;
import ac.knu.likeknu.utils.DateTimeUtils;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
Expand All @@ -15,7 +19,12 @@
import lombok.Builder;
import lombok.Getter;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;

Expand All @@ -38,6 +47,10 @@ public class Shuttle {

private int sequence;

@Convert(converter = OperatingDaysConverter.class)
@Column(name = "operating_days", columnDefinition = "SET")
private EnumSet<DayOfWeek> operatingDays;

@OneToMany(mappedBy = "shuttle")
private List<ShuttleBus> shuttleBuses = new ArrayList<>();

Expand All @@ -51,11 +64,44 @@ protected Shuttle() {
}

@Builder
public Shuttle(String origin, String destination, ShuttleType shuttleType, String note) {
public Shuttle(String origin, String destination, ShuttleType shuttleType, String note, EnumSet<DayOfWeek> operatingDays) {
this.origin = origin;
this.destination = destination;
this.shuttleType = shuttleType;
this.note = note;
this.operatingDays = operatingDays;
}

public LocalDateTime getNextDepartureDateTime() {
if (isAvailableToday()) {
LocalTime todayNextDepartureTime = shuttleBuses.stream()
.filter(ShuttleBus::isAvailableToday)
.map(ShuttleBus::getDepartureTime)
.sorted()
.findFirst()
.orElseThrow(() -> new BusinessException("There are no shuttle buses available."));
return LocalDateTime.of(LocalDate.now(), todayNextDepartureTime);
}

LocalTime nextDateEarliestDepartureTime = shuttleBuses.stream()
.map(ShuttleBus::getDepartureTime)
.sorted()
.findFirst()
.orElseThrow(() -> new BusinessException("There are no shuttle buses available."));
LocalDate earliestNextAvailableDate = DateTimeUtils.getEarliestNextAvailableDate(operatingDays);
return LocalDateTime.of(earliestNextAvailableDate, nextDateEarliestDepartureTime);
}

private boolean isAvailableToday() {
// TODO Judgment including public holidays
DayOfWeek currentDayOfWeek = LocalDate.now()
.getDayOfWeek();
if (!operatingDays.contains(currentDayOfWeek)) {
return false;
}

return shuttleBuses.stream()
.anyMatch(ShuttleBus::isAvailableToday);
}

@Override
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/ac/knu/likeknu/domain/ShuttleBus.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public LocalTime getDepartureTime() {
.orElse(LocalTime.of(23, 59, 59));
}

public boolean isAvailableToday() {
return getDepartureTime().isAfter(LocalTime.now());
}

@Override
public boolean equals(Object object) {
if (this == object) return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ac.knu.likeknu.domain.constants;

public enum ShuttleOperatingDays {

WEEKDAY,
WEEKEND,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import ac.knu.likeknu.domain.Shuttle;
import ac.knu.likeknu.domain.constants.Campus;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface ShuttleRepository extends JpaRepository<Shuttle, String> {

@EntityGraph(attributePaths = "shuttleBuses")
List<Shuttle> findByCampusesContains(Campus campus);
}
66 changes: 61 additions & 5 deletions src/main/java/ac/knu/likeknu/service/ShuttleBusService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@
import ac.knu.likeknu.exception.BusinessException;
import ac.knu.likeknu.repository.ShuttleBusRepository;
import ac.knu.likeknu.repository.ShuttleRepository;
import ac.knu.likeknu.utils.DateTimeUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

@Slf4j
@Transactional(readOnly = true)
@Service
public class ShuttleBusService {
Expand All @@ -34,11 +44,57 @@ public ShuttleBusService(ShuttleRepository shuttleRepository, ShuttleBusReposito
* @return 캠퍼스별 셔틀버스 경로 목록
*/
public List<ShuttleListResponse> getRouteList(Campus campus) {
return shuttleRepository.findByCampusesContains(campus)
.stream()
.sorted(Comparator.comparing(Shuttle::getSequence))
.map(ShuttleListResponse::of)
.toList();
List<Shuttle> shuttles = shuttleRepository.findByCampusesContains(campus);
shuttles.sort(Comparator.comparing(Shuttle::getSequence));

List<ShuttleListResponse> routeList = new ArrayList<>();
for (Shuttle shuttle : shuttles) {
LocalDateTime nextDepartureDateTime = shuttle.getNextDepartureDateTime();
String departureTimeMessage = generateDepartureTimeMessage(nextDepartureDateTime);

ShuttleListResponse shuttleListResponse = ShuttleListResponse.of(shuttle, departureTimeMessage);
routeList.add(shuttleListResponse);
}

return routeList;
}

private String generateDepartureTimeMessage(LocalDateTime nextDepartureDateTime) {
StringBuilder message = new StringBuilder();
String dateMessage = determineDateMessage(nextDepartureDateTime.toLocalDate());
message.append(dateMessage)
.append(" ");

String timeMessage = determineTimeMessage(nextDepartureDateTime.toLocalTime());
message.append(timeMessage);
return message.toString();
}

private String determineDateMessage(LocalDate nextDepartureDate) {
LocalDate currentDate = LocalDate.now();
if (nextDepartureDate.isEqual(currentDate)) {
return "";
}

if (nextDepartureDate.minusDays(1).isEqual(currentDate)) {
return "내일";
}

String displayName = nextDepartureDate.getDayOfWeek()
.getDisplayName(TextStyle.FULL, Locale.KOREA);
if (DateTimeUtils.isAnotherWeek(nextDepartureDate, currentDate)) {
return "다음주 " + displayName;
}
return displayName;
}

private String determineTimeMessage(LocalTime nextDepartureTime) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("a h시 m분", Locale.KOREA);
String formattedTime = nextDepartureTime.format(formatter);
if (formattedTime.contains(" 0분")) {
return formattedTime.split(" 0분")[0];
}
return formattedTime;
}

/**
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/ac/knu/likeknu/utils/DateTimeUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package ac.knu.likeknu.utils;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.EnumSet;

public class DateTimeUtils {

public static LocalDate getEarliestNextAvailableDate(EnumSet<DayOfWeek> availableDayOfWeeks) {
LocalDate date = LocalDate.now();
while (true) {
date = date.plusDays(1);
DayOfWeek dayOfWeek = date.getDayOfWeek();
if (availableDayOfWeeks.contains(dayOfWeek)) {
return date;
}
}
}

public static boolean isAnotherWeek(LocalDate date1, LocalDate date2) {
if (date1.isAfter(date2)) {
return date1.getDayOfWeek().getValue() <= date2.getDayOfWeek().getValue();
}
return date2.getDayOfWeek().getValue() <= date1.getDayOfWeek().getValue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE shuttle
ADD COLUMN operating_days
SET ('MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY')
DEFAULT 'MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY';

0 comments on commit 6d14038

Please sign in to comment.