본문 바로가기

항해 99/Java

Java - 날짜와 시간

김영한 중급 자바 1편 강의 참조

 

날짜와 시간

날짜와 시간 라이브러리의 필요성

1. 날짜와 시간 차이 계산

  • 특정 날짜에서 다른 날짜까지의 정확한 일수 계산은 윤년, 각 달의 일수 등을 모두 고려해야 하며, 간단한 연산으로는 정확한 결과를 얻기 힘듬

2. 윤년 계산

  • 윤년 계산은 간단해 보이지만 실제로 매우 복잡하다, 보통 4년마다 한 번씩 발생하지만, 100년 단위일 때는 윤년이 아니며, 400년 단위일 때는 다시 윤년이다.

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

  • 보통 3월에서 10월은 태양이 일찍 뜨고, 나머지는 태양이 상대적으로 늦게 뜨기 때문에 시간도 여기에 맞춰 앞당기거나 늦추는 제도를 일광 절약 시간제 또는 썸머타임이라 한다.
  • 일광 절약 시간은 국가나 지역에 따라 적용 여부와 시작 및 종료 날짜가 다르기 때문에 정확하게 계산하는 것은 복잡하다.

4. 타임존 계산

  • 세계는 다양한 타임존으로 나뉘어 있으며, 각 타임존은 UTC(협정 세계시)로부터의 시간 차이로 정의된다. 타임존 간의 날짜와 시간 변환을 정확히 계산하는 것은 복잡하다.

 

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

JDK 1.0(java.util.Date)

  • 문제점
    • 타임존 처리 부족: 초기 Date 클래스는 타임존을 제대로 처리하지 못함
    • 불편한 날짜 시간 연산: 날짜 각 연산이나 시간의 증감 등을 처리하기 어려움
    • 불변 객체 부재: 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의 많은 기능을 표준 자바 플랫폼으로 가져옴

 

자바 날짜와 시간 라이브러리 종류

  • *: 초는 나노초 단위의 정밀도로 캡처된다.(밀리초, 나노초 가능)
  • **: 이 클래스는 이 정도를 저장하지는 않지만 이러한 단위로 시간을 제공하는 메서드가 있다.
  • ***: ZonedDateTime에 Period를 추가하면 서머타임 또는 기타 현지 시간 차이를 준수한다.

 

LocalDate, LocalTime, LocalDateTime

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

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

 

ZonedDateTime, OffsetDateTime

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

 

Year, Month, YearMonth, MonthDay

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

 

Instant

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

 

Period, Duration

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

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

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

  • Period
    • 두 날짜 사이의 간격을 년, 월, 일 단위로 나타낸다.
  • Duration
    • 두 시간 사이의 간격을 시, 분, 초(나노초) 단위로 나타낸다.

 

기본 날짜와 시간 - LocalDateTime

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

 

LocalDate

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);

        //계산(불변)
        ofDate = ofDate.plusDays(10);
        System.out.println("지정 날짜+10d = " + ofDate);
    }
}
  • 생성
    • now(): 현재 시간을 기준으로 생성한다.
    • of(...): 특정 날짜를 기준으로 생성한다. 년, 월, 일을 입력할 수 있다.
  • 계산
    • plusDays(): 특정 일을 더한다. 다양한 plusXxx() 메서드가 존재한다.

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

 

LocalTime

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);
    }
}
  • 생성
    • now(): 현재 시간을 기준으로 생성한다.
    • of(...): 특정 시간을 기준으로 생성한다. 시, 분, 초, 나노초를 입력할 수 있다.
  • 계산
    • plusSeconds(): 특정 초를 더한다. 다양한 plusXxx() 메서드가 존재한다.

 

LocalDateTime

LocalDateTime은 LocalDate와 LocalTime을 내부에 가지고 날짜와 시간을 모두 표현한다.

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

 

타임존 - ZonedDateTime

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

 

ZoneId

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

package time;

import java.time.ZoneId;

public class ZoneIdMain {

    public static void main(String[] args) {
        for (String availableZoneId : ZoneId.getAvailableZoneIds()) {
            System.out.println("availableZoneId: " + availableZoneId);
            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);
    }
}
  • 생성
    • ZoneId.systmeDefault(): 시스템이 사용하는 기본 ZoneId를 반환
    • Zoneid.of(): 타임존을 직접 제공해서 ZoneId를 반환
  • ZoneId는 내부에 일광 절약 시간 관련 정보, UTC와의 오프셋 정보를 포함하고 있다.

 

ZonedDateTime

