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

임선구·2025년 4월 5일

몸 비틀며 Java

목록 보기
48/58

오늘의 잔디


오늘의 공부


날짜와 시간 라이브러리가 필요한 이유

날짜와 시간을 계산하는 것은 단순하게 생각하면 쉬워보이지만, 실제로는 매우 어렵고 복잡하다.
왜 그런지 이유를 하나하나 살펴보자.

1. 날짜와 시간 차이 계산

특정 날짜에서 다른 날짜까지의 정확한 일수를 계산하는 것은 생각보다 복잡하다. 윤년, 각 달의 일수 등을 모두 고려해야 하며, 간단한 뺄셈 연산으로는 정확한 결과를 얻기 어렵다.

예시
2024년 1월 1일에서 2024년 2월 1일까지는 몇 일일까? 이 계산은 1월이 31일 까지라는 점을 고려해야 한다. 각각의 월 마다 날짜가 다르다.

2. 윤년 계산

지구가 태양을 한 바퀴 도는 데 걸리는 평균 시간은 대략 365.2425 일, 즉 365일 5시간 48분 45초 정도이다. 우리가 사용하는 그레고리력(현재 대부분의 세계가 사용하는 달력)은 1년이 보통 365일로 설정되어 있다. 따라서 둘의 시간이 정확히 맞지 않다. 이런 문제를 해결하기 위해 4년마다 하루(2월 29일)를 추가하는 윤년(leap year)을 도입한다. 쉽게 이야기해서 2월은 보통 2월 28일까지 있는데, 4년마다 한번은 2월이 29일까지 하루 더 있다.

윤년 계산은 간단해 보이지만 실제로는 매우 복잡하다. 윤년은 보통 4년마다 한 번씩 발생하지만, 100년 단위일 때는
윤년이 아니며, 400년 단위일 때는 다시 윤년이다.
이 규칙에 따라, 2000년과 2020년은 윤년이지만, 1900년과 2100년은 윤년이 아니다. 이러한 규칙을 사용함으로써
달력 연도는 태양 연도에 매우 가깝게 유지될 수 있다.

예시
2024년 1월 1일에서 2024년 3월 1일까지는 몇 일일까? 이 계산은 2024년이 윤년으로 2월이 29일 까지라는 점을 고려해야 한다.

3. 일광 절약 시간(Daylight Saving Time, DST) 변환

보통 3월에서 10월은 태양이 일찍 뜨고, 나머지는 태양이 상대적으로 늦게 뜬다. 시간도 여기에 맞추어 1시간 앞당기거나 늦추는 제도를 일광 절약 시간제 또는 썸머타임이라 한다. 일광 절약 시간은 국가나 지역에 따라 적용 여부와 시작 및 종료 날짜가 다르다. 이로 인해 날짜와 시간 계산 시 1시간의 오차가 발생할 수 있으며, 이를 정확히 계산하는 것은 복잡하다.
줄여서 DST는 각 나라마다 다르지만 보통 3월 중순 ~ 11월 초 정도까지 시행된다.
참고로 대한민국에서는 1988년 이후로는 시행하지 않는다.

예시
특정 지역에서는 3월의 마지막 일요일에 DST가 시작되어 10월의 마지막 일요일에 종료된다. 이 기간 동안 발생하는
모든 날짜와 시간 계산은 1시간을 추가하거나 빼는 로직을 포함해야 한다

4. 타임존 계산

세계는 다양한 타임존으로 나뉘어 있으며, 각 타임존은 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

GMT, UTC
London/ UTC / GMT는 세계 시간의 기준이 되는 00:00 시간대이다.

GMT (그리니치 평균시, Greenwich Mean Time)
처음 세계 시간을 만들 때 영국 런던에 있는 그리니치 천문대를 기준으로 했다. 태양이 그리니치 천문대를 통과할 때를
정오로 한다.

UTC(협정 세계시, Universal Time Coordinated)
역사적으로 GMT가 국제적인 시간 표준으로 사용되었고, UTC가 나중에 이를 대체하기 위해 도입되었다.
둘 다 경도 0°에 위치한 영국의 그리니치 천문대를 기준으로 하며, 이로 인해 실질적으로 같은 시간대를 나타낸다. 그러나 두 시간 체계는 시간을 정의하고 유지하는 방법에서 차이가 있다.
UTC는 원자 시계를 사용하여 측정한 국제적으로 합의된 시간 체계이다. 지구의 자전 속도가 변화하는 것을 고려하여
윤초를 추가하거나 빼는 방식으로 시간을 조정함으로써, 보다 정확한 시간을 유지한다. 우리가 일반적으로 사용할 때는
GMT와 UTC는 거의 차이가 없기 때문에 GMT와 UTC가 종종 같은 의미로 사용될 수 있지만, 정밀한 시간 측정과 국제적인 표준에 관해서는 UTC가 선호된다.

