인프런/김영한 자바

[김영한의 실전 자바 중급 1편] - 6. 날짜와 시간

sson-coding 2025. 10. 16. 12:31

본 글은 김영한 님의 『김영한의 실전 자바 - 중급 1편』 강의를 학습하며 정리한 내용입니다.
강의 자료에 포함된 일부 코드와 이미지를 참고하여 발췌·활용하였습니다.

자바 기본기를 제대로 다지고 싶으시다면, 아래 링크에서 강의를 확인해 보세요
『김영한의 실전 자바 - 중급 1편』 보러 가기
본게시물은 파트너스 활동의 일환으로 작성되었으며, 구매 시 소정의 수수료를 받을 수 있습니다.

 

김영한의 실전 자바 - 중급 1편| 김영한 - 인프런 강의

현재 평점 5.0점 수강생 10,701명인 강의를 만나보세요. 실무에 필요한 자바의 다양한 중급 기능을 예제 코드로 깊이있게 학습합니다. 실무에 필요한 다양한 자바 중급 기능, Object, 불변 객체, String

www.inflearn.com


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

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

날짜와 시간 차이 계산

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

윤년 계산

윤년은 4년마다 하루를 추가하는 것이다.
보통 2월28일 까지인 것을 4년마다 한 번씩 2월 29일까지 하루 더 있다.

윤년은 보통 4년마다 한 번씩 발생하지만, 100년 단위일 때는 윤년이 아니며, 400년 단위일 때는 다시 윤년이다.

이렇게 윤년 계산은 간단해 보이지만 매우 복잡하다.

일관 절약 시간 (Daylight Saving Time,DST) 변환

보통 3월에서 10월은 태양이 일찍 뜬다.
시간도 여기에 맞춰 1시간 앞당기거나 늦추는 제도를 일광 절약 시간제 또는 썸머타임이라고 한다.

일광 절약 시간은 국가나 지역에 따라 적용 여부와 시작 및 종료 날짜가 다르다.
이로 인해 날짜와 시간 계산 시 1시간의 오차가 발생할 수 있으며, 정확히 계산하는 것은 복잡하다.

DST 는 각 나라마다 다르지만 보통 3월 중순 ~ 11월 초 정도 까지 시행되고, 대한민국은 1988 이후 시행하지 않는다.

타임존 계산

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

세계 시간의 기준이 되는 00:00 시간대이다.

GMT(그리니치 평균시)

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

UTC(협정 세계시)

영국의 그리니치 천문대를 기준으로 한다.
UTC 는 원자 시계를 사용하여 측정한 국제적으로 합의된 시간 체계이다.
지구의 자전 속도가 변화하는 것을 고려하여 윤초를 추가하거나 빼는 방식으로 시간을 조정함으로써, 정확한 시간을 유지한다.

정밀한 시간 측정과 국제적인 표준에 관해서는 GMT 보다 UTC 가 선호된다.

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

Joda-Time

  • JDK 1.1 Calender 클래스를 해결하기 위해 만든 오픈소스 라이브러리
  • 문제점
    • 표준 라이브러리가 아님
    • 외부 라이브러리로 , 자바 표준에 포함되지 않아 프로젝트에 별도로 추가해야 했음
  • 해결책
    • 자바 8에서 java.time 패키지를 표준 API로 도입

JDK 8

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

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

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

원문:  https://docs.oracle.com/javase/tutorial/datetime/iso/overview.html

LocalDate,LocalTime,LocalDateTime

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

LocalDate

  • 날짜만 표현할 때 사용한다.
  • 년,월,일 을 다룬다.
  • 예) 2025-01-01

LocalTime

  • 시간만을 표현할 떄 사용한다.
  • 시,분,초 를 다룬다.
  • 예) 08:20:30.213

LocalDateTime

  • LocalDate,LocalTime 을 합한 개념이다.
  • 예) 2013-11-21T08:20:30.213

ZonedDateTime,OffsetDateTime

ZonedDateTime 은 시간대를 고려해야 할 때 실제 사용하는 날짜와 시간 정보를 나타내는 데 적합하고,
OffsetDateTime 은 UTC 로 부터의 고정된 오프셋만을 고려해야 할 때 유용하다.

ZonedDateTime

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

OffsetDateTime

  • 시간대를 고려한 날짜와 시간을 표현할 때 사용한다.
  • 타임존이 없고, UTC 로 부터의 시간대 차이인 고정된 오프셋만 포함한다.
  • 예) 2013-11-21T08:20:30.213+9:00
  • 일광 절약 시간제가 적용되지 않는다.