LocalDateTime에 시간대 정보인 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);
    }
}
  • ZonedDateTime: 시간대를 고려한 날짜와 시간을 표현할 때 사용한다. 여기에는 시간대를 표현하는 타임존이 포함된다.
  • 생성
    • now(): 현재 날짜와 시간을 기준으로 생성, 이때 ZoneId는 현재 시스템을 따른다.
    • of(...): 특정 날짜와 시간을 기준으로 생성한다. ZoneId를 추가해야 한다.
      • LocalDateTime에 ZoneId를 추가해서 생성할 수 있다.
  • 타임존 변경
    • withZoneSameInstant(ZoneId): 타임존을 변경한다(타임존에 맞춰 시간도 함께 변경됨).

 

OffsetDateTime

LocalDateTime에 UTC 오프셋 정보인 ZoneOffset이 합쳐진 것

package time;

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);
    }
}
  • OffsetDateTime: 시간대를 고려한 날짜와 시간을 표현할 때 사용한다. 여기에는 타임존은 없고, UTC로 부터의 시간대 차이인 고정된 오프셋만 포함된다.
  • ZonedDateTime vs OffsetDateTime
    • ZonedDateTime은 구체적인 지역 시간대를 다룰 때 사용하며, 일광 절약 시간을 자동으로 처리할 수 있다. 사
      용자 지정 시간대에 따른 시간 계산이 필요할 때 적합하다.
    • OffsetDateTime은 UTC와의 시간 차이만을 나타낼 때 사용하며, 지역 시간대의 복잡성을 고려하지 않는다. 시간대 변환 없이 로그를 기록하고, 데이터를 저장하고 처리할 때 적합하다.

 

기계 중심의 시간 - Instant

Instant 는 UTC(협정 세계시)를 기준으로 하는, 시간의 한 지점을 나타낸다. `Instant` 는 날짜와 시간을 나노초 정밀도로 표현하며, 1970년 1월 1일 0시 0분 0초(UTC 기준)를 기준으로 경과한 시간으로 계산된다.

쉽게 이야기해서 Instant 내부에는 초 데이터만 들어있다. (나노초 포함) 따라서 날짜와 시간을 계산에 사용할 때는 적합하지 않다.

package time;

import java.time.Instant;
import java.time.ZonedDateTime;

public class InstanceMain {

    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);
    }
}

 

Instant 특징

  • 장점
    • 시간대 독립성: UTC를 기준으로 하므로, 시간대에 영향을 받지 않는다. 전세계 어디서나 동일한 시점을 가리키는데 유용하다.
    • 고정된 기준점: 모든 Instant는 1970년 1월 1일 UTC를 기준으로 하기 때문에, 시간 계산 및 비교가 명확하고 일관된다.
  • 단점
    • 사용자 친화적이지 않음: 기계적인 시간 처리에는 적합하지만, 사람이 읽고 이해하기에는 직관적이지 않다. 날짜와 시간을 계산하고 사용하는데 필요한 기능이 부족하다.
    • 시간대 정보 부재: 시간대 정보가 포함되어 있지 않아, 특정 지역의 날짜와 시간으로 변환하려면 추가적인 작업이 필요하다.
  • 생성
    • now(): UTC를 기준 현재 시간의 Instant를 생성한다.
    • from(): 다른 타입의 날짜와 시간을 기준으로 Instant를 생성한다. 
    • ofEpochSecond(): 에포크 시간을 기준으로 Instant를 생성한다. 0초를 선택하면 에포크 시간인 1970년 1월 1일 0시 0분 0초로 생성된다.
  • 계산
    • plusSeconds(): 초를 더한다. 초, 밀리초, 나노초 정도만 더하는 간단한 메서드가 제공된다.
  • 조회
    • getEpochSecond(): 에포크 시간인 UTC 1970년 1월 1일 0시 0분 0초를 기준으로 흐른 초를 반환한다.

 

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

 

Period

package time;

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 = " + currentDate);
        System.out.println("plusDate = " + 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 = " + between);
        System.out.println("기간: " + between.getMonths() + "개월 " + between.getDays() + "일");
    }
}
  • 생성
    • of(): 특정 기간을 지정해서 Period를 생성한다.
      • of(년, 월, 일)
      • ofDays()
      • ofMonths()
      • ofYears()
  • 계산에 사용
    • 특정 날짜에 원하는 기간을 더할 수 있다.
  • 기간 차이
    • 특정 날짜의 차이를 구하면 기간이 된다.
    • Period.between(startDate, endDate)와 같이 특정 날짜의 차이를 구하면 Period가 반환된다.

 

