시간(時間): 어떤 시각에서 어떤 시각까지의 사이.
국립국어원 표준국어대사전
'시간'은 어떤 시각에서 어떤 시각까지의 사이를 나타낸다.
그리고 우리는 다음과 같은 상황들에서 시간을 사용한다.
캐시가 만료되기 까지 유효한 시간
생성된 JWT 토큰이 만료되기 까지 유효한 시간
API가 호출되고 응답까지 걸리는 시간
프로그래밍에서 이 '시간'은 어떻게 나타낼 수 있을까?
enum class StatisticsStandard(
private val duration: Long,
) {
DAILY(1440),
WEEKLY(10080),
MONTHLY(312480),
;
}
다음과 같은 코드가 있다. 1440
, 10080
, 312480
은 무엇을 의미할까?
쉽게 와닿지 않는다. 설령 셈을 잘못해서 24시간을 1440초가 아닌, 1400초라고 오타를 내더라도 오류를 찾기가 쉽지가 않다.
enum class StatisticsStandard(
private val duration: Long,
) {
DAILY(24 * 60), // 1일
WEEKLY(24 * 60 * 7), // 7일
MONTHLY(24 * 60 * 7 * 31), // 31일
;
}
주석과 곱셈을 이용해서 표현하면 그나마 낫다.
하지만 여전히 더러우며, 1년 2개월 5일
과 같은 시간은 표현하기에 장황해질 수도 있고, 또한 현재 초 (second)를 이용해서 시간을 나타내고 있는데, 밀리세컨드 단위의 시간이 필요할 경우 크게 애로사항이 생길 수 있다.
어떻게 하면 이것을 더 깔끔하게 나타낼 수 있을까?
Java Duration API
A time-based amount of time, such as '34.5 seconds'.
This class models a quantity or amount of time in terms of seconds and nanoseconds.'34.5초'와 같은 시간 기반 시간입니다.
이 클래스는 수량 또는 시간을 초 및 나노초 단위로 모델링합니다.
Oracle - Duration (Java Platform SE 8)
이를 해결하기 위해 Java에서는 Duration
이라는 클래스를 제공한다.
import java.time.Duration;
enum class StatisticsStandard(
private val duration: Duration,
) {
DAILY(Duration.ofDays(1)),
WEEKLY(Duration.ofDays(7)),
MONTHLY(Duration.ofDays(31)),
;
fun getMinutes(): Long {
return this.duration.toMinutes();
}
fun getHours(): Long {
return this.duration.toHours();
}
}
훨씬 간결하다.
그 자체로 시간 단위를 명확히 코드로 표현하고, toMillis()
, toMinutes()
, toHours()
과 같이 다양한 단위 변환 메서드를 제공하여 편리하다.
Duration 활용하기
자바에서 기본적으로 제공하는 객체이기 때문에, 자바의 객체들과, Spring과 같은 프레임워크나 라이브러리에서 기본적으로 Duration
을 지원한다.
LocalDateTime에서 시간 더하고 빼기
val date = LocalDateTime.now()
date.minus(Duration.ofSeconds(10))
LocalDateTime
에서 시간을 더하고 빼는 데에도 간단히 사용할 수 있다.
CaffeineCache에서 캐시 만료 시간 설정하기
enum class CacheType(
val cacheName: String,
val expiredAfterWrite: Duration,
) {
POPUlAR_ARTICLE("popular-articles", Duration.ofDays(1)),
RECENT_ARTICLE("recent-articles", Duration.ofMinutes(10)),
// ...
;
}
@Configuration
class CacheConfig {
@Bean
fun cacheManager(): CacheManager {
val simpleCacheManager = SimpleCacheManager()
val caches =
CacheType.entries.map {
CaffeineCache(
it.cacheName,
Caffeine
.newBuilder()
.expireAfterWrite(it.expiredAfterWrite)
.maximumSize(it.maximumSize)
.build(),
)
}
simpleCacheManager.setCaches(caches)
return simpleCacheManager
}
}
다음과 같이 Caffeine
에서 expreAfterWrite
값을 설정할 때에도 기본적으로 Duration
을 지원하기 때문에 산뜻히 사용할 수 있다.
Kotlin Duration API
Represents the amount of time one instant of time is away from another instant.
한 순간이 다른 순간과 떨어져 있는 시간을 나타냅니다.
kotlin-stdlib
Kotlin도 Duration
이라는 클래스를 제공한다.
enum class StatisticsStandard(
private val duration: Duration,
) {
DAILY(1.days), // 1일
WEEKLY(7.days), // 7일
MONTHLY(31.days), // 31일
;
fun getMinutes(): Long {
return this.duration.inWholeMinutes);
}
fun getHours(): Long {
return this.duration.inWholeHours();
}
}
kotlin.time.Duration
의 생성자는 기본적으로 internal 하므로 호출할 수 없고, 대신 companion object 안의 확장 함수를 이용해서 객체를 생성할 수 있다.
아까의 Duration.ofDays
와 같은 형태보다 훨씬 깔끔하게 1.days
와 같은 방식으로 사용한다.
장점
덧셈 뺄셈의 가독성
// Java
Duration.ofHours(1).plusMinutes(30)
// Kotlin
1.hours + 30.minutes
1.hours - 500.milliseconds
Java의 Duration
에서 1분 30초와 같은 시간을 표현하고 싶을 때에는 plusMinutes
, plusHours
등과 같은 메소드를 활용해야 하는데, 코드가 조금 길다.
하지만 Kotlin은 연산자 오버로딩을 지원하기 때문에, Duration끼리의 덧셈 뺄셈을 아주 간결하게 처리할 수 있다.
Java Duration과 상호호환
val jDuration = java.time.Duration.ofSeconds(10)
val kDuration = 10.seconds
jDuration.toKotlinDuration()
kDuration.toJavaDuration()
Kotlin에서는 각 Duration끼리 변환을 하는 확장함수를 따로 제공하므로 상호호환이 굉장히 자유롭다.
toComponents를 이용한 유연한 처리
// 1시간 2분 3초 -> 총 3723초
val duration = 3723.seconds
// 1) 시, 분, 초 단위로 분해하기
duration.toComponents { hours, minutes, seconds ->
println("시: $hours, 분: $minutes, 초: $seconds")
}
// 출력: 시: 1, 분: 2, 초: 3
// 2) 4개 단위로 분해: 시, 분, 초, 나노초
duration.toComponents { hours, minutes, seconds, nanoseconds ->
println("시: $hours, 분: $minutes, 초: $seconds, 나노초: $nanoseconds")
}
// 출력: 시: 1, 분: 2, 초: 3, 나노초: 0
// 3) 일(Days), 시, 분, 초 단위도 가능
val bigDuration = (24 * 3 + 5) * 3600 // 3일 5시간
bigDuration.seconds.toComponents { days, hours, minutes, seconds ->
println("일: $days, 시: $hours, 분: $minutes, 초: $seconds")
}
// 출력: 일: 3, 시: 5, 분: 0, 초: 0
toComponents
함수를 통해 시·분·초·나노초 등 원하는 형태로 분해(디스트럭쳐링)할 수 있다.
코드 실행 시간 측정에도 활용 가능
/* Java */
val start = System.currentTimeMillis()
veryBigLogic()
val end = System.currentTimeMillis()
println("${end - start} ms 소요됨")
/* Kotlin */
val elapsed: Duration = measureTime {
veryBigLogic()
}
println("${elapsed.inWholeMilliseconds} ms 소요됨")
Kotlin에서 제공하는 measureTime을 이용하면 간단하게 로직 수행 시간을 측정하고, Duration
으로 결과를 반환받을 수 있다