Java 공부 48일차(날짜와 시간이란?)2편

임선구·2025년 4월 5일

몸 비틀며 Java

목록 보기
49/58

오늘의 잔디


오늘의 공부


타임존 - ZonedDateTime

"Asia/Seoul" 같은 타임존 안에는 일광 절약 시간제에 대한 정보와 UTC+9:00와 같은 UTC로 부터 시간 차이인 오프셋 정보를 모두 포함하고 있다.

타임존 목록 예시

  • Europe/London
  • GMT
  • UTC
  • US/Arizona -07:00
  • America/New_York -05:00
  • Asia/Seoul +09:00
  • Asia/Dubai +04:00
  • Asia/Istanbul +03:00
  • Asia/Shanghai +08:00
  • Europe/Paris +01:00
  • Europe/Berlin +01:00

ZoneId

자바는 타임존을 ZoneId 클래스로 제공한다.

package time;
import java.time.ZoneId;public class ZoneIdMain {
 public static void main(String[] args) {
 for (String availableZoneId : ZoneId.getAvailableZoneIds()) {
 ZoneId zoneId = ZoneId.of(availableZoneId);
 System.out.println(zoneId + " | " + zoneId.getRules());
 }
 ZoneId zoneId = ZoneId.systemDefault();
 System.out.println("ZoneId.systemDefault = " + zoneId);
 ZoneId seoulZoneId = ZoneId.of("Asia/Seoul");
 System.out.println("seoulZoneId = " + seoulZoneId);
 }
}

실행 결과

Europe/London | ZoneRules[currentStandardOffset=Z]
UTC | ZoneRules[currentStandardOffset=Z]
GMT | ZoneRules[currentStandardOffset=Z]
Asia/Seoul | ZoneRules[currentStandardOffset=+09:00]
Asia/Dubai | ZoneRules[currentStandardOffset=+04:00]
US/Arizona | ZoneRules[currentStandardOffset=-07:00]
Asia/Istanbul | ZoneRules[currentStandardOffset=+03:00]
Asia/Shanghai | ZoneRules[currentStandardOffset=+08:00]
...
Europe/Paris | ZoneRules[currentStandardOffset=+01:00]
ZoneId.systemDefault = Asia/Seoul
seoulZoneId = Asia/Seoul

생성

  • ZoneId.systemDefault() : 시스템이 사용하는 기본 ZoneId 를 반환한다.
    • 각 PC 환경 마다 다른 결과가 나올 수 있다.
  • ZoneId.of() : 타임존을 직접 제공해서 ZoneId 를 반환한다.

ZoneId 는 내부에 일광 절약 시간 관련 정보, UTC와의 오프셋 정보를 포함하고 있다.

ZonedDateTime

ZonedDateTimeLocalDateTime 에 시간대 정보인 ZoneId 가 합쳐진 것이다.

ZonedDateTime 클래스

public class ZonedDateTime {
 private final LocalDateTime dateTime;
 private final ZoneOffset offset;
 private final ZoneId zone;
}

ZonedDateTime: 시간대를 고려한 날짜와 시간을 표현할 때 사용한다. 여기에는 시간대를 표현하는 타임존이 포함된
다.

  • 예) 2013-11-21T08:20:30.213+9:00[Asia/Seoul]
  • +9:00 은 UTC(협정 세계시)로 부터의 시간대 차이이다. 오프셋이라 한다. 한국은 UTC보다 +9:00 시간이다.
  • Asia/Seoul 은 타임존이라고 한다. 이 타임존을 알면 오프셋도 알 수 있다. +9:00 같은 오프셋 정보도 타임존에 포함된다.
  • 추가로 ZoneId 를 통해 타임존을 알면 일광 절약 시간제에 대한 정보도 알 수 있다. 따라서 일광 절약 시간제가
    적용된다.
package time;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class ZonedDateTimeMain {
 public static void main(String[] args) {
 ZonedDateTime nowZdt = ZonedDateTime.now();
 System.out.println("nowZdt = " + nowZdt);
 LocalDateTime ldt = LocalDateTime.of(2030, 1, 1, 13, 30, 50);
 ZonedDateTime zdt1 = ZonedDateTime.of(ldt, ZoneId.of("Asia/Seoul"));
 System.out.println("zdt1 = " + zdt1);
 ZonedDateTime zdt2 = ZonedDateTime.of(2030, 1, 1, 13, 30, 50, 0, ZoneId.of("Asia/Seoul"));
 System.out.println("zdt2 = " + zdt2);
 ZonedDateTime utcZdt = zdt2.withZoneSameInstant(ZoneId.of("UTC"));
 System.out.println("utcZdt = " + utcZdt);
 }
}

실행 결과

nowZdt = 2024-02-09T12:02:13.457712+09:00[Asia/Seoul]
zdt1 = 2030-01-01T13:30:50+09:00[Asia/Seoul]
zdt2 = 2030-01-01T13:30:50+09:00[Asia/Seoul]
utcZdt = 2030-01-01T04:30:50Z[UTC]