Instant

  • UTC 를 기준으로 하는, 시간의 한 지점을 나타낸다.
  • 날짜와 시간을 나노초 정밀도로 표현하며, 1970년 1월 1일 0시 0분 0초(UTC) 를 기준으로 경과한 시간으로 계산된다.
  • Instant 내부에는 초 데이터만 들어있다.

Period,Duration

시간은 크게 특정 시점의 시간(시각) 과 시간의 간격(기간) 으로 표현할 수 있다.
Period,Duration 은 시간의 간격(기간) 을 표현하는데 사용된다.

시간의 간격은 영어로 amount of time(시간의 양) 으로 불린다.

Period

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

Duration

  • 두 시간 사이의 간격을 시,분,초 단위로 나타낸다.

기본 날짜와 시간 - LocalDateTime

LocalDate

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

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

public class LocalDateTime {
 private final LocalDate date;
 private final LocalTime 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() : 특정 날짜와 시간을 기준으로 생성한다.
  • 분리
    • 날짜 와 시간을 toXxx() 메서드로 분리할 수 있다.
  • 합체
    • LocalDateTime.of(localDate,localTime) 을 사용해서 만든다.
  • 계산
    • plusXxx() : 특정 날짜와 시간을 더한다.
    • 다양한 plusXxx() 메서드가 존재한다.
  • 비교
    • isBefore() : 다른 날짜시간과 비교한다. 현재 날짜가 시간보다 이전이라면 true 를 반환한다.
    • isAfter() : 다른 날짜시간과 비교한다. 현재 날짜가 시간보다 이후라면 true 를 반환한다.
    • isEqual() : 다른 날짜시간과 시간적으로 동일한지 비교한다. 시간이 같으면 true 를 반환한다.
  • isEqual() vs equals()
    • isEqual
      • 단순히 비교 대상이 시간적으로 같으면 true 를 반환한다.
      • 객체가 다르고, 타임존이 달라도 시간적으로 같으면 된다.
    • equals()
      • 객체의 타입, 타임존 등등 내부 데이터의 모든 구성요소가 같아야 true 를 반환한다.

타임존 - ZonedDateTime

타임존 안에는 일광 절약 시간제에 대한 정보와 UTC로 부터 시간 차이인 오프셋 정보를 모두 포함하고 있다.

ZoneId

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

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

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 를 반환한다.
    • ZoneId.of() : 타임존을 직접 제공해서 ZoneId 를 반환한다.

ZonedDateTime

LocalDateTime 에 시간대 정보인 ZoneId 가 합쳐졌다.

public class ZonedDateTime {
 private final LocalDateTime dateTime;
 private final ZoneOffset offset;
 private final ZoneId zone;
}
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 는 현재 시스템을 따른다.
    • of() : 특정 날짜와 시간을 기준으로 생성한다. ZoneId 를 추가해야 한다.
  • 타임존 변경
    • withZoneSameInstant(ZoneId) : 타임존에 맞추어 시간도 함께 변경한다.

OffsetDateTime

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

public class OffsetDateTime {
 private final LocalDateTime dateTime;
 private final ZoneOffset offset;
}
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

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 은 UTC 와의 시간 차이인 오프셋 정보만 보관한다.

ZonedDateTime vs OffsetDateTime

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

두개 다 글로벌 서비스를 하지 않으면 잘 사용하지 않는다.
따라서 깊이 있게 파기 보다는 실무에서 개발하면서 글로벌 서비스를 개발할 기회가 있다면 그 때 학습해도 충분하다.


기계 중심의 시간 - Instant

특징

  • UTC 를 기준으로 하는, 시간의 한 지점을 나타낸다.
  • 날짜와 시간을 나노초 정밀도로 표현하며, 1970년 1월 1일 0시 0분 0초를 기준으로 경과한 시간으로 계산된다.
  • 내부에는 초 데이터만 들어있다.
public class Instant {
 private final long seconds;
 private final int nanos;
 ...
}

Epoch 시간(에포크 시간)

에포크 시간, Unix timestamp 는 컴퓨터 시스템에서 시간을 나타내는 방법 중 하나이다.
이는 1970년 1월 1일 00:00:00 UTC 로 부터 현재까지 경과된 시간을 초 단위로 표현한 것이다.

즉, Unix 시간은 1970년 1월 1일 이후로 경과한 전체 초의 수로, 시간대에 영향을 받지 않는 절대적인 시간 표현 방식이다.

Epoch 라는 뜻은 중요한 사건이 발생한 시점을 기준으로 삼는 시작점을 뜻하는 용어이다.
Instant 는 바로 이 에포크 시간을 다루는 클래스이다.

장점

