diff --git a/src/main/java/com/leets/monifit_be/domain/expense/service/DashboardService.java b/src/main/java/com/leets/monifit_be/domain/expense/service/DashboardService.java index b7fccdf..e0bfe0f 100644 --- a/src/main/java/com/leets/monifit_be/domain/expense/service/DashboardService.java +++ b/src/main/java/com/leets/monifit_be/domain/expense/service/DashboardService.java @@ -11,6 +11,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; +import java.time.ZoneId; import java.time.temporal.ChronoUnit; @Service @@ -33,6 +34,7 @@ public DashboardResponse getDashboardData(Long memberId) { } // 3. 기본 데이터 계산 + LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); // 한국 시간대 기준 오늘 날짜 int budgetAmount = period.getBudgetAmount(); long totalExpense = expenseRepository.sumAmountByBudgetPeriod(period); long remainingBudget = Math.max(0, budgetAmount - totalExpense); @@ -40,9 +42,9 @@ public DashboardResponse getDashboardData(Long memberId) { double savingRate = Math.max(0, 100 - usageRate); long totalDays = ChronoUnit.DAYS.between(period.getStartDate(), period.getEndDate()) + 1; - long elapsedDays = Math.min(ChronoUnit.DAYS.between(period.getStartDate(), LocalDate.now()) + 1, + long elapsedDays = Math.min(ChronoUnit.DAYS.between(period.getStartDate(), today) + 1, totalDays); - long remainingDays = Math.max(0, ChronoUnit.DAYS.between(LocalDate.now(), period.getEndDate())); + long remainingDays = Math.max(0, ChronoUnit.DAYS.between(today, period.getEndDate())); double progressRate = ((double) elapsedDays / totalDays) * 100; long dailyRecommended = (remainingDays > 0) ? remainingBudget / remainingDays : 0; @@ -58,7 +60,7 @@ public DashboardResponse getDashboardData(Long memberId) { // 4. 마감일 도달 체크 및 처리 boolean showPeriodComplete = false; DashboardResponse.PeriodCompleteDetail periodCompleteDetail = null; - if (LocalDate.now().isAfter(period.getEndDate())) { + if (today.isAfter(period.getEndDate())) { // 예산 초과 여부에 따라 completionType 결정 CompletionType completionType = totalExpense <= budgetAmount ? CompletionType.SUCCESS diff --git a/src/main/java/com/leets/monifit_be/domain/expense/service/ExpenseService.java b/src/main/java/com/leets/monifit_be/domain/expense/service/ExpenseService.java index 69d482a..7f13d3b 100644 --- a/src/main/java/com/leets/monifit_be/domain/expense/service/ExpenseService.java +++ b/src/main/java/com/leets/monifit_be/domain/expense/service/ExpenseService.java @@ -20,6 +20,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.ZoneId; import java.time.format.TextStyle; import java.util.List; import java.util.Locale; @@ -39,8 +40,9 @@ public ExpenseCreateResponse createExpense(Long memberId, ExpenseCreateRequest r .orElseThrow(() -> new BusinessException(ErrorCode.ACTIVE_PERIOD_NOT_FOUND)); // 2. 날짜 설정 및 오늘 첫 기록 여부 확인 - LocalDate spentDate = (request.getSpentDate() != null) ? request.getSpentDate() : LocalDate.now(); - boolean isTodayRecord = spentDate.equals(LocalDate.now()); + LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); // 한국 시간대 기준 오늘 날짜 + LocalDate spentDate = (request.getSpentDate() != null) ? request.getSpentDate() : today; + boolean isTodayRecord = spentDate.equals(today); boolean todayFirstExpense = !expenseRepository.existsByBudgetPeriodAndSpentDate(budgetPeriod, spentDate); @@ -64,7 +66,7 @@ public ExpenseCreateResponse createExpense(Long memberId, ExpenseCreateRequest r long remainingBudget = Math.max(0, budgetAmount - totalExpense); double usageRate = (double) totalExpense / budgetAmount * 100; long remainingDays = Math.max(1, - java.time.temporal.ChronoUnit.DAYS.between(LocalDate.now(), budgetPeriod.getEndDate())); + java.time.temporal.ChronoUnit.DAYS.between(today, budgetPeriod.getEndDate())); long dailyRecommended = remainingBudget / remainingDays; // 6. 예산 초과 감지 시 자동 종료 @@ -228,8 +230,9 @@ public ExpenseUpdateResponse updateExpense(Long memberId, Long expenseId, Expens int budgetAmount = budgetPeriod.getBudgetAmount(); long remainingBudget = Math.max(0, budgetAmount - totalExpense); double usageRate = (double) totalExpense / budgetAmount * 100; + LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); long remainingDays = Math.max(1, - java.time.temporal.ChronoUnit.DAYS.between(LocalDate.now(), budgetPeriod.getEndDate())); + java.time.temporal.ChronoUnit.DAYS.between(today, budgetPeriod.getEndDate())); long dailyRecommended = remainingBudget / remainingDays; // 예산 초과 감지 diff --git a/src/main/java/com/leets/monifit_be/domain/expense/service/StampService.java b/src/main/java/com/leets/monifit_be/domain/expense/service/StampService.java index aeb36dc..5be5340 100644 --- a/src/main/java/com/leets/monifit_be/domain/expense/service/StampService.java +++ b/src/main/java/com/leets/monifit_be/domain/expense/service/StampService.java @@ -12,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; +import java.time.ZoneId; import java.util.ArrayList; import java.util.List; @@ -43,7 +44,7 @@ public StampResponse getStamps(Long memberId, Long periodId) { // 이전/다음 기간 탐색 정보 조회 StampResponse.NavigationDto navigation = buildNavigation(memberId, period); - LocalDate today = LocalDate.now(); + LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); // 한국 시간대 기준 오늘 날짜 List stampDetails = new ArrayList<>(); int stampedDaysCount = 0; diff --git a/src/main/java/com/leets/monifit_be/global/config/JpaConfig.java b/src/main/java/com/leets/monifit_be/global/config/JpaConfig.java index 8cbb3d6..dfebead 100644 --- a/src/main/java/com/leets/monifit_be/global/config/JpaConfig.java +++ b/src/main/java/com/leets/monifit_be/global/config/JpaConfig.java @@ -5,6 +5,7 @@ import org.springframework.data.auditing.DateTimeProvider; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Optional; @@ -13,12 +14,20 @@ @EnableJpaAuditing(dateTimeProviderRef = "auditingDateTimeProvider") public class JpaConfig { + private static final ZoneId KOREA_ZONE = ZoneId.of("Asia/Seoul"); + /** - * JPA Auditing에서 사용할 시간대를 한국(Asia/Seoul)으로 설정 - * @CreatedDate, @LastModifiedDate가 한국 시간으로 저장됨 + * JPA Auditing에서 사용할 시간을 한국 시간대(Asia/Seoul) 기준으로 생성 + * + * Instant.now()를 사용하여 현재 순간(UTC)을 가져온 후 + * Asia/Seoul 시간대로 변환하여 LocalDateTime 반환 + * + * 이 방식은 서버 시스템 시간대나 JDBC serverTimezone 설정과 무관하게 + * 항상 정확한 한국 시간을 생성합니다. */ @Bean public DateTimeProvider auditingDateTimeProvider() { - return () -> Optional.of(LocalDateTime.now(ZoneId.of("Asia/Seoul"))); + return () -> Optional.of( + LocalDateTime.ofInstant(Instant.now(), KOREA_ZONE)); } }