생성

  • now() : 현재 날짜와 시간을 기준으로 생성한다. 이때 ZoneId 는 현재 시스템을 따른다.
    ( ZoneId.systemDefault() )
  • of(...) : 특정 날짜와 시간을 기준으로 생성한다. ZoneId 를 추가해야 한다.
    • LocalDateTimeZoneId 를 추가해서 생성할 수 있다.

타임존 변경

  • withZoneSameInstant(ZoneId) : 타임존을 변경한다. 타임존에 맞추어 시간도 함께 변경된다. 이 메서드를 사용하면 지금 다른 나라는 몇 시 인지 확인일 수 있다. 예를 들어서 서울이 지금 9시라면, UTC 타임존으로 변경하면 0시를 확인할 수 있다.

OffsetDateTime

OffsetDateTimeLocalDateTime 에 UTC 오프셋 정보인 ZoneOffset 이 합쳐진 것이다.

OffsetDateTime 클래스

public class OffsetDateTime {
 private final LocalDateTime dateTime;
 private final ZoneOffset offset;
}

OffsetDateTime: 시간대를 고려한 날짜와 시간을 표현할 때 사용한다. 여기에는 타임존은 없고, UTC로 부터의 시간대 차이인 고정된 오프셋만 포함한다.

  • 예) 2013-11-21T08:20:30.213+9:00
  • ZoneId 가 없으므로 일광 절약 시간제가 적용되지 않는다.
package time;
import java.time.*;
public class OffsetDateTimeMain {
 public static void main(String[] args) {
 OffsetDateTime nowOdt = OffsetDateTime.now();
 System.out.println("nowOdt = " + nowOdt);
 LocalDateTime ldt = LocalDateTime.of(2030, 1, 1, 13, 30, 50);
 System.out.println("ldt = " + ldt);
 OffsetDateTime odt = OffsetDateTime.of(ldt, ZoneOffset.of("+01:00"));
 System.out.println("odt = " + odt);
 }
}

실행 결과

nowOdt = 2024-02-13T15:03:36.422230+09:00
ldt = 2030-01-01T13:30:50
odt = 2030-01-01T13:30:50+01:00

ZoneOffset+01:00 처럼 UTC와의 시간 차이인 오프셋 정보만 보관한다.

ZonedDateTime vs OffsetDateTime

  • ZonedDateTime 은 구체적인 지역 시간대를 다룰 때 사용하며, 일광 절약 시간을 자동으로 처리할 수 있다. 사용자 지정 시간대에 따른 시간 계산이 필요할 때 적합하다.
  • OffsetDateTime 은 UTC와의 시간 차이만을 나타낼 때 사용하며, 지역 시간대의 복잡성을 고려하지 않는다.
    시간대 변환 없이 로그를 기록하고, 데이터를 저장하고 처리할 때 적합하다.

참고
ZonedDateTime 이나 OffsetDateTime 은 글로벌 서비스를 하지 않으면 잘 사용하지 않는다. 따라서 너무 깊이있게 파기 보다는 대략 이런 것이 있다 정도로만 알아두면 된다. 실무에서 개발하면서 글로벌 서비스를 개발할 기회가 있다면 그때 필요한 부분을 찾아서 깊이있게 학습하면 된다.

기계 중심의 시간 - Instant

Instant 는 UTC(협정 세계시)를 기준으로 하는, 시간의 한 지점을 나타낸다. Instant 는 날짜와 시간을 나노초 정밀도로 표현하며, 1970년 1월 1일 0시 0분 0초(UTC 기준)를 기준으로 경과한 시간으로 계산된다.
쉽게 이야기해서 Instant 내부에는 초 데이터만 들어있다. (나노초 포함)
따라서 날짜와 시간을 계산에 사용할 때는 적합하지 않다.

Instant 클래스

public class Instant {
 private final long seconds;
 private final int nanos;
 ...
}
  • UTC 기준 1970년 1월 1일 0시 0분 0초라면 seconds 에 0이 들어간다.
  • UTC 기준 1970년 1월 1일 0시 0분 10초라면 seconds 에 10이 들어간다.
  • UTC 기준 1970년 1월 1일 0시 1분 0초라면 seconds 에 60이 들어간다.

참고 - Epoch 시간
Epoch time(에포크 시간), 또는 Unix timestamp는 컴퓨터 시스템에서 시간을 나타내는 방법 중 하나이다. 이는
1970년 1월 1일 00:00:00 UTC부터 현재까지 경과된 시간을 초 단위로 표현한 것이다. 즉, Unix 시간은 1970년 1월
1일 이후로 경과한 전체 초의 수로, 시간대에 영향을 받지 않는 절대적인 시간 표현 방식이다.
참고로 Epoch라는 뜻은 어떤 중요한 사건이 발생한 시점을 기준으로 삼는 시작점을 뜻하는 용어다.
Instant 는 바로 이 Epoch 시간을 다루는 클래스이다.