  • 시간대 독립성 : UTC 를 기준으로 하므로, 시간대에 영향을 받지 않는다.
  • 고정된 기준점 : 모든 Instant 는 1970년 1월 1일을 기준으로 하기 때문에, 시간 계산 및 비교가 명확하고 일관된다.

단점

  • 사용자 친화적이지 않음 : 기계적인 시간 처리에는 적합하지만, 사람이 읽고 이해하기에는 직관적이지 않다. 예를 들어, 날짜와 시간을 계산하고 사용하는데 필요한 기능이 부족하다.
  • 시간대 정보 부재 : 시간대 정보가 포함되어 있지 않아, 특정 지역의 날짜와 시간으로 변환하려면 추가적인 작업이 필요하다.

사용 예

  • 전 세계적인 시간 기준 필요 시
    • 전 세계적으로 일관된 시점을 표현할 때 사용하기 좋다.
    • 로그 기록이나, 트랜잭션 타임스탬프, 서버 간의 시간 동기화 등이 해당한다.
  • 시간대 변환 없이 시간 계산 필요 시
    • 시간대 변환 없이 순수하게 시간의 흐름만을 다루고 싶을 때 적합하다.
  • 데이터 저장 및 교환
    • 데이터베이스에 날짜와 시간 정보를 저장하거나, 다른 시스템과 날짜와 시간 정보를 교환할 때 사용하면, 모든 시스템에서 동일한 기준점을 사용하게 되므로 데이터의 일관성을 유지하기 쉽다.
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() : 다른 타입의 날짜와 시간을 기준으로 생성한다.
      • UTC 를 기준으로 하기 때문에 시간대 정보가 필요하므로 LocalDateTime 은 사용할 수 없다.
    • ofEpochSecond() : 에포크 시간을 기준으로 생성한다.
      • 0초를 선택하면 에포크 시간인 1970년 1월 1일 0시 0분 0초로 생성된다.
  • 계산
    • plusSeconds() : 초를 더한다. 초, 밀리초, 나노초 정도만 더하는 간단한 메서드가 제공된다.
  • 조회
    • getEpochSecond : 에포크 시간인 UTC 를 기준으로 흐른 초를 반환한다.

정리

  • 에포크 시간으로부터 흐른 시간을 초 단위로 저장
  • 전세계 모든 서버 시간을 똑같이 맞출 수 있다.
  • 서버 로그 , epoch 시간 기반 계산이 필요할 대, 간단히 두 시간의 차이를 구할 때
  • 단점 : 초 단위의 간단한 연산 가능, 복잡한 연산 못함
  • 대안 : 날짜 계산 필요하면 LocalDateTime , ZonedDateTime 사용

기간, 시간의 간격 - Duration,Period

시간의 간격(기간)

Period

  • 두 날짜 사이의 간격을 년, 월, 일 단위로 나타낸다.
  • 이 프로젝트는 3개월 정도 걸릴 것 같아
  • 기념일이 183일 남았어
  • 프로젝트 시작일과 종료일 사이의 간격: 프로젝트 기간

Duration