예시
상황: 서울에 있는 사람이 독일 베를린에 있는 사람과 미팅을 계획하고 있다. 서울의 타임존은 Asia/Seoul, UTC+9에
위치해 있고, 베를린의 타임존은 Europe/Berlin, UTC+1에 위치해 있다. 서울에서 오후 9:00에 미팅을 하려면 베를린에서는 몇 시일까?

타임존 차이: 서울(UTC+9)와 베를린(UTC+1) 사이의 타임존 차이는 8시간이다. 이는 서울의 시간이 베를린의 시간보
다 8시간 더 앞서있다는 것을 의미한다.

계산:

  • 서울 시간: 오후 9시
  • 베를린 시간: 오후 9시 - 8시간 = 오후 1시

주의할 점

  • 일광 절약 시간(DST): 일광 절약 시간이 적용되는 경우, 타임존 차이가 변할 수 있다. 예를 들어, 베를린에서
    DST가 적용되면 UTC+1 UTC+2가 되어, 타임존 차이는 7시간으로 줄어든다.
  • 예를 들어 베를린의 경우 3월 마지막 일요일에서 10월 마지막 일요일까지 DST가 적용된다.

이러한 복잡성 때문에 대부분의 현대 개발 환경에서는 날짜와 시간을 처리하기 위해 잘 설계된 라이브러리를 사용해야
한다. 이러한 라이브러리는 위에서 언급한 복잡한 계산을 추상화하여 제공하므로, 개발자는 보다 안정적이고 정확하며
효율적인 코드를 작성할 수 있다.

자바 날짜와 시간 라이브러리의 역사

자바는 날짜와 시간 라이브러리를 지속해서 업데이트 했다.

JDK 1.0 (java.util.Date)

  • 문제점
    • 타임존 처리 부족: 초기 Date 클래스는 타임존(time zone)을 제대로 처리하지 못했다.
    • 불편한 날짜 시간 연산: 날짜 간 연산이나 시간의 증감 등을 처리하기 어려웠다.
    • 불변 객체 부재: Date 객체는 변경 가능(mutable)하여, 데이터가 쉽게 변경될 수 있었고 이로 인해 버그
      가 발생하기 쉬웠다.
  • 해결책
    • JDK 1.1에서 java.util.Calendar 클래스 도입으로 타임존 지원 개선.
    • 날짜 시간 연산을 위한 추가 메소드 제공.

JDK 1.1 (java.util.Calendar)

  • 문제점
    • 사용성 저하: Calendar 는 사용하기 복잡하고 직관적이지 않았다.
    • 성능 문제: 일부 사용 사례에서 성능이 저하되는 문제가 있었다.
    • 불변 객체 부재: Calendar 객체도 변경 가능하여, 사이드 이펙트, 스레드 안전성 문제가 있었다.
  • 해결책
    • Joda-Time 오픈소스 라이브러리의 도입으로 사용성, 성능, 불변성 문제 해결.

Joda-Time

  • 문제점
    • 표준 라이브러리가 아님: Joda-Time은 외부 라이브러리로, 자바 표준에 포함되지 않아 프로젝트에 별도로 추가해야 했다.
  • 해결책
    • 자바 8에서 java.time 패키지(JSR-310)를 표준 API로 도입.

JDK 8(1.8) (java.time 패키지)

  • java.time 패키지는 이전 API의 문제점을 해결하면서, 사용성, 성능, 스레드 안전성, 타임존 처리 등에서 크게
    개선되었다. 변경 불가능한 불변 객체 설계로 사이드 이펙트, 스레드 안전성 보장, 보다 직관적인 API 제공으로 날짜와 시간 연산을 단순화했다.
  • LocalDate , LocalTime , LocalDateTime , ZonedDateTime , Instant 등의 클래스를 포함한다.
  • Joda-Time의 많은 기능을 표준 자바 플랫폼으로 가져왔다.

