From dc93f9845b3fac5b1a0f67a277d8807b2ddbead1 Mon Sep 17 00:00:00 2001 From: Klaus Date: Wed, 24 Jun 2026 12:37:26 +0900 Subject: [PATCH] =?UTF-8?q?feat(content-ranking):=20=EB=9E=AD=ED=82=B9=20?= =?UTF-8?q?=EA=B3=B5=EA=B0=9C=20=EC=8B=9C=EA=B0=81=20=EC=A0=95=EC=B1=85?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/AudioRankingSchedulePolicy.kt | 25 +++++++++++ .../domain/AudioRankingSchedulePolicyTest.kt | 42 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/domain/AudioRankingSchedulePolicy.kt create mode 100644 src/test/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/domain/AudioRankingSchedulePolicyTest.kt diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/domain/AudioRankingSchedulePolicy.kt b/src/main/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/domain/AudioRankingSchedulePolicy.kt new file mode 100644 index 00000000..ca74d0d3 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/domain/AudioRankingSchedulePolicy.kt @@ -0,0 +1,25 @@ +package kr.co.vividnext.sodalive.v2.content.ranking.domain + +import java.time.LocalDateTime +import java.time.LocalTime +import java.time.ZoneId + +class AudioRankingSchedulePolicy { + fun resolveVisibleFromAt(aggregationEndAtKst: LocalDateTime): LocalDateTime { + return aggregationEndAtKst.toLocalDate() + .atTime(VISIBLE_FROM_TIME) + .atZone(KST_ZONE) + .withZoneSameInstant(UTC_ZONE) + .toLocalDateTime() + } + + fun isVisible(visibleFromAtUtc: LocalDateTime, nowUtc: LocalDateTime): Boolean { + return !nowUtc.isBefore(visibleFromAtUtc) + } + + companion object { + private val KST_ZONE: ZoneId = ZoneId.of("Asia/Seoul") + private val UTC_ZONE: ZoneId = ZoneId.of("UTC") + private val VISIBLE_FROM_TIME: LocalTime = LocalTime.of(9, 0) + } +} diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/domain/AudioRankingSchedulePolicyTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/domain/AudioRankingSchedulePolicyTest.kt new file mode 100644 index 00000000..8652182b --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/content/ranking/domain/AudioRankingSchedulePolicyTest.kt @@ -0,0 +1,42 @@ +package kr.co.vividnext.sodalive.v2.content.ranking.domain + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import java.time.LocalDateTime + +class AudioRankingSchedulePolicyTest { + private val policy = AudioRankingSchedulePolicy() + + @Test + @DisplayName("집계 종료일과 같은 KST 날짜 09시를 UTC LocalDateTime으로 변환해 공개 시각을 산출한다") + fun shouldResolveVisibleFromAtAsSameKstDateNineAmConvertedToUtc() { + val aggregationEndAtKst = LocalDateTime.of(2026, 6, 8, 0, 0) + + val visibleFromAtUtc = policy.resolveVisibleFromAt(aggregationEndAtKst) + + assertEquals(LocalDateTime.of(2026, 6, 8, 0, 0), visibleFromAtUtc) + } + + @Test + @DisplayName("09시 KST 이전에는 새 스냅샷을 공개하지 않는다") + fun shouldNotBeVisibleBeforeNineAmKst() { + val visibleFromAtUtc = LocalDateTime.of(2026, 6, 8, 0, 0) + val nowUtc = LocalDateTime.of(2026, 6, 7, 23, 59) + + val visible = policy.isVisible(visibleFromAtUtc, nowUtc) + + assertFalse(visible) + } + + @Test + @DisplayName("09시 KST 경계와 이후에는 새 스냅샷을 공개한다") + fun shouldBeVisibleAtAndAfterNineAmKst() { + val visibleFromAtUtc = LocalDateTime.of(2026, 6, 8, 0, 0) + + assertTrue(policy.isVisible(visibleFromAtUtc, LocalDateTime.of(2026, 6, 8, 0, 0))) + assertTrue(policy.isVisible(visibleFromAtUtc, LocalDateTime.of(2026, 6, 8, 0, 1))) + } +}