  • 두 시간 사이의 간격을 시, 분, 초(나노초) 단위로 나타낸다.
  • 라면을 끓이는 시간은 3분이야
  • 영화 상영 시간은 2시간 30분이야
  • 서울에서 부산까지는 4시간이 걸려

Period

public class Period {
 private final int years;
 private final int months;
 private final int days;
}
import java.time.LocalDate;
import java.time.Period;

public class PeriodMain {
    public static void main(String[] args) {
        //생성
        Period period = Period.ofDays(10);
        System.out.println("period = " + period);
        //계산에 사용
        LocalDate currentDate = LocalDate.of(2030, 1, 1);
        LocalDate plusDate = currentDate.plus(period);
        System.out.println("현재 날짜: " + currentDate);
        System.out.println("더한 날짜: " + plusDate);
        //기간 차이
        LocalDate startDate = LocalDate.of(2023, 1, 1); 
        LocalDate endDate = LocalDate.of(2023, 4, 2);
        Period between = Period.between(startDate, endDate);
        System.out.println("기간: " + between.getMonths() + "개월 " + between.getDays() + "일");
    }
}

--실행 결과--
period = P10D
현재 날짜: 2030-01-01
더한 날짜: 2030-01-11
기간: 3개월 1일
  • 생성
    • of() : 특정 기간을 지정해서 생성한다.
      • of(년,월,일)
      • ofDays()
      • ofMonths()
      • ofYears()
  • 계산에 사용
    • 2030년 1월 1일에 10일을 더하면 2030년 1월 11일이 된다.
  • 기간 차이
    • Period.between(startDate,endDate) 와 같이 특정 날짜의 차이를 구하면 Period 가 반환된다.

Duration

내부에서 초를 기반으로 시, 분, 초를 계산해서 사용한다.

public class Duration {
 private final long seconds; 
 private final int nanos;
}
import java.time.Duration;
import java.time.LocalTime;

public class DurationMain {
    public static void main(String[] args) {
        //생성
        Duration duration = Duration.ofMinutes(30);
        System.out.println("duration = " + duration);
        LocalTime lt = LocalTime.of(1, 0);
        System.out.println("기준 시간 = " + lt);
        //계산에 사용
        LocalTime plusTime = lt.plus(duration);
        System.out.println("더한 시간 = " + plusTime);
        //시간 차이
        LocalTime start = LocalTime.of(9, 0);
        LocalTime end = LocalTime.of(10, 0);
        Duration between = Duration.between(start, end);
        System.out.println("차이: " + between.getSeconds() + "초");
        System.out.println("근무 시간: " + between.toHours() + "시간 " +
            between.toMinutesPart() + "분");
    }
}
--실행 결과--
duration = PT30M
기준 시간 = 01:00더한 시간 = 01:30
차이: 3600초
근무 시간: 1시간 0분
  • 생성
    • of() : 특정 시간을 지정해서 생성한다.
      • of(지정)
      • ofSeconds()
      • ofMinutes()
      • ofHours()
  • 계산에 사용
    • 1:00에 30분을 더하면 1:30이 된다. 라고 표현할 때 특정 시간에 30분이라는 시간(시간의 간격)을 더할 수 있다.
  • 시간 차이
    • Duration.between(start, end) 와 같이 특정 시간의 차이를 구하면 Duration 이 반환된다

날짜와 시간의 핵심 인터페이스

날짜와 시간은 특정 시점의 사간(시각) 과 시간의 간격(기간) 으로 나눌 수 있다.

  • 특정 시점의 시간 : Temporal 인스턴스를 구현한다.
    • 구현으로 LocalDateTime,LocalDate,LocalTime,ZonedDateTime,OffsetDateTime,Instant 등이 있다.
  • 시간의 간격(기간) : TemporalAmount 인터페이스를 구현한다.
    • 구현으로 Period,Duration 이 있다.

TemporalAccessor 인터페이스

  • 날짜와 시간을 읽기 위한 기본 인터페이스
  • 이 인터페이스는 특정 시점의 날짜와 시간 정보를 읽을 수 있는 최소한의 기능을 제공한다.

Temporal 인터페이스

  • TemporalAccessor 의 하위 인터페이스로, 날짜와 시간을 조작하기 위한 기능을 제공한다.

즉, TemporalAccessor 인터페이스는 읽기 전용 접근을, Temporal 은 읽기와 쓰기 모두를 지원한다.

TemporalAmount 인터페이스

  • 시간의 간격을 나타내며, 날짜와 시간 객체에 적용하여 그 객체를 조정할 수 있다.
  • 특정 날짜에 일정 기간을 더하거나 빼는 데 사용된다.

시간의 단위와 시간 필드

시간의 단위를 뜻하는 TemporalUnit(ChronoUnit)
시간의 각 필드를 뜻하는 TemporalField(ChronoField) 가 있다.

시간의 단위 - TemporalUnit, ChronoUnit