참고
자바가 표준으로 제공했던 Date , Calendar 는 사용성이 너무 떨어지고, 문제가 많은 라이브러리였다. 이런 문제를 해결하기 위해 참다참다 결국 Joda-Time이라는 오픈소스 라이브러리가 등장한다. Joda-Time의 편리함과 사용성 덕분에 이 라이브러리는 크게 대중화 되었다. 자바는 기존 날짜와 시간의 설계를 반성하고, Joda-Time을 만든 개발자를 데려와서 JSR-310( java.time )이라는 새로운 자바 표준 날짜와 시간 라이브러리를 정의한다.
실용적인 Joda-Time에 많은 자바 커뮤니티의 의견을 반영해서 좀 더 안정적이고 표준적인 날짜와 시간 라이브러리인
java.time 패키지가 성공적으로 완성되었다.
참고로 자바 표준 ORM 기술인 JPA도 비슷한 역사를 가지고 있다. 과거 자바가 표준으로 제공하는 ORM 기술이 너무 불편해서, 누군가 하이버네이트라는 ORM 오픈 소스를 만들었는데, 이 기술이 자바 표준 ORM 기술보다 더 대중화되었다. 자바는 하이버네이트를 만든 개발자를 대려와서 새로운 자바 ORM 기술 표준을 만들었는데, 이것이 바로 JPA이다.
java.time , JPA 모두 큰 성공을 거두고 자바의 메인 표준 기술로 완전히 자리 잡았다.

자바 날짜와 시간 라이브러리 소개

자바 날짜와 시간 라이브러리는 자바 공식 문서가 제공하는 다음 표 하나로 정리할 수 있다.

  • 원문:https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html
  • *: 초는 나노초 단위의 정밀도로 캡처된다. (밀리초, 나노초 가능)
  • **: 이 클래스는 이 정보를 저장하지는 않지만 이러한 단위로 시간을 제공하는 메서드가 있다.
  • ***: ZonedDateTimePeriod 를 추가하면 서머타임 또는 기타 현지 시간 차이를 준수한다.

LocalDate, LocalTime, LocalDateTime

  • LocalDate: 날짜만 표현할 때 사용한다. 년, 월, 일을 다룬다. 예) 2013-11-21
  • LocalTime: 시간만을 표현할 때 사용한다. 시, 분, 초를 다룬다. 예) 08:20:30.213
    • 초는 밀리초, 나노초 단위도 포함할 수 있다.
  • LocalDateTime: LocalDateLocalTime 을 합한 개념이다. 예) 2013-11-21T08:20:30.213

앞에 Local (현지의, 특정 지역의)이 붙는 이유는 세계 시간대를 고려하지 않아서 타임존이 적용되지 않기 때문이다.
특정 지역의 날짜와 시간만 고려할 때 사용한다.
예)

  • 애플리케이션 개발시 국내 서비스만 고려할 때
  • 나의 생일은 2016년 8월 16일이야.

ZonedDateTime, OffsetDateTime

  • ZonedDateTime: 시간대를 고려한 날짜와 시간을 표현할 때 사용한다. 여기에는 시간대를 표현하는 타임존이
    포함된다.
    • 예) 2013-11-21T08:20:30.213+9:00[Asia/Seoul]
    • +9:00 은 UTC(협정 세계시)로 부터의 시간대 차이이다. 오프셋이라 한다. 한국은 UTC보다 +9:00 시간이다.
    • Asia/Seoul 은 타임존이라 한다. 이 타임존을 알면 오프셋과 일광 절약 시간제에 대한 정보를 알 수 있다.
    • 일광 절약 시간제가 적용된다.
  • OffsetDateTime: 시간대를 고려한 날짜와 시간을 표현할 때 사용한다. 여기에는 타임존은 없고, UTC로 부터의 시간대 차이인 고정된 오프셋만 포함한다.
    • 예) 2013-11-21T08:20:30.213+9:00
    • 일광 절약 시간제가 적용되지 않는다.

Asia/Seoul 같은 타임존 안에는 일광 절약 시간제에 대한 정보와 UTC+9:00와 같은 UTC로 부터 시간 차이인 오프셋 정보를 모두 포함하고 있다.
일광 절약 시간제(DST, 썸머타임)을 알려면 타임존을 알아야 한다. 따라서 ZonedDateTime 은 일광 절약 시간제를 함께 처리한다. 반면에 타임존을 알 수 없는 OffsetDateTime 는 일광 절약 시간제를 처리하지 못한다.
ZonedDateTime 은 시간대를 고려해야 할 때 실제 사용하는 날짜와 시간 정보를 나타내는 데 더 적합하고,
OffsetDateTime 은 UTC로부터의 고정된 오프셋만을 고려해야 할 때 유용하다.

