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

임선구·2025년 4월 13일

몸 비틀며 Java

목록 보기
51/58

오늘의 잔디


오늘의 공부 기타 연습하느라 바쁘다


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

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

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

  • 특정 시점의 시간: Temporal ( TemporalAccessor 포함) 인터페이스를 구현한다.
    • 구현으로 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 은 다양한 시간 단위를 제공한다.
  • 여기서 Unit 이라는 뜻을 번역하면 단위이다. 따라서 시간의 단위 하나하나를 나타낸다.

시간 단위

날짜 단위

기타 단위

ChronoUnit의 주요 메서드

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

실행 결과

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

ChronoUnit 을 사용하면 두 날짜 또는 시간 사이의 차이를 해당 단위로 쉽게 계산할 수 있다.
예제 코드에서는 두 LocalTime 객체 사이의 차이를 초, 분 단위로 구한다.

시간 필드 - ChronoField

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

  • TemporalField 인터페이스는 날짜와 시간을 나타내는데 사용된다. 주로 사용되는 구현체는
    java.time.temporal.ChronoField 열거형으로 구현되어 있다.
  • ChronoField 는 다양한 필드를 통해 날짜와 시간의 특정 부분을 나타낸다. 여기에는 연도, 월, 일, 시간, 분 등이 포함된다.
  • 여기서 필드(Field)라는 뜻이 날짜와 시간 중에 있는 특정 필드들을 뜻한다. 각각의 필드 항목은 다음을 참고하자.
    • 예를 들어 2024년 8월 16일이라고 하면 각각의 필드는 다음과 같다.
      • 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());
 }
}

실행 결과

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) 는 단독으로 사용하기 보다는 주로 날짜와
시간을 조회하거나 조작할 때 사용한다. 다음 시간을 통해서 날짜와 시간을 조회하고 조작하는 방법을 알아보자.

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

날짜와 시간 조회하기

날짜와 시간을 조회하려면 날짜와 시간 항목중에 어떤 필드를 조회할 지 선택해야 한다. 이때 날짜와 시간의 필드를 뜻하는 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));
 }
}

실행 결과

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) 을 호출할 때 어떤 날짜와 시간 필드를 조회할 지 TemporalField 의 구현인 ChronoField 를 인수로 전달하면 된다.

편의 메서드 사용

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

편의 메서드에 없음

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

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

package time;
import java.time.*;
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);
 }
}
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 은 특정 시점의 시간을 조작하는 기능을 제공한다.
  • plus(long amountToAdd, TemporalUnit unit) 를 호출할 때 더하기 할 숫자와 시간의 단위(Unit)를
    전달하면 된다. 이때 TemporalUnit 의 구현인 ChronoUnit 을 인수로 전달하면 된다.
  • 불변이므로 반환 값을 받아야 한다.
  • 참고로 minus() 도 존재한다.

편의 메서드 사용

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

Period를 사용한 조작
PeriodDuration 은 기간(시간의 간격)을 뜻한다. 특정 시점의 시간에 기간을 더할 수 있다.

정리
시간을 조회하고 조작하는 부분을 보면 TemporalAccessor.get() , Temporal.plus() 와 같은 인터페이스를 통해 특정 구현 클래스와 무관하게 아주 일관성 있는 시간 조회, 조작 기능을 제공하는 것을 확인할 수 있다.
덕분에 LocalDateTime , LocalDate , LocalTime , ZonedDateTime , Instant 와 같은 수 많은 구현에 관계없이 일관성 있는 방법으로 시간을 조회하고 조작할 수 있다.

하지만 모든 시간 필드를 다 조회할 수 있는 것은 아니다.

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

실행 결과

Exception in thread "main" 
java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: 
SecondOfMinute

LocalDate 는 날짜 정보만 가지고 있고, 분에 대한 정보는 없다. 따라서 분에 대한 정보를 조회하려고 하면 예외가 발생한다.
이런 문제를 예방하기 위해 TemporalAccessorTemporal 인터페이스는 현재 타입에서 특정 시간 단위나 필드를 사용할 수 있는지 확인할 수 있는 메서드를 제공한다.

TemporalAccessor

boolean isSupported(TemporalField field);

Temporal

boolean isSupported(TemporalUnit unit);
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);
 }
 }
}

실행 결과

supported = false

LocalDate 는 분의 초 필드를 지원하지 않으므로 ChronoField.SECOND_OF_MINUTE 를 조회하면 false 를 반환한다.

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

날짜와 시간을 조작하는 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);
 }
}

실행 결과

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

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

편의 메서드

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

TemporalAdjuster 사용

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

TemporalAdjuster 인터페이스

public interface TemporalAdjuster {
 Temporal adjustInto(Temporal temporal);
}

원래대로 하면 이 인터페이스를 직접 구현해야겠지만, 자바는 이미 필요한 구현체들을 TemporalAdjusters 에 다
만들어두었다. 우리는 단순히 구현체들을 모아둔 TemporalAdjusters 를 사용하면 된다.

  • TemporalAdjusters.next(DayOfWeek.FRIDAY) : 다음 금요일을 구한다.
  • TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY) : 이번 달의 마지막 일요일을 구한다.

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

TemporalAdjusters 클래스가 제공하는 주요 기능

profile
끝까지 가면 내가 다 이겨

0개의 댓글