Java
Java의 시간 클래스들
minturtle
2024. 11. 15. 15:12
반응형
개요
- JAVA의 발전에 따라 사용하는 JAVA의 시간 클래스를 다루어보고 각 클래스들의 차이점과 주의 사항에 대해 알아보고자 합니다.
JAVA의 발전에 따른 JAVA 라이브러리의 변경
- 초기 Java에서는 Date와 Calander를 사용하여 시간을 표현하였습니다. 그러나 이 라이브러리들은 TimeZone을 표현하기 불편하다거나, 클래스의 인터페이스가 불편한 문제점이 있었습니다. 그리고 가장 큰 문제는 가변 객체이기 때문에 다양한 환경에서 하나의 객체를 나타낼 때 안전성을 보장하지 못했습니다.
- 그래서 이를 개선해서 나온게 joda의 Joda-Time 라이브러리로, 위 초기 Java 라이브러리에서 발생한 문제를 모두 해결한 라이브러리였습니다.
- Java 1.8 버젼에서 Joda-Time 라이브러리와 유사한 java.time 패키지를 도입하여 현재 우리가 사용하는 시간 관련 클래스들을 추가하게 됩니다.
Temporal, TemporalAccessor, TemporalAmount 인터페이스에 대한 소개
- java.time에는 시간을 나타내기 위한 여러 클래스들이 존재하는데, 이러한 클래스들을 추상화 시킨 인터페이스가 대표적으로 3개 존재합니다.
- TemporalAccessor: 이를 구현한 클래스가 시간을 표현하는 객체임을 나타내고, 해당 클래스가 시간 또는 날짜를 지원하는지에 대해 체크하는 기능을 제공합니다.
- Temporal : TemporalAccessor의 자식 인터페이스로, 시간에 대한 연산(plus, minus 등)을 수행하는 기능을 제공합니다.
- TemporalAmount: 시간 그 자체가 아닌 시간의 양을 표현하고, 시간의 양에 대한 연산을 지원합니다.
- Temporal을 구현한 클래스로는 LocalDateTime, ZonedDateTime, OffsetDateTime이 존재하며, 이는 모두 특정 시간을 나타내는 클래스입니다.
- TemporalAmount를 구현한 클래스로는 Period와 Duration이 있으며, 이는 모두 시간의 양을 나타내는 클래스입니다.
- java.time 패키지의 시간 관련 클래스들에 대한 상속 관계를 도식화 하면 아래와 같습니다.
Period, Duration에 대한 소개
- Period와 Duration은 모두 TemporalAmount를 구현하여 시간의 양을 나타내는 클래스이며, 차이점은 아래와 같습니다.
- Period는 일(day) 이상의 시간 단위를 다룹니다.
- Duration은 시(hour) 이하의 시간 단위를 다룹니다.
LocalDateTime, ZonedDateTime, OffsetDateTime에 대한 소개 및 주의 사항
- LocalDateTime과 OffsetDateTime, ZonedDateTime은 모두 Temporal을 구현하여 특정 시간을 나타내는 클래스이며, 차이점은 아래와 같습니다.
- LocalDateTime : 단순히 시간과 날짜 정보만을 포함합니다.
- OffsetDateTime : UTC 시간 기준 OFFSET 정보를 추가하여 저장합니다.
- ZonedDateTime : UTC 시간 기준 타임존 정보를 추가하여 저장합니다.
- LocalDateTime : 2024-07-29T12:00 OffsetDateTime : 2024-07-29T12:00+09:00 ZonedDateTime : 2024-07-29T12:00+09:00[Asia/Seoul]
- 그럼 ZonedDateTime과 OffsetDateTime이 정확히 어떤 차이를 가지는지에 대해 궁금해 할 수 있습니다. 결론부터 말하자면 ZonedDateTime은 Summer Time이라는 개념을 자동으로 처리해 줍니다!
- SummerTime을 이해하기 위해서 아래의 그림을 봅시다.
SummerTime에 대한 설명
- 위 그림은 프랑스의 TimeZone으로, 프랑스는 Summer Time이라는 제도를 도입하여 여름에는 UTC +02:00을, 여름 외에는 UTC + 01:00을 사용합니다.
- 파리에는 3월 마지막 일요일에 SummerTime을 적용하여 TimeZone이 +01:00에서 +02:00으로 변경되며 3월 마지막 일요일 새벽 1시에서 바로 새벽 3시로 넘어 갑니다.
- 또한 10월 마지막 주 일요일에 SummerTime이 해제되어 TimeZone이 +02:00에서 +01:00으로 변경되며 10월 마지막 일요일 새벽 3시가 새벽 2시로 넘어갑니다.
ZonedDateTime에서의 지원
- ZonedDateTime은 이러한 SummerTime을 직접적으로 지원해주기 때문에, 개발자가 수동으로 Offset을 바꿀필요 없이 자동으로 변경해줍니다. 아래의 코드는 SummerTime이 시작되는 시간과 종료되는 시간에 시간이 조정되는 것을 보여주는 예시 코드입니다.
var summerTimeStartDateTime = ZonedDateTime.of(
LocalDateTime.of(2024, 3, 31, 1, 55, 0),
ZoneId.of("Europe/Paris")
)
var summerTimeEndDateTime = ZonedDateTime.of(
LocalDateTime.of(2024, 10, 27, 2, 55, 0),
ZoneId.of("Europe/Paris")
)
println("Summer Time Start")
for(i in 0 .. 5){
println(summerTimeStartDateTime)
summerTimeStartDateTime = summerTimeStartDateTime.plusMinutes(1)
}
println("Summer Time End")
for(i in 0 .. 5){
println(summerTimeEndDateTime)
summerTimeEndDateTime = summerTimeEndDateTime.plusMinutes(1)
}
출력
Summer Time Star
t2024-03-31T01:55+01:00[Europe/Paris]
2024-03-31T01:56+01:00[Europe/Paris]
2024-03-31T01:57+01:00[Europe/Paris]
2024-03-31T01:58+01:00[Europe/Paris]
2024-03-31T01:59+01:00[Europe/Paris]
2024-03-31T03:00+02:00[Europe/Paris]
Summer Time End
2024-10-27T02:55+02:00[Europe/Paris]
2024-10-27T02:56+02:00[Europe/Paris]
2024-10-27T02:57+02:00[Europe/Paris]
2024-10-27T02:58+02:00[Europe/Paris]
2024-10-27T02:59+02:00[Europe/Paris]
2024-10-27T02:00+01:00[Europe/Paris]
- 이렇게 자동으로 시간을 맞춰주는 것은 정말 좋지만, 시스템이 예상치 못하게 동작할 수 있기 때문에 사용에 주의가 필요합니다.
Instant에 대한 소개
- Instant는 Temporal 인터페이스를 구현하여 특정 시간을 표현하는 클래스입니다. OffsetDateTime이나 ZonedDateTime과는 다르게 UTC 시간이 아닌 Epoch 시간(UNIX 시간)을 사용하며, Epoch 시간은 1970년 1월 1일 00시 00분을 기준으로 특정 시간이 몇초나 흘렀는지를 나타내주는 클래스이며, 아래의 특징과 장점을 가집니다.
- 시간 정보를 Long 형으로 저장합니다. 따라서 DB에 저장하거나 plus, minus 연산을 사용할 시 성능상의 이점을 얻을 수 있습니다.
- 특정 TimeZone에 종속적이지 않은 절대적인 시간 기준을 따르기 때문에 통일된 시간 처리가 필요할 경우 사용하기 좋습니다.
- 그러나 instant는 정수형으로 시간을 저장하기 때문에 사람이 알아보기 어려우므로 필요할 경우 ZonedDateTime으로 바꾸어 사용해야 합니다.
- 다음으로 Instant가 사용될만한 예제를 하나 소개해보겠습니다. 한국과 이탈리아가 한국 시간으로 6월 18일 20시 30분에 축구 경기를 했다고 가정했을 때, 이를 이탈리아 시간으로 바꾸는 코드입니다.
val koreaZoneId = ZoneId.of("Asia/Seoul")
val italyZoneId = ZoneId.of("Europe/Rome")
val koreaDateTime = LocalDateTime.of(2024, 7, 29, 20, 30)
val koreaZonedDateTime = ZonedDateTime.of(koreaDateTime, koreaZoneId)
val instant = koreaZonedDateTime.toInstant()
val italyZonedDateTime = instant.atZone(italyZoneId)
- 마지막으로 Instant와 LocalDateTime의 연산 속도를 비교해보았습니다. 아래는 instant를 사용하여 1분을 1000만번 더했을때의 성능을 측정하는 코드입니다.
fun main(){
val startTime = System.nanoTime()
var instant = Instant.now()
val start = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Seoul"))
for(i in 1 .. 10000000){
instant = instant.plus(1, ChronoUnit.MINUTES);
}
val endTime = System.nanoTime()
println("천만번 변환 시간 : ${endTime - startTime} ns")
val end = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Seoul"))
print("결과 : ${Duration.between(start, end).toMinutes()}분 차이 발생");
}
천만번 변환 시간 : 84975100 ns
결과 : 10000000분 차이 발생
fun main(){
val startTime = System.nanoTime()
val start = LocalDateTime.now()
var end = start
for(i in 1 .. 10000000){
end = end.plusMinutes(1)
}
val endTime = System.nanoTime()
println("천만번 변환 시간 : ${endTime - startTime} ns")
print("결과 : ${Duration.between(start, end).toMinutes()}분 차이 발생");
}
천만번 변환 시간 : 169768300 ns
결과 : 10000000분 차이 발생
반응형