Year, Month, YearMonth, MonthDay

년, 월, 년월, 달일을 각각 다룰 때 사용한다. 자주 사용하지는 않는다.
DayOfWeek 와 같이 월, 화, 수, 목, 금, 토, 일을 나타내는 클래스도 있다.

Instant

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

Period, Duration

시간의 개념은 크게 2가지로 표현할 수 있다.

  • 특정 시점의 시간(시각)
    • 이 프로젝트는 2013년 8월 16일 까지 완료해야해
    • 다음 회의는 11시 30분에 진행한다.
    • 내 생일은 8월 16일이야.
  • 시간의 간격(기간)
    • 앞으로 4년은 더 공부해야 해
    • 이 프로젝트는 3개월 남았어
    • 라면은 3분 동안 끓어야 해

Period , Duration 은 시간의 간격(기간)을 표현하는데 사용된다.
시간의 간격은 영어로 amount of time(시간의 양)으로 불린다.

Period
두 날짜 사이의 간격을 년, 월, 일 단위로 나타낸다.

Duration
두 시간 사이의 간격을 시, 분, 초(나노초) 단위로 나타낸다.

기본 날짜와 시간 - LocalDateTime

가장 기본이 되는 날짜와 시간 클래스는 LocalDate , LocalTime , LocalDateTime 이다.

  • LocalDate: 날짜만 표현할 때 사용한다. 년, 월, 일을 다룬다. 예) 2013-11-21
  • LocalTime: 시간만을 표현할 때 사용한다. 시, 분, 초를 다룬다. 예) 08:20:30.213
    • 초는 밀리초, 나노초 단위도 포함할 수 있다.
  • LocalDateTime: LocalDateLocalTime 을 합한 개념이다. 예) 2013-11-21T08:20:30.213

앞에 Local (현지의, 특정 지역의)이 붙는 이유는 세계 시간대를 고려하지 않아서 타임존이 적용되지 않기 때문이다.
특정 지역의 날짜와 시간만 고려할 때 사용한다.
예)

  • 애플리케이션 개발시 국내 서비스만 고려할 때
  • 나의 생일은 2016년 8월 16일이야.
LocalDate
```java
package time;
import java.time.LocalDate;public class LocalDateMain {
 public static void main(String[] args) {
 LocalDate nowDate = LocalDate.now();
 LocalDate ofDate = LocalDate.of(2013, 11, 21);
 System.out.println("오늘 날짜 = " + nowDate);
 System.out.println("지정 날짜 = " + ofDate);
 //계산(불변)
 LocalDate plusDays = ofDate.plusDays(10);
 System.out.println("지정 날짜+10d = " + plusDays);
 }
}

실행 결과

오늘 날짜 = 2024-02-09
지정 날짜 = 2013-11-21
지정 날짜+10d = 2013-12-01

생성

  • now() : 현재 시간을 기준으로 생성한다.
  • of(...) : 특정 날짜를 기준으로 생성한다. 년, 월, 일을 입력할 수 있다.

계산

  • plusDays() : 특정 일을 더한다. 다양한 plusXxx() 메서드가 존재한다.

주의! - 불변
모든 날짜 클래스는 불변이다. 따라서 변경이 발생하는 경우 새로운 객체를 생성해서 반환하므로 반환값을 꼭 받아야 한다.

LocalTime
```java
package time;
import java.time.LocalTime;public class LocalTimeMain {
 public static void main(String[] args) {
 LocalTime nowTime = LocalTime.now();
 LocalTime ofTime = LocalTime.of(9, 10, 30);
 System.out.println("현재 시간 = " + nowTime);
 System.out.println("지정 시간 = " + ofTime);
 //계산(불변)
 LocalTime ofTimePlus = ofTime.plusSeconds(30);
 System.out.println("지정 시간+30s = " + ofTimePlus);
 }
}

실행 결과

현재 시간 = 11:52:51.219602
지정 시간 = 09:10:30
지정 시간+30s = 09:11:00

생성

  • now() : 현재 시간을 기준으로 생성한다.
  • of(...) : 특정 시간을 기준으로 생성한다. 시, 분, 초, 나노초를 입력할 수 있다.