  • TemporalUnit 인터페이스는 날짜와 시간을 측정하는 단위를 나타내며, 주로 사용되는 구현체는
    java.time.temporal.ChronoUnit 열거형으로 구현되어 있다.
  • ChronoUnit 은 다양한 시간 단위를 제공한다.

시간 단위

날짜 단위

기타 단위

ChronoUnit 의 주요 메서드

public class ChronoUnitMain {
    public static void main(String[] args) {
        ChronoUnit[] values = ChronoUnit.values();
        for (ChronoUnit value : values) {
            System.out.println("value = " + value);
        }
        System.out.println("HOURS = " + ChronoUnit.HOURS);
        System.out.println("HOURS.duration = " +
            ChronoUnit.HOURS.getDuration().getSeconds());
        System.out.println("DAYS = " + ChronoUnit.DAYS);
        System.out.println("DAYS.duration = " +
            ChronoUnit.DAYS.getDuration().getSeconds());
        //차이 구하기
        LocalTime lt1 = LocalTime.of(1, 10, 0);
        LocalTime lt2 = LocalTime.of(1, 20, 0);
        long secondsBetween = ChronoUnit.SECONDS.between(lt1, lt2);
        System.out.println("secondsBetween = " + secondsBetween);
        long minutesBetween = ChronoUnit.MINUTES.between(lt1, lt2);
        System.out.println("minutesBetween = " + minutesBetween);
    }
}
--실행 결과--
value = Nanos
value = Micros
value = Millis
value = Seconds
value = Minutes
value = Hours
value = HalfDays
value = Days
value = Weeksvalue = Months
value = Years
value = Decades
value = Centuries
value = Millennia
value = Eras
value = Forever
HOURS = Hours
HOURS.duration = 3600
DAYS = Days
DAYS.duration = 86400
secondsBetween = 600
minutesBetween = 10

시간 필드 - ChronoField

날짜 및 시간을 나타내는데 사용되는 열거형이다.
다양한 필드를 통해 날짜와 시간의 특정 부분을 나타낸다.

특징

  • TemporalField 인터페이스는 날짜와 시간을 나타내는데 사용된다.
    주로 사용되는 구현체는 java.time.temporal.ChronoField 열거형으로 구현되어 있다.
  • ChronoField 는 다양한 필드를 통해 날짜와 시간의 특정 부분을 나타낸다.
  • 예를 들어 2024년 8월 16일이라고 하면 각각의 필드는 다음과 같다.
    • YEAR : 2024
    • MONTH_OF_YEAR : 8
    • DAY_OF_MONTH : 16

연도 관련 필드

월 관련 필드

주 및 일 관련 필드

시간 관련 필드

기타 필드

주요 메서드

public class ChronoFieldMain {
    public static void main(String[] args) {
        ChronoField[] values = ChronoField.values();
        for (ChronoField value : values) {
            System.out.println(value + ", range = " + value.range());
        }
        System.out.println("MONTH_OF_YEAR.range() = " +
            ChronoField.MONTH_OF_YEAR.range());
        System.out.println("DAY_OF_MONTH.range() = " +
            ChronoField.DAY_OF_MONTH.range());
    }
}

--실행 결과--
NanoOfSecond, range = 0 - 999999999
NanoOfDay, range = 0 - 86399999999999
MicroOfSecond, range = 0 - 999999
MicroOfDay, range = 0 - 86399999999
MilliOfSecond, range = 0 - 999
MilliOfDay, range = 0 - 86399999
SecondOfMinute, range = 0 - 59
SecondOfDay, range = 0 - 86399
MinuteOfHour, range = 0 - 59
MinuteOfDay, range = 0 - 1439
HourOfAmPm, range = 0 - 11
ClockHourOfAmPm, range = 1 - 12
HourOfDay, range = 0 - 23
ClockHourOfDay, range = 1 - 24
AmPmOfDay, range = 0 - 1
DayOfWeek, range = 1 - 7
AlignedDayOfWeekInMonth, range = 1 - 7
AlignedDayOfWeekInYear, range = 1 - 7
DayOfMonth, range = 1 - 28/31
DayOfYear, range = 1 - 365/366
EpochDay, range = -365243219162 - 365241780471
AlignedWeekOfMonth, range = 1 - 4/5
AlignedWeekOfYear, range = 1 - 53
MonthOfYear, range = 1 - 12ProlepticMonth, range = -11999999988 - 11999999999
YearOfEra, range = 1 - 999999999/1000000000
Year, range = -999999999 - 999999999
Era, range = 0 - 1
InstantSeconds, range = -9223372036854775808 - 9223372036854775807
OffsetSeconds, range = -64800 - 64800
MONTH_OF_YEAR.range() = 1 - 12
DAY_OF_MONTH.range() = 1 - 28/31

정리

TemporalUnit(ChronoUnit) , TemporalField(ChronoField) 는 단독으로 사용하기 보다는 주로 날짜와 시간을 조회하거나 조작할 때 사용한다.


날짜와 시간 조회하고 조작하기

날짜와 시간 조회하기

날짜와 시간을 조회하려면 날짜와 시간의 필드를 뜻하는 ChronoField 가 사용된다.

public class GetTimeMain {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2030, 1, 1, 13, 30, 59);
        System.out.println("YEAR = " + dt.get(ChronoField.YEAR));
        System.out.println("MONTH_OF_YEAR = " +
            dt.get(ChronoField.MONTH_OF_YEAR));
        System.out.println("DAY_OF_MONTH = " +
            dt.get(ChronoField.DAY_OF_MONTH));
        System.out.println("HOUR_OF_DAY = " +
            dt.get(ChronoField.HOUR_OF_DAY));
        System.out.println("MINUTE_OF_HOUR = " +
            dt.get(ChronoField.MINUTE_OF_HOUR));
        System.out.println("SECOND_OF_MINUTE = " +
            dt.get(ChronoField.SECOND_OF_MINUTE));
        System.out.println("편의 메서드 사용");
        System.out.println("YEAR = " + dt.getYear());
        System.out.println("MONTH_OF_YEAR = " + dt.getMonthValue());
        System.out.println("DAY_OF_MONTH = " + dt.getDayOfMonth());
        System.out.println("HOUR_OF_DAY = " + dt.getHour());
        System.out.println("MINUTE_OF_HOUR = " + dt.getMinute());
        System.out.println("SECOND_OF_MINUTE = " + dt.getSecond());
        System.out.println("편의 메서드에 없음");
        System.out.println("MINUTE_OF_DAY = " +
            dt.get(ChronoField.MINUTE_OF_DAY));
        System.out.println("SECOND_OF_DAY = " +
            dt.get(ChronoField.SECOND_OF_DAY));
    }
}