Duration

package time;

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 = " + 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() + "분");

    }
}
  • 생성
    •  of(): 특정 시간을 지정해서 Duration를 생성한다.
      • of(지정)
      • ofSeconds()
      • ofMinutes()
      • ofHours()
  • 계산에 사용
    • 특정 시간에 원하는 시간(시간의 간격)을 더할 수 있다.
  • 시간 차이
    • 시간의 차이를 구할 수 있다.
    • Duration.between(start, end)와 같이 특정 시간의 차이를 구하면 Duration이 반환된다.

 

 

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

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

TemporalAccessor 인터페이스

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

Temporal 인터페이스

  • TemporalAccessor의 하위 인터페이스로, 날짜와 시간을 조작(추가, 빼기 등)하기 위한 기능을 제공한다. 이를 통해 날짜와 시간을 변경하거나 조정할 수 있다.

TemporalAmount 인터페이스

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

 

시간 단위와 시간 필드

 

시간의 단위 - TemporalUnit, ChronoUnit

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

 

예제

package time;

import java.time.LocalTime;
import java.time.temporal.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);
    }
}
  • ChronoUnit을 사용하면 두 날짜 또는 시간 사이의 차이를 해당 단위로 쉽게 계산할 수 있다.

 

 

시간 필드 - ChronoField

ChronoField는 날짜 및 시간을 나타내는 데 사용되는 열거형이다. 이 열거형은 다양한 필드를 통해 날짜와 시간의 특정 부분을 나타낸다. 여기에는 연도, 월, 일, 시간, 분 등이 포함된다.

  • TemporalField 인터페이스는 날짜와 시간을 나타내는데 사용된다. 주로 사용되는 구현체는 java.time.temporal.ChronoField 열거형으로 구현됨
  • ChronoField는 다양한 필드를 통해 날짜와 시간의 특정 부분을 나타낸다. 여기에는 연도, 월, 일, 시간, 분 등이 포함된다.
  • 여기서 필드(Field)라는 뜻이 날짜와 시간 중에 있는 특정 필드들을 뜻한다.
    • YEAR: 2024
    • MONTH_OF_YEAR: 8
    • DAY_OF_MONTH: 16
  • 단순히 시간의 단위를 하나하나를 뜻하는 ChronoUnit과는 다른 것을 알 수 있다. ChronoField를 사용해야 날짜와 시간의 각 필드 중에 원하는 데이터를 조회할 수 있다.

예제

package time;

import java.time.temporal.ChronoField;

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());
    }
}
  • TemporalUnit(ChronoUnit), TemporalField(ChronoField)는 단독으로 사용하기 보다 주로 날짜와 시간을 조회하거나 조작할 때 사용한다.

 

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

package time;

import java.time.LocalDateTime;
import java.time.temporal.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));
    }
}

 

TemporalAccessor.get(TemporalField field)

  • LocalDateTime을 포함한 특정 시점의 시간을 제공하는 클래스는 모두 TemporalAccessor 인터페이스를 구현함
  • TemporalAccessor는 특정 시점의 시간을 조회하는 기능을 제공함
  • get(TemporalField field)을 호출할 때 어떤 날짜와 시간 필드를 조회할 지 TemporalField의 구현인 ChronoField를 인수로 전달하면 된다.

편의 메서드 사용

  • get(TemporalField field)을 사용하면 코드가 길어지고 번거롭기 때문에 자주 사용하는 조회 필드는 간단한 편의 메서드를 제공한다.
  • dt.get(ChronoField.DAY_OF_MONTH) dt.getDayOfMonth()

편의 메서드에 없음

  • 자주 사용하지 않은 특별한 기능은 편의 메서드를 제공하지 않는다.
  • 편의 메서드를 사용하는 것이 가독성이 좋기 때문에 일반적으로는 편의 메서드를 사용하고, 편의 메서드가 없는 경우 get(TemporalField field)을 사용하면 된다.

 

package time;

import java.time.LocalDateTime;
import java.time.Period;
import java.time.temporal.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);
    }
}

 

Temporal plus(long amountToAdd, TemporalUnit unit)

  • LocalDateTime을 포함한 특정 시점의 시간을 제공하는 클래스는 모두 Temporal 인터페이스를 구현한다.
  • Temporal은 특정 시점의 시간을 조작하는 기능을 제공한다.
  • plus(long amountToAdd, TemporalUnit unit)를 호출할 때 더하기 할 숫자와 시간의 단위(Unit)를 전달하면 된다. 이때 TemporalUnit의 구현인 ChronoUnit을 인수로 전달하면 된다.
  • 불변이므로 반환 값을 받아야 한다.
  • minus()도 존재한다.