계산

  • plusSeconds() : 특정 초를 더한다. 다양한 plusXxx() 메서드가 존재한다.

주의! - 불변
모든 날짜 클래스는 불변이다. 따라서 변경이 발생하는 경우 새로운 객체를 생성해서 반환하므로 반환값을 꼭 받아야 한다.

LocalDateTime

LocalDateTimeLocalDateLocalTime 을 내부에 가지고 날짜와 시간을 모두 표현한다.

LocalDateTime 클래스

public class LocalDateTime {
 private final LocalDate date;
 private final LocalTime time;
 ...
}
package time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class LocalDateTimeMain {
 public static void main(String[] args) {
 LocalDateTime nowDt = LocalDateTime.now();
 LocalDateTime ofDt = LocalDateTime.of(2016, 8, 16, 8, 10, 1);
 System.out.println("현재 날짜시간 = " + nowDt);
 System.out.println("지정 날짜시간 = " + ofDt);
 //날짜와 시간 분리
 LocalDate localDate = ofDt.toLocalDate();
 LocalTime localTime = ofDt.toLocalTime();
 System.out.println("localDate = " + localDate);
 System.out.println("localTime = " + localTime);
 //날짜와 시간 합체
 LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
 System.out.println("localDateTime = " + localDateTime);
 //계산(불변)
 LocalDateTime ofDtPlus = ofDt.plusDays(1000);
 System.out.println("지정 날짜시간+1000d = " + ofDtPlus);
 LocalDateTime ofDtPlus1Year = ofDt.plusYears(1);
 System.out.println("지정 날짜시간+1년 = " + ofDtPlus1Year);
 //비교
 System.out.println("현재 날짜시간이 지정 날짜시간보다 이전인가? " +
nowDt.isBefore(ofDt));
 System.out.println("현재 날짜시간이 지정 날짜시간보다 이후인가? " +nowDt.isAfter(ofDt));
 System.out.println("현재 날짜시간과 지정 날짜시간이 같은가? " +
nowDt.isEqual(ofDt));
 }
}

실행 결과

현재 날짜시간 = 2024-02-09T11:54:54.389163
지정 날짜시간 = 2016-08-16T08:10:01
localDate = 2016-08-16
localTime = 08:10:01
localDateTime = 2016-08-16T08:10:01
지정 날짜시간+1000d = 2019-05-13T08:10:01
지정 날짜시간+1년 = 2017-08-16T08:10:01
현재 날짜시간이 지정 날짜시간보다 이전인가? false
현재 날짜시간이 지정 날짜시간보다 이후인가? true
현재 날짜시간과 지정 날짜시간이 같은가? false

생성

  • now() : 현재 날짜와 시간을 기준으로 생성한다.
  • of(...) : 특정 날짜와 시간을 기준으로 생성한다.

분리
날짜( LocalDate )와 시간( LocalTime )을 toXxx() 메서드로 분리할 수 있다.

합체
LocalDateTime.of(localDate, localTime)
날짜와 시간을 사용해서 LocalDateTime 을 만든다.

계산

  • plusXxx() : 특정 날짜와 시간을 더한다. 다양한 plusXxx() 메서드가 존재한다.

비교

  • isBefore(): 다른 날짜시간과 비교한다. 현재 날짜와 시간이 이전이라면 true 를 반환한다.
  • isAfter(): 다른 날짜시간과 비교한다. 현재 날짜와 시간이 이후라면 true 를 반환한다.
  • isEqual(): 다른 날짜시간과 시간적으로 동일한지 비교한다. 시간이 같으면 true 를 반환한다.

isEqual() vs equals()

  • isEqual() 는 단순히 비교 대상이 시간적으로 같으면 true 를 반환한다. 객체가 다르고, 타임존이 달라도 시간적으로 같으면 true 를 반환한다. 쉽게 이야기해서 시간을 계산해서 시간으로만 둘을 비교한다.
    • 예) 서울의 9시와 UTC의 0시는 시간적으로 같다. 이 둘을 비교하면 true 를 반환한다.
  • equals() 객체의 타입, 타임존 등등 내부 데이터의 모든 구성요소가 같아야 true 를 반환한다.
    • 예) 서울의 9시와 UTC의 0시는 시간적으로 같다. 이 둘을 비교하면 타임존의 데이터가 다르기 때문에 false 를 반환한다.

profile
끝까지 가면 내가 다 이겨

0개의 댓글