--실행 결과--
YEAR = 2030
MONTH_OF_YEAR = 1
DAY_OF_MONTH = 1
HOUR_OF_DAY = 13
MINUTE_OF_HOUR = 30
SECOND_OF_MINUTE = 59
편의 메서드 사용
YEAR = 2030
MONTH_OF_YEAR = 1
DAY_OF_MONTH = 1
HOUR_OF_DAY = 13
MINUTE_OF_HOUR = 30
SECOND_OF_MINUTE = 59
편의 메서드에 없음
MINUTE_OF_DAY = 810
SECOND_OF_DAY = 48659
  • TemporalAccessor.get(TemporalField field)
    • LocalDateTime 을 포함한 특정 시점의 시간을 제공하는 클래스는 모두 TemporalAccessor 인터페이스를 구현한다.
    • TemporalAccessor 는 특정 시점의 시간을 조회하는 기능을 제공한다.
  • 편의 메서드 사용
    • get(TemporalField field) 을 사용하면 코드가 길어지고 번거롭기 때문에 자주 사용하는 조회 필드는 간단한 편의 메서드를 제공한다.

날짜와 시간 조작하기

날짜와 시간을 조작하려면 어떤 시간 단위(Unit) 을 변경할 지 선택해야 한다.
이때 날짜와 시간의 단위를 뜻하는 ChronoUnit 이 사용한다.

public class ChangeTimePlusMain {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2018, 1, 1, 13, 30, 59);
        System.out.println("dt = " + dt);
        LocalDateTime plusDt1 = dt.plus(10, ChronoUnit.YEARS);
        System.out.println("plusDt1 = " + plusDt1);
        LocalDateTime plusDt2 = dt.plusYears(10); System.out.println("plusDt2 = " + plusDt2);
        Period period = Period.ofYears(10);
        LocalDateTime plusDt3 = dt.plus(period);
        System.out.println("plusDt3 = " + plusDt3);
    }
}
--실행 결과--
dt = 2018-01-01T13:30:59
plusDt1 = 2028-01-01T13:30:59
plusDt2 = 2028-01-01T13:30:59
plusDt3 = 2028-01-01T13:30:59
  • Temporal plus(long amountToAdd,TemporalUnit unit)
    • LocalDateTime 을 포함한 특정 시점의 시간을 제공하는 클래스는 모두 Temporal 인터페이스를 구현한다.
    • Temporal 은 특정 시점의 시간을 조작하는 기능을 제공한다.
    • 불변이므로 반환 값을 받아야 한다.
  • Period 를 사용한 조작
    • Period 나 Duration 은 기간을 뜻한다.
    • 특정 시점의 시간에 기간을 더할 수 있다.