Instant 특징

  • 장점:
    • 시간대 독립성: Instant 는 UTC를 기준으로 하므로, 시간대에 영향을 받지 않는다. 이는 전 세계 어디서나 동일한 시점을 가리키는데 유용하다.
    • 고정된 기준점: 모든 Instant 는 1970년 1월 1일 UTC를 기준으로 하기 때문에, 시간 계산 및 비교가 명확하고 일관된다.
  • 단점:
    • 사용자 친화적이지 않음: Instant 는 기계적인 시간 처리에는 적합하지만, 사람이 읽고 이해하기에는 직
      관적이지 않다. 예를 들어, 날짜와 시간을 계산하고 사용하는데 필요한 기능이 부족하다.
    • 시간대 정보 부재: Instant 에는 시간대 정보가 포함되어 있지 않아, 특정 지역의 날짜와 시간으로 변환하려면 추가적인 작업이 필요하다.

사용 예

  • 전 세계적인 시간 기준 필요 시: Instant 는 UTC를 기준으로 하므로, 전 세계적으로 일관된 시점을 표현할 때
    사용하기 좋다. 예를 들어, 로그 기록이나, 트랜잭션 타임스탬프, 서버 간의 시간 동기화 등이 이에 해당한다.
  • 시간대 변환 없이 시간 계산 필요 시: 시간대의 변화 없이 순수하게 시간의 흐름(예: 지속 시간 계산)만을 다루고
    싶을 때 Instant 가 적합하다. 이는 시간대 변환의 복잡성 없이 시간 계산을 할 수 있게 해준다.
  • 데이터 저장 및 교환: 데이터베이스에 날짜와 시간 정보를 저장하거나, 다른 시스템과 날짜와 시간 정보를 교환할
    Instant 를 사용하면, 모든 시스템에서 동일한 기준점(UTC)을 사용하게 되므로 데이터의 일관성을 유지하기 쉽다.

일반적으로 날짜와 시간을 사용할 때는 LocalDateTime , ZonedDateTime 등을 사용하면 된다. Instant 는 날짜를 계산하기 어렵기 때문에 앞서 사용 예와 같은 특별한 경우에 한정해서 사용하면 된다.

package time;
import java.time.Instant;
import java.time.ZonedDateTime;
public class InstantMain {
 public static void main(String[] args) {
 //생성
 Instant now = Instant.now(); //UTC 기준
 System.out.println("now = " + now);
 ZonedDateTime zdt = ZonedDateTime.now();
 Instant from = Instant.from(zdt);
 System.out.println("from = " + from);
 Instant epochStart = Instant.ofEpochSecond(0); System.out.println("epochStart = " + epochStart);
 //계산
 Instant later = epochStart.plusSeconds(3600);
 System.out.println("later = " + later);
 //조회
 long laterEpochSecond = later.getEpochSecond();
 System.out.println("laterEpochSecond = " + laterEpochSecond);
 }
}

실행 결과

now = 2024-02-13T06:46:07.101393Z
from = 2024-02-13T06:46:07.117732Z
epochStart = 1970-01-01T00:00:00Z
later = 1970-01-01T01:00:00Z
laterEpochSecond = 3600

생성

  • now() : UTC를 기준 현재 시간의 Instant 를 생성한다.
  • from() : 다른 타입의 날짜와 시간을 기준으로 Instant 를 생성한다. 참고로 Instant 는 UTC를 기준으로 하기 때문에 시간대 정보가 필요하다. 따라서 LocalDateTime 은 사용할 수 없다.
  • ofEpochSecond() : 에포크 시간을 기준으로 Instant 를 생성한다. 0초를 선택하면 에포크 시간인 1970년 1월 1일 0시 0분 0초로 생성된다.

계산

  • plusSeconds() : 초를 더한다. 초, 밀리초, 나노초 정도만 더하는 간단한 메서드가 제공된다.

조회

  • getEpochSecond() : 에포크 시간인 UTC 1970년 1월 1일 0시 0분 0초를 기준으로 흐른 초를 반환한다.
  • 여기서는 앞서 에포크 시간에 3600초를 더했기 때문에 3600이 반환된다.

Instant 정리

  • 조금 특별한 시간, 기계 중심, UTC 기준
  • 에포크 시간으로부터 흐른 시간을 초 단위로 저장
  • 전세계 모든 서버 시간을 똑같이 맞출 수 있음, 항상 UTC 기준이므로 한국에 있는 Instant , 미국에 있는
    Instant 의 시간이 똑같음
  • 서버 로그, epoch 시간 기반 계산이 필요할 때, 간단히 두 시간의 차이를 구할 때
  • 단점: 초 단위의 간단한 연산 가능, 복잡한 연산 못함
  • 대안: 날짜 계산 필요하면 LocalDateTime 또는, ZonedDateTime 사용
profile
끝까지 가면 내가 다 이겨

0개의 댓글