Skip to content

Commit

Permalink
refactor: 반복주기에 따른 할일 조회 기능 리팩토링
Browse files Browse the repository at this point in the history
- 기존의 Util Class의 메서드로 존재하던 구조 대신 전략 패턴 적용해서 클래스 단위로 분리
- 이에 따른 테스트 수정

Resolves: #38
  • Loading branch information
jiwon83 committed Nov 17, 2024
1 parent 014915d commit 0720586
Show file tree
Hide file tree
Showing 12 changed files with 735 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.wypl.wyplcore.schedule.service.repetition;

import java.time.LocalDate;
import java.util.List;

import com.wypl.jpacalendardomain.calendar.domain.Schedule;
import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;
import com.wypl.wyplcore.schedule.service.repetition.strategy.RepetitionStrategy;

public class RepetitionService {

public static List<ScheduleFindResponse> getScheduleResponses(Schedule schedule, LocalDate searchStartDate, LocalDate searchEndDate) {
RepetitionStrategy repetitionStrategy = RepetitionStrategyFactory.getRepetitionStrategy(schedule.getRepetitionCycle());
return repetitionStrategy.getScheduleResponses(schedule, searchStartDate, searchEndDate);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.wypl.wyplcore.schedule.service.repetition;

import java.util.HashMap;
import java.util.Map;

import com.wypl.jpacalendardomain.calendar.data.RepetitionCycle;
import com.wypl.wyplcore.schedule.service.repetition.strategy.DayRepetitionStrategy;
import com.wypl.wyplcore.schedule.service.repetition.strategy.MonthRepetitionStrategy;
import com.wypl.wyplcore.schedule.service.repetition.strategy.RepetitionStrategy;
import com.wypl.wyplcore.schedule.service.repetition.strategy.WeekRepetitionStrategy;
import com.wypl.wyplcore.schedule.service.repetition.strategy.YearRepetitionStrategy;

public class RepetitionStrategyFactory {

private static RepetitionStrategyFactory instance;

private final Map<RepetitionCycle, RepetitionStrategy> map = new HashMap<>();

private RepetitionStrategyFactory() {
map.put(RepetitionCycle.DAY, new DayRepetitionStrategy());
map.put(RepetitionCycle.WEEK, new WeekRepetitionStrategy());
map.put(RepetitionCycle.MONTH, new MonthRepetitionStrategy());
map.put(RepetitionCycle.YEAR, new YearRepetitionStrategy());
}

public static RepetitionStrategy getRepetitionStrategy(RepetitionCycle repetitionCycle) {
if (instance == null) {
instance = new RepetitionStrategyFactory();
}
return instance.map.get(repetitionCycle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.wypl.wyplcore.schedule.service.repetition.strategy;

import static com.wypl.wyplcore.calendar.CalendarServiceUtil.*;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import com.wypl.jpacalendardomain.calendar.domain.Schedule;
import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;

public class DayRepetitionStrategy implements RepetitionStrategy{
@Override
public List<ScheduleFindResponse> getScheduleResponses(Schedule schedule, LocalDate searchStartDate, LocalDate searchEndDate) {

List<ScheduleFindResponse> responses = new ArrayList<>();

searchStartDate = getMaxDate(searchStartDate, schedule.getRepetitionStartDate());
searchEndDate = getMinDate(searchEndDate, schedule.getRepetitionEndDate());

for(; !searchStartDate.isAfter(searchEndDate); searchStartDate = searchStartDate.plusDays(1)) {
LocalDateTime startDateTime = LocalDateTime.of(searchStartDate, schedule.getStartDateTime().toLocalTime());
LocalDateTime endDateTime = LocalDateTime.of(searchStartDate, schedule.getEndDateTime().toLocalTime());
responses.add(ScheduleFindResponse.of(schedule, startDateTime, endDateTime));
}
return responses;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.wypl.wyplcore.schedule.service.repetition.strategy;

import static com.wypl.wyplcore.calendar.CalendarServiceUtil.*;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import com.wypl.jpacalendardomain.calendar.domain.Schedule;
import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;

public class MonthRepetitionStrategy implements RepetitionStrategy{
@Override
public List<ScheduleFindResponse> getScheduleResponses(Schedule schedule, LocalDate searchStartDate,
LocalDate searchEndDate) {

List<ScheduleFindResponse> responses = new ArrayList<>();
searchStartDate = getMaxDate(searchStartDate, schedule.getRepetitionStartDate());
searchEndDate = getMinDate(searchEndDate, schedule.getRepetitionEndDate());

// 끝나는 날짜
int endDayOfMonth = schedule.getEndDateTime().getDayOfMonth();

// 탐색할 시작 일시와 끝 일시 설정
LocalDateTime searchEndDateTime = LocalDateTime.of(searchStartDate.withDayOfMonth(endDayOfMonth), schedule.getEndDateTime().toLocalTime());
Duration duration = Duration.between(schedule.getStartDateTime(), schedule.getEndDateTime());
LocalDateTime searchStartDateTime = searchEndDateTime.minus(duration);

for ( LocalDate date = searchStartDateTime.toLocalDate(); !date.isAfter(searchEndDate); date = date.plusMonths(1)) {
LocalDateTime startDateTime = LocalDateTime.of(date, schedule.getStartDateTime().toLocalTime());
LocalDateTime endDateTime = startDateTime.plus(duration);
responses.add(ScheduleFindResponse.of(schedule, startDateTime, endDateTime));
}
return responses;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.wypl.wyplcore.schedule.service.repetition.strategy;

import java.time.LocalDate;
import java.util.List;

import com.wypl.jpacalendardomain.calendar.domain.Schedule;
import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;

public interface RepetitionStrategy {

List<ScheduleFindResponse> getScheduleResponses(Schedule schedule, LocalDate searchStartDate, LocalDate searchEndDate);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.wypl.wyplcore.schedule.service.repetition.strategy;

import static com.wypl.wyplcore.calendar.CalendarServiceUtil.*;

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;

import com.wypl.jpacalendardomain.calendar.domain.Schedule;
import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;

public class WeekRepetitionStrategy implements RepetitionStrategy{

@Override
public List<ScheduleFindResponse> getScheduleResponses(Schedule schedule, LocalDate searchStartDate,
LocalDate searchEndDate) {

List<ScheduleFindResponse> responses = new ArrayList<>();

// 검색할 시작일자와 끝일자 설정
searchEndDate = getMinDate(searchEndDate, schedule.getRepetitionEndDate());
searchStartDate = getMaxDate(searchStartDate, schedule.getRepetitionStartDate());

// 반복 주에 포함되도록 가공
searchStartDate = getNearestDateUsingWeekInterval(searchStartDate, schedule.getWeekInterval(), schedule);

if(!schedule.existsDayOfWeek()){ // 반복 요일을 설정하지 않았을 경우

searchStartDate = resetForBeforeStarted(searchStartDate, schedule);
Duration duration = Duration.between(schedule.getStartDateTime(), schedule.getEndDateTime());

// 설정한 weekInterval 만큼씩 증가하면서 endDate까지 반복
for( LocalDate date = searchStartDate; !date.isAfter(searchEndDate); date = date.plusWeeks(schedule.getWeekInterval())) {
LocalDateTime startDateTime = LocalDateTime.of(date, schedule.getStartDateTime().toLocalTime());
LocalDateTime endDateTime = startDateTime.plus(duration);
responses.add(ScheduleFindResponse.of(schedule, startDateTime, endDateTime));
}
return responses;
}
// 반복 요일을 설정했을 경우
int repetitionDayOfWeek = schedule.getDayOfWeek();

for (int dayOfWeek = 1; dayOfWeek <= 7; dayOfWeek++) {
if( isSelectedDayOfWeek(repetitionDayOfWeek, dayOfWeek) ) {

// Find nearest day by dayOfWeek and increase weekInterval
LocalDate date = getMaxDate(searchStartDate, schedule.getRepetitionStartDate()).with(
TemporalAdjusters.nextOrSame(DayOfWeek.of(dayOfWeek)));

for(; !date.isAfter(searchEndDate); date = date.plusWeeks(schedule.getWeekInterval())) {
responses.add(ScheduleFindResponse
.of(schedule, LocalDateTime.of(date, schedule.getStartDateTime().toLocalTime()), LocalDateTime.of(date, schedule.getEndDateTime().toLocalTime())));
}
}
}
return responses;
}

/**
* 검색 조건에는 포함되지만, 검색이 시작된 날짜보다 이전에 시작한 Schedule 반영
* @param searchStartDate
* @param schedule
* @return searchStartDate
* Todo: 일정이 일주일 이상 지속될 경우에는 처리 불가, 생각해 보기
*/
private static LocalDate resetForBeforeStarted(LocalDate searchStartDate, Schedule schedule) {

// 끝나는 요일 찾기
DayOfWeek endDayOfWeek = schedule.getEndDateTime().getDayOfWeek();
Duration duration = Duration.between(schedule.getStartDateTime(), schedule.getEndDateTime());

// 탐색을 시작할 일자 설정
LocalDateTime endDateTimeForSearch = searchStartDate.with(TemporalAdjusters.nextOrSame(endDayOfWeek))
.atTime(schedule.getEndDateTime().toLocalTime());

return endDateTimeForSearch.minus(duration).toLocalDate();
}

/**
* 검색 시작일자에 WeekInterval을 적용
* @param searchStartDate
* @param weekInterval
* @param schedule
* @return searchStartDate
*/
public static LocalDate getNearestDateUsingWeekInterval(LocalDate searchStartDate, Integer weekInterval,
Schedule schedule){

// searchStartDate를 해당 주의 월요일로 설정
LocalDateTime searchStartMonday = LocalDateTime.of(searchStartDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)), LocalTime.of(0, 0));
LocalDateTime scheduleStartMonday = LocalDateTime.of(schedule.getRepetitionStartDate().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)), LocalTime.of(0, 0));

int gapOfWeek = (int) Duration.between(scheduleStartMonday, searchStartMonday).toDays() / 7;

if (gapOfWeek % weekInterval == 0) return searchStartDate;

int addWeek = (gapOfWeek / weekInterval + 1) * weekInterval - gapOfWeek;

return searchStartDate.plusWeeks(addWeek);
}

/**
* repetitionDayOfWeek에 해당하는 요일이 선택되었는지 확인
* @param repetitionDayOfWeek of Schedule
* @param dayOfWeek (1: 월요일, 2: 화요일, 3: 수요일, 4: 목요일, 5: 금요일, 6: 토요일, 7: 일요일)
* @return boolean
*/
static boolean isSelectedDayOfWeek(int repetitionDayOfWeek, int dayOfWeek) {
return (repetitionDayOfWeek & (1 << dayOfWeek)) != 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.wypl.wyplcore.schedule.service.repetition.strategy;

import static com.wypl.wyplcore.calendar.CalendarServiceUtil.*;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

import com.wypl.jpacalendardomain.calendar.domain.Schedule;
import com.wypl.wyplcore.schedule.data.response.ScheduleFindResponse;

@Service
public class YearRepetitionStrategy implements RepetitionStrategy{
@Override
public List<ScheduleFindResponse> getScheduleResponses(Schedule schedule, LocalDate searchStartDate,
LocalDate searchEndDate) {
List<ScheduleFindResponse> responses = new ArrayList<>();

searchStartDate = getMaxDate(searchStartDate, schedule.getRepetitionStartDate());
searchEndDate = getMinDate(searchEndDate, schedule.getRepetitionEndDate());

// startDate와 같거나 가장 가까운 schedule.endDateTime의 날짜, 시간을 가진 LocalDateTime 생성
LocalDateTime nearestEndDateTime = LocalDateTime.of(searchStartDate.withDayOfYear(schedule.getEndDateTime().getDayOfYear()), schedule.getEndDateTime().toLocalTime());
LocalDateTime nearestStartDateTime = nearestEndDateTime.minus(
Duration.between(schedule.getStartDateTime(), schedule.getEndDateTime()));

for(LocalDate date = nearestStartDateTime.toLocalDate(); !date.isAfter(searchEndDate); date = date.plusYears(1)) {
LocalDateTime startDateTime = LocalDateTime.of(date, schedule.getStartDateTime().toLocalTime());
LocalDateTime endDateTime = startDateTime.plus(Duration.between(schedule.getStartDateTime(), schedule.getEndDateTime()));
responses.add(ScheduleFindResponse.of(schedule, startDateTime, endDateTime));
}

return responses;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,26 @@ public enum ScheduleFixture {
"월간 일정",
"매달 첫째 주 화요일에 반복되는 일정입니다.",
LocalDateTime.of(2024, 10, 20, 10, 0),
LocalDateTime.of(2024, 10, 20, 11, 0),
LocalDateTime.of(2024, 10, 21, 11, 0),
LocalDate.of(2024, 10, 20),
LocalDate.of(2025, 2, 1),
RepetitionCycle.MONTH,
2, // 화요일
1 // 매월 반복
),
YEARLY_SCHEDULE(
"연간 일정",
"매년 10월 20일에 반복되는 일정입니다.",
LocalDateTime.of(2024, 10, 20, 10, 0),
LocalDateTime.of(2024, 10, 21, 11, 0),
LocalDate.of(2024, 10, 20),
LocalDate.of(2026, 10, 20),
RepetitionCycle.YEAR,
null,
null
);


private final String title;
private final String description;
private final LocalDateTime startDateTime;
Expand Down
Loading

0 comments on commit 0720586

Please sign in to comment.