편의 메서드 사용

  • 자주 사용하는 메서드는 편의 메서드가 제공된다.
  • dt.plus(10, ChronoUnit.YEARS) dt.plusYears(10)

Period를 사용한 조작

Period나 Duration은 기간(시간의 간격)을 뜻한다. 특정 시점의 시간에 기간을 더할 수 있다.

 

정리

시간을 조회하고 조작하는 부분을 보면 TemporalAccessor.get(), Temporal.plus()와 같은 인터페이스를 통해 특정 구현 클래스와 무관하게 아주 일관성 있는 시간 조회, 조작 기능을 제공한다(모든 시간 필드를 다 조회하고 조작할 수 있는 것은 아님).

 

예제

package time;

import java.time.LocalDate;
import java.time.temporal.ChronoField;

public class IsSupportedMain1 {

    public static void main(String[] args) {
        LocalDate now = LocalDate.now();
        int minute = now.get(ChronoField.SECOND_OF_MINUTE);
        System.out.println("minute = " + minute);
    }
}
  • LocalDate는 날짜 정보만 가지고 분에 대한 정보는 없다. 따라서 분에 대한 정보를 조회하려면 예외가 발생한다.
  • 이런 문제를 예방하기 위해 TemporalAccessor, Temporal 인터페이스는 현재 타입에서 특정 시간 단위나 필드를 사용할 수 있는 지 확인할 수 잇는 메서드를 제공한다.
package time;

import java.time.LocalDate;
import java.time.temporal.ChronoField;

public class IsSupportedMain2 {

    public static void main(String[] args) {
        LocalDate now = LocalDate.now();

        boolean supported = now.isSupported(ChronoField.SECOND_OF_MINUTE);
        System.out.println("supported = " + supported);
        if (supported) {
            int minute = now.get(ChronoField.SECOND_OF_MINUTE);
            System.out.println("minute = " + minute);
        }
    }
}

 

 

with() 메서드

package time;

import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAdjusters;

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);
    }
}

 

Temporal with(TemporalField field, long new Value)

  • Temporal.with()를 사용하면 날짜와 시간의 특정 필드의 값만 변경할 수 있다.
  • 불변이므로 반환 값을 받아야 한다.

편의 메서드

  • 자주 사용하는 메서드는 편의 메서드가 제공된다.
  • dt.with(ChronoField.YEAR, 2020) dt.withYear(2020)

TemporalAdjuster 사용

  • with()는 아주 단순한 날짜만 변경할 수 있다. 다음주 금요일, 이번 달의 마지막 일요일 같은 복잡한 날짜를 계산하고 싶다면 TemporalAdjuster를 사용하면 된다.

 

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

  • 포맷팅: 날짜와 시간 데이터를 원하는 포맷의 문자열로 변경하는 것, Date String
  • 파싱: 문자열을 날짜와 시간 데이터로 변경하는 것, String Date
package time;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class FormattingMain1 {

    public static void main(String[] args) {
        // 포맷팅: 날짜를 문자로
        LocalDate date = LocalDate.of(2024, 12, 31);
        System.out.println("date = " + date);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
        String formattedDate = date.format(formatter);
        System.out.println("날짜와 시간 포맷팅 = " + formattedDate);

        // 파싱: 문자를 날짜로
        String input = "2030년 01월 01일";
        LocalDate parseDate = LocalDate.parse(input, formatter);
        System.out.println("문자열 파싱 날짜와 시간: " + parseDate);
    }
}

 

DateTimeFormatter 패턴 공식 사이트

https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#patterns

 

문자열을 날짜와 시간으로 파싱

package time;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

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);
        System.out.println(now);

        // 파싱: 문자를 날짜와 시간으로
        String dateTimeString = "2030-01-01 11:30:00";
        LocalDateTime parsedDateTime = LocalDateTime.parse(dateTimeString, formatter);
        System.out.println("문자열 파싱 날짜와 시간: " + parsedDateTime);
    }
}

 

 

'항해 99 > Java' 카테고리의 다른 글

Java - Nested, Inner Class  (0) 2024.08.14
Java - 정렬 알고리즘  (0) 2024.08.08
Java - ENUM  (0) 2024.07.18
Java - Wrapper, Class, System, Math, Random  (0) 2024.06.13
String 클래스  (0) 2024.06.05