특정 시간 단위나 필드를 사용할 수 있는지 확인할 수 있는 메서드

  • TemporalAccessor
  • boolean isSupported(TemporalField field);
  • Temporal
  • boolean isSupported(TemporalUnit unit);

날짜와 시간 조회하고 조작하기 - with()

public class ChangeTimeWithMain {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2018, 1, 1, 13, 30, 59);
        System.out.println("dt = " + dt);
        LocalDateTime changedDt1 = dt.with(ChronoField.YEAR, 2020);
        System.out.println("changedDt1 = " + changedDt1);
        LocalDateTime changedDt2 = dt.withYear(2020);
        System.out.println("changedDt2 = " + changedDt2);
        //TemporalAdjuster 사용
        //다음주 금요일
        LocalDateTime with1 =
            dt.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
        System.out.println("기준 날짜: " + dt);
        System.out.println("다음 금요일: " + with1);
        //이번 달의 마지막 일요일
        LocalDateTime with2 =
            dt.with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));
        System.out.println("같은 달의 마지막 일요일 = " + with2);
    }
}

--실행 결과--
dt = 2018-01-01T13:30:59
changedDt1 = 2020-01-01T13:30:59
changedDt2 = 2020-01-01T13:30:59
기준 날짜: 2018-01-01T13:30:59
다음 금요일: 2018-01-05T13:30:59
같은 달의 마지막 일요일 = 2018-01-28T13:30:59
  • Temporal with(TemporalField field,long new Value)
    • 날짜와 시간의 특정 필드의 값만 변경할 수 있다.
    • 불변이므로 반환 값을 받아야 한다.

TemporalAdjuster

  • with() 는 단순한 날짜만 변경할 수 있다.
  • 이번달의 마지막 일요일 같은 복잡한 날짜를 계산하고 싶다면 사용
public interface TemporalAdjuster {
 Temporal adjustInto(Temporal temporal);
}
  • TemporalAdjusters.next(DayOfWeek.FRIDAY) : 다음 금요일을 구한다.
  • TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY) : 이번 달의 마지막 일요일을 구한다

DayOfWeek

월,화,수,목,금,토,일 을 나타내는 열거형


날짜와 시간 문자열 파싱과 포맷팅

  • 포맷팅 : 날짜와 시간 데이터를 원하는 포맷의 문자열로 변경
  • 파싱 : 문자열을 날짜와 시간 데이터로 변경

포맷팅

public class FormattingMain1 {
    public static void main(String[] args) {
        // 포맷팅: 날짜를 문자로
        LocalDate date = LocalDate.of(2024, 12, 31);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
        String formattedDate = date.format(formatter);
        System.out.println("날짜와 시간 포맷팅: " + formattedDate);
        // 파싱: 문자를 날짜로
        String input = "2030년 01월 01일";
        LocalDate parsedDate = LocalDate.parse(input, formatter);
        System.out.println("문자열 파싱 날짜와 시간: " + parsedDate);
    }
}
--실행 결과--
현재 날짜와 시간 포맷팅: 2024년 12월 31일
문자열 파싱 날짜와 시간: 2030-01-01
  • LocalDate 와 같은 날짜 객체를 원하는 형태의 문자로 변경하려면 DateTimeFormatter 을 사용하면 된다.
  • ofPattern 으로 원하는 포맷을 지정하면 된다.

파싱

public class FormattingMain2 {
    public static void main(String[] args) {
        // 포맷팅: 날짜와 시간을 문자로
        LocalDateTime now = LocalDateTime.of(2024, 12, 31, 13, 30, 59);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String formattedDateTime = now.format(formatter);
        System.out.println("날짜와 시간 포맷팅: " + formattedDateTime);
        // 파싱: 문자를 날짜와 시간으로
        String dateTimeString = "2030-01-01 11:30:00";
        LocalDateTime parsedDateTime = LocalDateTime.parse(dateTimeString,
            formatter);
        System.out.println("문자열 파싱 날짜와 시간: " + parsedDateTime);
    }
}

--실행 결과--
현재 날짜와 시간 포맷팅: 2024-12-31 13:30:59
문자열 파싱 날짜와 시간: 2030-01-01T11:30