From 6d861a7a72afeefb3859813511ea15766c103119 Mon Sep 17 00:00:00 2001 From: jhan0121 Date: Tue, 10 Mar 2026 00:43:59 +0900 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=EC=84=9C=EB=B2=84=20=ED=83=80?= =?UTF-8?q?=EC=9E=84=EC=A1=B4=20=EC=B2=98=EB=A6=AC=20UTC=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 7 ++---- .../common/config/JacksonConfig.java | 20 ++++++++++++++++ .../common/config/TimeConfig.java | 2 +- .../recyclestudy/email/ReviewEmailSender.java | 2 +- .../response/MemberFindResponse.java | 7 +++--- .../response/NextReviewResponse.java | 10 +++++--- .../response/ReviewSaveResponse.java | 7 ++++-- src/main/resources/application.yaml | 5 +++- .../V20260310_1__convert_time_data_to_utc.sql | 23 +++++++++++++++++++ .../review/service/ReviewServiceTest.java | 2 +- 10 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/recyclestudy/common/config/JacksonConfig.java create mode 100644 src/main/resources/db/migration/V20260310_1__convert_time_data_to_utc.sql diff --git a/Dockerfile b/Dockerfile index feede4b..e97eaad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,7 @@ FROM amazoncorretto:25-alpine3.21 WORKDIR /app -RUN apk add --no-cache curl tzdata && \ - cp /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \ - echo "Asia/Seoul" > /etc/timezone && \ - apk del tzdata +RUN apk add --no-cache curl RUN addgroup -g 1001 appgroup && adduser -u 1001 -G appgroup -D appuser RUN mkdir -p /app/log && chown -R appuser:appgroup /app @@ -16,4 +13,4 @@ USER appuser EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "app.jar"] +ENTRYPOINT ["java", "-Duser.timezone=UTC", "-jar", "app.jar"] diff --git a/src/main/java/com/recyclestudy/common/config/JacksonConfig.java b/src/main/java/com/recyclestudy/common/config/JacksonConfig.java new file mode 100644 index 0000000..8c3941a --- /dev/null +++ b/src/main/java/com/recyclestudy/common/config/JacksonConfig.java @@ -0,0 +1,20 @@ +package com.recyclestudy.common.config; + +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.util.TimeZone; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JacksonConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { + return builder -> builder + .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .modules(new JavaTimeModule()) + .timeZone(TimeZone.getTimeZone("UTC")); + } +} diff --git a/src/main/java/com/recyclestudy/common/config/TimeConfig.java b/src/main/java/com/recyclestudy/common/config/TimeConfig.java index a7b112f..2a73c70 100644 --- a/src/main/java/com/recyclestudy/common/config/TimeConfig.java +++ b/src/main/java/com/recyclestudy/common/config/TimeConfig.java @@ -9,6 +9,6 @@ public class TimeConfig { @Bean public Clock clock() { - return Clock.systemDefaultZone(); + return Clock.systemUTC(); } } diff --git a/src/main/java/com/recyclestudy/email/ReviewEmailSender.java b/src/main/java/com/recyclestudy/email/ReviewEmailSender.java index f332ead..a85048b 100644 --- a/src/main/java/com/recyclestudy/email/ReviewEmailSender.java +++ b/src/main/java/com/recyclestudy/email/ReviewEmailSender.java @@ -22,7 +22,7 @@ public class ReviewEmailSender { private final ReviewCycleService reviewCycleService; private final Clock clock; - @Scheduled(cron = "${schedule.review-mail.cron}", zone = "Asia/Seoul") + @Scheduled(cron = "${schedule.review-mail.cron}", zone = "UTC") public void sendReviewMail() { final LocalDateTime targetDateTime = LocalDateTime.now(clock).truncatedTo(ChronoUnit.MINUTES); diff --git a/src/main/java/com/recyclestudy/member/controller/response/MemberFindResponse.java b/src/main/java/com/recyclestudy/member/controller/response/MemberFindResponse.java index 4845dce..457fba3 100644 --- a/src/main/java/com/recyclestudy/member/controller/response/MemberFindResponse.java +++ b/src/main/java/com/recyclestudy/member/controller/response/MemberFindResponse.java @@ -1,7 +1,8 @@ package com.recyclestudy.member.controller.response; import com.recyclestudy.member.service.output.MemberFindOutput; -import java.time.LocalDateTime; +import java.time.Instant; +import java.time.ZoneOffset; import java.util.List; public record MemberFindResponse(String email, List devices) { @@ -9,11 +10,11 @@ public record MemberFindResponse(String email, List devices) public static MemberFindResponse from(final MemberFindOutput output) { final List memberFindElements = output.elements().stream() .map(outputElement -> new MemberFindElement(outputElement.identifier().getValue(), - outputElement.createdAt())) + outputElement.createdAt().toInstant(ZoneOffset.UTC))) .toList(); return new MemberFindResponse(output.email().getValue(), memberFindElements); } - private record MemberFindElement(String identifier, LocalDateTime createdAt) { + private record MemberFindElement(String identifier, Instant createdAt) { } } diff --git a/src/main/java/com/recyclestudy/review/controller/response/NextReviewResponse.java b/src/main/java/com/recyclestudy/review/controller/response/NextReviewResponse.java index 5aeb6ae..6a4a44e 100644 --- a/src/main/java/com/recyclestudy/review/controller/response/NextReviewResponse.java +++ b/src/main/java/com/recyclestudy/review/controller/response/NextReviewResponse.java @@ -1,11 +1,15 @@ package com.recyclestudy.review.controller.response; import com.recyclestudy.review.service.output.NextReviewOutput; -import java.time.LocalDateTime; +import java.time.Instant; +import java.time.ZoneOffset; -public record NextReviewResponse(LocalDateTime scheduledAt, int count) { +public record NextReviewResponse(Instant scheduledAt, int count) { public static NextReviewResponse of(final NextReviewOutput output) { - return new NextReviewResponse(output.scheduledAt(), output.count()); + final Instant scheduledAt = output.scheduledAt() != null + ? output.scheduledAt().toInstant(ZoneOffset.UTC) + : null; + return new NextReviewResponse(scheduledAt, output.count()); } } diff --git a/src/main/java/com/recyclestudy/review/controller/response/ReviewSaveResponse.java b/src/main/java/com/recyclestudy/review/controller/response/ReviewSaveResponse.java index f46f791..c3063e0 100644 --- a/src/main/java/com/recyclestudy/review/controller/response/ReviewSaveResponse.java +++ b/src/main/java/com/recyclestudy/review/controller/response/ReviewSaveResponse.java @@ -1,12 +1,15 @@ package com.recyclestudy.review.controller.response; import com.recyclestudy.review.domain.ReviewURL; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.List; -public record ReviewSaveResponse(String url, List scheduledAts) { +public record ReviewSaveResponse(String url, List scheduledAts) { public static ReviewSaveResponse of(ReviewURL url, List scheduledAts) { - return new ReviewSaveResponse(url.getValue(), scheduledAts); + return new ReviewSaveResponse(url.getValue(), + scheduledAts.stream().map(scheduledAt -> scheduledAt.toInstant(ZoneOffset.UTC)).toList()); } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index fc22016..97b0610 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -24,8 +24,11 @@ spring: jpa: hibernate: ddl-auto: validate - open-in-view: false + properties: + hibernate: + jdbc: + time_zone: UTC server: diff --git a/src/main/resources/db/migration/V20260310_1__convert_time_data_to_utc.sql b/src/main/resources/db/migration/V20260310_1__convert_time_data_to_utc.sql new file mode 100644 index 0000000..71a18b6 --- /dev/null +++ b/src/main/resources/db/migration/V20260310_1__convert_time_data_to_utc.sql @@ -0,0 +1,23 @@ +-- review_cycle.scheduled_at 보정: PENDING 상태 + 미래 주기만 (-9h) +UPDATE review_cycle rc + INNER JOIN notification_history nh ON nh.review_cycle_id = rc.id +SET rc.scheduled_at = DATE_SUB(rc.scheduled_at, INTERVAL 9 HOUR) +WHERE nh.status = 'PENDING' + AND rc.scheduled_at > NOW(); + +-- notification_history.deadline 보정: PENDING 상태만 (-9h) +UPDATE notification_history nh +SET nh.deadline = DATE_SUB(nh.deadline, INTERVAL 9 HOUR) +WHERE nh.status = 'PENDING'; + +-- member.notification_time 보정: Seoul 기준 -> UTC (-9h) +-- LocalTime은 날짜가 없으므로 자정을 넘는 케이스 처리 필요 +-- 예: 01:00 Seoul -> UTC 전날 16:00 → LocalTime으로 16:00 저장 +UPDATE member +SET notification_time = CASE + WHEN notification_time >= '09:00:00' + THEN SUBTIME(notification_time, '09:00:00') + ELSE + ADDTIME(notification_time, '15:00:00') +END +WHERE notification_time IS NOT NULL; diff --git a/src/test/java/com/recyclestudy/review/service/ReviewServiceTest.java b/src/test/java/com/recyclestudy/review/service/ReviewServiceTest.java index 80fab28..a9d18bd 100644 --- a/src/test/java/com/recyclestudy/review/service/ReviewServiceTest.java +++ b/src/test/java/com/recyclestudy/review/service/ReviewServiceTest.java @@ -154,7 +154,7 @@ void saveReview_withPreferredNotificationTime() { final Email email = Email.from("test@test.com"); final Member member = Member.withoutId(email); - final LocalTime preferredTime = LocalTime.of(9, 0); + final LocalTime preferredTime = LocalTime.of(0, 0); member.updateNotificationTime(preferredTime); final Review review = Review.withoutId(member, ReviewURL.from(urlValue)); From 06cccf506b06c8164c66dfa3bb34d0e098746652 Mon Sep 17 00:00:00 2001 From: jhan0121 Date: Tue, 10 Mar 2026 00:43:59 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=EC=84=9C=EB=B2=84=20=ED=83=80?= =?UTF-8?q?=EC=9E=84=EC=A1=B4=20=EC=B2=98=EB=A6=AC=20UTC=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberControllerTest.java | 8 ++++---- .../review/controller/ReviewControllerTest.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/recyclestudy/member/controller/MemberControllerTest.java b/src/test/java/com/recyclestudy/member/controller/MemberControllerTest.java index 651a3c7..29b388f 100644 --- a/src/test/java/com/recyclestudy/member/controller/MemberControllerTest.java +++ b/src/test/java/com/recyclestudy/member/controller/MemberControllerTest.java @@ -145,7 +145,7 @@ void findAllMemberDevices() { fieldWithPath("devices[].identifier").type(JsonFieldType.STRING) .description("디바이스 식별자 값"), fieldWithPath("devices[].createdAt").type(JsonFieldType.STRING) - .description("디바이스 생성일") + .description("디바이스 생성일 (UTC, ISO 8601)") ) )) .header("X-Device-Id", headerIdentifier) @@ -396,7 +396,7 @@ void findAllMemberDevices_WithHeader() { fieldWithPath("devices[].identifier").type(JsonFieldType.STRING) .description("디바이스 식별자 값"), fieldWithPath("devices[].createdAt").type(JsonFieldType.STRING) - .description("디바이스 생성일") + .description("디바이스 생성일 (UTC, ISO 8601)") ), queryParameters( parameterWithName("email").description("이메일 (다음 버전에서 제거 예정)") @@ -434,7 +434,7 @@ void findNotificationTime() { ) .responseFields( fieldWithPath("notificationTime").type(JsonFieldType.STRING) - .description("알림 시간 (HH:mm:ss)") + .description("알림 시간 (HH:mm:ss, UTC 기준)") ) )) .header("X-Device-Id", headerIdentifier) @@ -531,7 +531,7 @@ void updateNotificationTime() { ) .requestFields( fieldWithPath("notificationTime").type(JsonFieldType.STRING) - .description("알림 시간 (HH:mm:ss)") + .description("알림 시간 (HH:mm:ss, UTC 기준)") ) )) .contentType(MediaType.APPLICATION_JSON_VALUE) diff --git a/src/test/java/com/recyclestudy/review/controller/ReviewControllerTest.java b/src/test/java/com/recyclestudy/review/controller/ReviewControllerTest.java index a008a14..8e382fa 100644 --- a/src/test/java/com/recyclestudy/review/controller/ReviewControllerTest.java +++ b/src/test/java/com/recyclestudy/review/controller/ReviewControllerTest.java @@ -96,7 +96,7 @@ void saveReview_withDefaultCycle() { .responseFields( fieldWithPath("url").type(JsonFieldType.STRING).description("리뷰할 URL"), fieldWithPath("scheduledAts").type(JsonFieldType.ARRAY) - .description("복습 예정 일시 목록") + .description("복습 예정 일시 목록 (UTC, ISO 8601)") ) )) .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -146,7 +146,7 @@ void saveReview_withCustomCycle() { .responseFields( fieldWithPath("url").type(JsonFieldType.STRING).description("리뷰할 URL"), fieldWithPath("scheduledAts").type(JsonFieldType.ARRAY) - .description("복습 예정 일시 목록") + .description("복습 예정 일시 목록 (UTC, ISO 8601)") ) )) .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -274,7 +274,7 @@ void findNextReview_success() { ) .responseFields( fieldWithPath("scheduledAt").type(JsonFieldType.STRING) - .description("다음 발송 예정 시간 (PENDING 없을 시 null)"), + .description("다음 발송 예정 시간, UTC ISO 8601 형식 (PENDING 없을 시 null)"), fieldWithPath("count").type(JsonFieldType.NUMBER) .description("해당 시간에 발송될 URL 개수") ) @@ -309,7 +309,7 @@ void findNextReview_empty() { ) .responseFields( fieldWithPath("scheduledAt").type(JsonFieldType.NULL) - .description("다음 발송 예정 시간 (PENDING 없을 시 null)"), + .description("다음 발송 예정 시간, UTC ISO 8601 형식 (PENDING 없을 시 null)"), fieldWithPath("count").type(JsonFieldType.NUMBER) .description("해당 시간에 발송될 URL 개수") ) From 61901ead3f6fcfb5ddf165797b815ee4e5b61870 Mon Sep 17 00:00:00 2001 From: jhan0121 Date: Tue, 10 Mar 2026 01:15:20 +0900 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20UTC=20=EB=B3=80=ED=99=98?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=96=B4=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/response/MemberFindResponse.java | 3 +-- .../member/service/output/MemberFindOutput.java | 8 +++++--- .../review/controller/ReviewController.java | 2 +- .../controller/response/NextReviewResponse.java | 6 +----- .../controller/response/ReviewSaveResponse.java | 9 +++------ .../review/service/output/NextReviewOutput.java | 6 ++++-- .../review/service/output/ReviewSaveOutput.java | 7 +++++-- .../member/controller/MemberControllerTest.java | 11 ++++++----- .../review/service/ReviewCycleServiceTest.java | 6 ++++-- 9 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/recyclestudy/member/controller/response/MemberFindResponse.java b/src/main/java/com/recyclestudy/member/controller/response/MemberFindResponse.java index 457fba3..ec3d1d5 100644 --- a/src/main/java/com/recyclestudy/member/controller/response/MemberFindResponse.java +++ b/src/main/java/com/recyclestudy/member/controller/response/MemberFindResponse.java @@ -2,7 +2,6 @@ import com.recyclestudy.member.service.output.MemberFindOutput; import java.time.Instant; -import java.time.ZoneOffset; import java.util.List; public record MemberFindResponse(String email, List devices) { @@ -10,7 +9,7 @@ public record MemberFindResponse(String email, List devices) public static MemberFindResponse from(final MemberFindOutput output) { final List memberFindElements = output.elements().stream() .map(outputElement -> new MemberFindElement(outputElement.identifier().getValue(), - outputElement.createdAt().toInstant(ZoneOffset.UTC))) + outputElement.createdAt())) .toList(); return new MemberFindResponse(output.email().getValue(), memberFindElements); } diff --git a/src/main/java/com/recyclestudy/member/service/output/MemberFindOutput.java b/src/main/java/com/recyclestudy/member/service/output/MemberFindOutput.java index 391b247..fb0d4a7 100644 --- a/src/main/java/com/recyclestudy/member/service/output/MemberFindOutput.java +++ b/src/main/java/com/recyclestudy/member/service/output/MemberFindOutput.java @@ -3,7 +3,8 @@ import com.recyclestudy.member.domain.Device; import com.recyclestudy.member.domain.DeviceIdentifier; import com.recyclestudy.member.domain.Email; -import java.time.LocalDateTime; +import java.time.Instant; +import java.time.ZoneOffset; import java.util.List; public record MemberFindOutput(Email email, List elements) { @@ -13,11 +14,12 @@ public static MemberFindOutput of( final List devices ) { final List memberFindElements = devices.stream() - .map(device -> new MemberFindElement(device.getIdentifier(), device.getCreatedAt())) + .map(device -> new MemberFindElement(device.getIdentifier(), + device.getCreatedAt() != null ? device.getCreatedAt().toInstant(ZoneOffset.UTC) : null)) .toList(); return new MemberFindOutput(email, memberFindElements); } - public record MemberFindElement(DeviceIdentifier identifier, LocalDateTime createdAt) { + public record MemberFindElement(DeviceIdentifier identifier, Instant createdAt) { } } diff --git a/src/main/java/com/recyclestudy/review/controller/ReviewController.java b/src/main/java/com/recyclestudy/review/controller/ReviewController.java index c552570..9233023 100644 --- a/src/main/java/com/recyclestudy/review/controller/ReviewController.java +++ b/src/main/java/com/recyclestudy/review/controller/ReviewController.java @@ -35,7 +35,7 @@ public ResponseEntity saveReview( ) { final ReviewSaveInput input = ReviewSaveInput.of(identifier, request.targetUrl(), request.cycle()); final ReviewSaveOutput output = reviewService.saveReview(input); - ReviewSaveResponse response = ReviewSaveResponse.of(output.url(), output.scheduledAts()); + ReviewSaveResponse response = ReviewSaveResponse.from(output); return ResponseEntity.status(HttpStatus.CREATED).body(response); } diff --git a/src/main/java/com/recyclestudy/review/controller/response/NextReviewResponse.java b/src/main/java/com/recyclestudy/review/controller/response/NextReviewResponse.java index 6a4a44e..b6b15d4 100644 --- a/src/main/java/com/recyclestudy/review/controller/response/NextReviewResponse.java +++ b/src/main/java/com/recyclestudy/review/controller/response/NextReviewResponse.java @@ -2,14 +2,10 @@ import com.recyclestudy.review.service.output.NextReviewOutput; import java.time.Instant; -import java.time.ZoneOffset; public record NextReviewResponse(Instant scheduledAt, int count) { public static NextReviewResponse of(final NextReviewOutput output) { - final Instant scheduledAt = output.scheduledAt() != null - ? output.scheduledAt().toInstant(ZoneOffset.UTC) - : null; - return new NextReviewResponse(scheduledAt, output.count()); + return new NextReviewResponse(output.scheduledAt(), output.count()); } } diff --git a/src/main/java/com/recyclestudy/review/controller/response/ReviewSaveResponse.java b/src/main/java/com/recyclestudy/review/controller/response/ReviewSaveResponse.java index c3063e0..88c19dd 100644 --- a/src/main/java/com/recyclestudy/review/controller/response/ReviewSaveResponse.java +++ b/src/main/java/com/recyclestudy/review/controller/response/ReviewSaveResponse.java @@ -1,15 +1,12 @@ package com.recyclestudy.review.controller.response; -import com.recyclestudy.review.domain.ReviewURL; +import com.recyclestudy.review.service.output.ReviewSaveOutput; import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.util.List; public record ReviewSaveResponse(String url, List scheduledAts) { - public static ReviewSaveResponse of(ReviewURL url, List scheduledAts) { - return new ReviewSaveResponse(url.getValue(), - scheduledAts.stream().map(scheduledAt -> scheduledAt.toInstant(ZoneOffset.UTC)).toList()); + public static ReviewSaveResponse from(final ReviewSaveOutput output) { + return new ReviewSaveResponse(output.url().getValue(), output.scheduledAts()); } } diff --git a/src/main/java/com/recyclestudy/review/service/output/NextReviewOutput.java b/src/main/java/com/recyclestudy/review/service/output/NextReviewOutput.java index f446c8b..fca7405 100644 --- a/src/main/java/com/recyclestudy/review/service/output/NextReviewOutput.java +++ b/src/main/java/com/recyclestudy/review/service/output/NextReviewOutput.java @@ -1,14 +1,16 @@ package com.recyclestudy.review.service.output; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneOffset; -public record NextReviewOutput(LocalDateTime scheduledAt, int count) { +public record NextReviewOutput(Instant scheduledAt, int count) { public static NextReviewOutput empty() { return new NextReviewOutput(null, 0); } public static NextReviewOutput of(final LocalDateTime scheduledAt, final int count) { - return new NextReviewOutput(scheduledAt, count); + return new NextReviewOutput(scheduledAt.toInstant(ZoneOffset.UTC), count); } } diff --git a/src/main/java/com/recyclestudy/review/service/output/ReviewSaveOutput.java b/src/main/java/com/recyclestudy/review/service/output/ReviewSaveOutput.java index 2beaadc..4000c0f 100644 --- a/src/main/java/com/recyclestudy/review/service/output/ReviewSaveOutput.java +++ b/src/main/java/com/recyclestudy/review/service/output/ReviewSaveOutput.java @@ -1,12 +1,15 @@ package com.recyclestudy.review.service.output; import com.recyclestudy.review.domain.ReviewURL; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.List; -public record ReviewSaveOutput(ReviewURL url, List scheduledAts) { +public record ReviewSaveOutput(ReviewURL url, List scheduledAts) { public static ReviewSaveOutput of(ReviewURL url, List scheduledAts) { - return new ReviewSaveOutput(url, scheduledAts); + return new ReviewSaveOutput(url, + scheduledAts.stream().map(scheduledAt -> scheduledAt.toInstant(ZoneOffset.UTC)).toList()); } } diff --git a/src/test/java/com/recyclestudy/member/controller/MemberControllerTest.java b/src/test/java/com/recyclestudy/member/controller/MemberControllerTest.java index 29b388f..f5d7aa6 100644 --- a/src/test/java/com/recyclestudy/member/controller/MemberControllerTest.java +++ b/src/test/java/com/recyclestudy/member/controller/MemberControllerTest.java @@ -17,6 +17,7 @@ import com.recyclestudy.member.service.output.MemberNotificationTimeFindOutput; import com.recyclestudy.member.service.output.MemberSaveOutput; import com.recyclestudy.restdocs.APIBaseTest; +import java.time.Instant; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.ChronoUnit; @@ -114,11 +115,11 @@ void findAllMemberDevices() { final MemberFindOutput.MemberFindElement device1 = new MemberFindOutput.MemberFindElement( DeviceIdentifier.from(headerIdentifier), - LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES).minusDays(1) + Instant.now().minus(1, ChronoUnit.DAYS).truncatedTo(ChronoUnit.MINUTES) ); final MemberFindOutput.MemberFindElement device2 = new MemberFindOutput.MemberFindElement( DeviceIdentifier.from("device-id-2"), - LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES) + Instant.now().truncatedTo(ChronoUnit.MINUTES) ); final MemberFindOutput output = new MemberFindOutput( @@ -337,7 +338,7 @@ void findAllMemberDevices_NullEmail() { Email.from("test@test.com"), List.of(new MemberFindOutput.MemberFindElement( DeviceIdentifier.from(headerIdentifier), - LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES) + Instant.now().truncatedTo(ChronoUnit.MINUTES) )) ); given(memberService.findAllMemberDevices(any())).willReturn(output); @@ -362,11 +363,11 @@ void findAllMemberDevices_WithHeader() { final MemberFindOutput.MemberFindElement device1 = new MemberFindOutput.MemberFindElement( DeviceIdentifier.from(headerIdentifier), - LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES).minusDays(1) + Instant.now().minus(1, ChronoUnit.DAYS).truncatedTo(ChronoUnit.MINUTES) ); final MemberFindOutput.MemberFindElement device2 = new MemberFindOutput.MemberFindElement( DeviceIdentifier.from("device-id-2"), - LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES) + Instant.now().truncatedTo(ChronoUnit.MINUTES) ); final MemberFindOutput output = new MemberFindOutput( diff --git a/src/test/java/com/recyclestudy/review/service/ReviewCycleServiceTest.java b/src/test/java/com/recyclestudy/review/service/ReviewCycleServiceTest.java index d43a0b5..57049b0 100644 --- a/src/test/java/com/recyclestudy/review/service/ReviewCycleServiceTest.java +++ b/src/test/java/com/recyclestudy/review/service/ReviewCycleServiceTest.java @@ -17,7 +17,9 @@ import com.recyclestudy.review.service.output.NextReviewOutput; import com.recyclestudy.review.service.output.ReviewSendOutput; import com.recyclestudy.review.service.output.ReviewSendOutput.ReviewSendElement; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.List; import java.util.Optional; import org.junit.jupiter.api.DisplayName; @@ -200,7 +202,7 @@ void findNextReview_returnsEarliestGroup() { // then assertSoftly(softly -> { - softly.assertThat(result.scheduledAt()).isEqualTo(t1); + softly.assertThat(result.scheduledAt()).isEqualTo(t1.toInstant(ZoneOffset.UTC)); softly.assertThat(result.count()).isEqualTo(1); }); } @@ -231,7 +233,7 @@ void findNextReview_countsSameScheduledAt() { // then assertSoftly(softly -> { - softly.assertThat(result.scheduledAt()).isEqualTo(t1); + softly.assertThat(result.scheduledAt()).isEqualTo(t1.toInstant(ZoneOffset.UTC)); softly.assertThat(result.count()).isEqualTo(3); }); } From 986a74e7231fce1fe31d5dd6004ee09179e0db2a Mon Sep 17 00:00:00 2001 From: jhan0121 Date: Tue, 10 Mar 2026 01:21:21 +0900 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20Jackson=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?application.yaml=20=EC=84=A4=EC=A0=95=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/config/JacksonConfig.java | 20 ------------------- src/main/resources/application.yaml | 5 +++++ .../controller/ReviewControllerTest.java | 1 + 3 files changed, 6 insertions(+), 20 deletions(-) delete mode 100644 src/main/java/com/recyclestudy/common/config/JacksonConfig.java diff --git a/src/main/java/com/recyclestudy/common/config/JacksonConfig.java b/src/main/java/com/recyclestudy/common/config/JacksonConfig.java deleted file mode 100644 index 8c3941a..0000000 --- a/src/main/java/com/recyclestudy/common/config/JacksonConfig.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.recyclestudy.common.config; - -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import java.util.TimeZone; -import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class JacksonConfig { - - @Bean - public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { - return builder -> builder - .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - .modules(new JavaTimeModule()) - .timeZone(TimeZone.getTimeZone("UTC")); - } -} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 97b0610..0362688 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -21,6 +21,11 @@ spring: flyway: enabled: true + jackson: + serialization: + write-dates-as-timestamps: false + time-zone: UTC + jpa: hibernate: ddl-auto: validate diff --git a/src/test/java/com/recyclestudy/review/controller/ReviewControllerTest.java b/src/test/java/com/recyclestudy/review/controller/ReviewControllerTest.java index 8e382fa..d6fc956 100644 --- a/src/test/java/com/recyclestudy/review/controller/ReviewControllerTest.java +++ b/src/test/java/com/recyclestudy/review/controller/ReviewControllerTest.java @@ -284,6 +284,7 @@ void findNextReview_success() { .get("/api/v1/reviews/next") .then() .statusCode(HttpStatus.OK.value()) + .body("scheduledAt", equalTo("2026-03-06T09:00:00Z")) .body("count", equalTo(3)); }