java.time
패키지는 JDK 8부터 도입된 새로운 날짜와 시간 라이브러리로, 기존의 java.util.Date
와 java.util.Calendar
의 문제점을 해결하기 위해 설계되었습니다. 이번 포스팅에서는 java.time
패키지에서 날짜와 시간을 조작하거나 계산할 때 사용하는 다양한 단위와 필드를 표현하는 클래스들을 먼저 정리해보고, java.time
패키지 클래스들의 공통적인 기능들을 구현하는 Temporal
계열의 인터페이스 및 주요 메서드들을 정리해볼 예정입니다.
주요 내용
ChronoUnit
과 ChronoField
는 날짜와 시간의 조작에 필수적인 단위와 필드를 정의합니다. Temporal
계열 인터페이스Temporal
계열 인터페이스는 java.time
패키지에서 날짜와 시간 객체들이 공통적으로 구현하는 기능을 정의합니다. Temporal, TemporalAccessor, TemporalAdjuster, TemporalAmount
등 다양한 인터페이스가 존재하며, 이들은 날짜와 시간의 조작, 계산, 비교 등을 위한 핵심 기능을 제공합니다. 이러한 인터페이스를 이해함으로써, java.time 패키지의 강력한 시간 처리 기능을 효과적으로 활용할 수 있습니다.java.time
패키지에는 날짜와 시간을 조작하거나 계산할 때 사용하는 다양한 단위와 필드를 표현하는 클래스가 있습니다. 그 중에서도 ChronoUnit
과 ChronoField
는 가장 핵심적인 역할을 합니다. ChronoUnit
클래스는 시간 단위를 정의하는 Enum으로, 연도, 월, 일, 시간, 분, 초와 같은 다양한 시간 단위를 표현합니다.java.time.temporal
패키지에 열거형으로 TemporalUnit
인터페이스를 구현하며, 날짜 및 시간의 계산, 차이 계산 등에 사용됩니다.ChronoUnit | 설명 |
---|---|
NANOS | 나노초 |
MICROS | 마이크로초 |
MILLIS | 밀리초 |
SECONDS | 초 |
MINUTES | 분 |
HOURS | 시간 |
DAYS | 일 단위 |
WEEKS | 주 단위 |
MONTHS | 월 |
YEARS | 연도 |
DECADES | 10년 |
CENTURIES | 100년 |
MILLENNIA | 1000년 |
ERAS | 시대(기원전, 서기) |
FOREVER | 무한대 |
Method | 반환타입 | 설명 |
---|---|---|
.getDuration() | Duration | 해당 단위의 길이를 Duration으로 반환합니다. |
.isDurationEstimated() | boolean | 해당 단위의 길이가 추정치인지 여부를 반환합니다. |
.isDateBased() | boolean | 해당 단위가 날짜 기반인지 여부를 반환합니다. |
.isTimeBased() | boolean | 해당 단위가 시간 기반인지 여부를 반환합니다. |
.isSupportedBy(Temporal) | boolean | 지정된 Temporal 객체가 해당 단위를 지원하는지 확인합니다. |
.addTo(Temporal, long) | Temporal | 해당 단위의 값을 더한 새로운 Temporal 객체를 반환합니다. |
.between(Temporal, Temporal) | long | 두 Temporal 객체 사이의 차이를 해당 단위로 계산하여 반환합니다. |
import java.time.temporal.ChronoUnit;
import java.time.LocalDate;
import java.time.temporal.Temporal;
LocalDate today = LocalDate.now();
LocalDate nextYear = today.plus(1, ChronoUnit.YEARS);
System.out.println("Next Year: " + nextYear); // 출력: Next Year: 2025-08-29
long daysBetween = ChronoUnit.DAYS.between(today, nextYear);
System.out.println("Days Between: " + daysBetween); // 출력: Days Between: 365 (윤년이 아닌 경우)
Temporal nextMonth = ChronoUnit.MONTHS.addTo(today, 1);
System.out.println("Next Month: " + nextMonth); // 출력: Next Month: 2024-09-29
ChronoField
클래스는 날짜와 시간의 특정 필드를 정의하는 Enum으로, 연도, 월, 일, 시, 분, 초 등과 같은 필드를 표현합니다.
java.time.temporal
패키지에 열거형으로 TemporalField
인터페이스를 구현하며, 특정 날짜 또는 시간의 필드를 읽거나 조작할 때 사용됩니다.getFrom()
메서드를 통해 값을 가져올 수 있으며, with()
메서드를 통해 값을 설정할 수 있습니다. (참고로 Java에서 with
는 새로운 불변 객체를 생성한다는 뜻이 있습니다.)주요 시간 및 날짜 필드
ChronoField | 설명 | 범위 |
---|---|---|
ERA | 연대 (BC 혹은 AD) | 0(BC), 1(AD) |
YEAR | 연도 | -999,999,999 ~ 999,999,999 |
MONTH_OF_YEAR | 연중 월 | 1(Jan) ~ 12(Dec) |
DAY_OF_YEAR | 연중 날짜 | 1 ~ 365(366) |
DAY_OF_MONTH | 월중 날짜 | 1 ~ 28/29/30/31 |
DAY_OF_WEEK | 요일 | 1(Mon) ~ 7(Sun) |
HOUR_OF_DAY | (24시간) 시간 | 0 ~ 23 |
CLOCK_HOUR_OF_DAY | (24시간) 시간 | 1 ~ 24 |
AMPM_OF_DAY | 오전/오후 | 0(AM), 1(PM) |
HOUR_OF_AMPM | (12시간) 시간 | 0 ~ 11 |
CLOCK_HOUR_OF_AMPM | (12시간) 시간 | 1 ~ 12 |
MINUTE_OF_HOUR | 분 | 0 ~ 59 |
SECOND_OF_MINUTE | 초 | 0 ~ 59 |
MILLI_OF_SECOND | 밀리초 | 0 ~ 999 |
MICRO_OF_SECOND | 마이크로초 | 0 ~ 999,999 |
NANO_OF_SECOND | 나노초 | 0 ~ 999,999,999 |
INSTANT_SECONDS | 타임스탬프 | -2^63 ~ 2^63-1 (long) |
OFFSET_SECONDS | UTC기준 오프셋 초 | -64800 ~ 64800 (±18시간) |
Method | 반환타입 | 설명 |
---|---|---|
.isSupportedBy(TemporalAccessor) | boolean | 지정된 TemporalAccessor가 해당 필드를 지원하는지 확인합니다. |
.getFrom(TemporalAccessor) | long | 해당 필드의 값을 가져옵니다. |
.adjustInto(Temporal, long) | Temporal | 해당 필드의 값을 설정한 새로운 Temporal 객체를 반환합니다. |
.range() | ValueRange | 해당 필드의 유효 범위를 반환합니다. |
import java.time.LocalDate;
import java.time.temporal.ChronoField;
import java.time.temporal.ValueRange;
LocalDate today = LocalDate.now();
int year = today.get(ChronoField.YEAR);
int month = today.get(ChronoField.MONTH_OF_YEAR);
int day = today.get(ChronoField.DAY_OF_MONTH);
System.out.println("Year: " + year); // 출력: Year: 2024
System.out.println("Month: " + month); // 출력: Month: 8
System.out.println("Day: " + day); // 출력: Day: 29
LocalDate adjustedDate = today.with(ChronoField.YEAR, 2025);
System.out.println("Adjusted Date: " + adjustedDate); // 출력: Adjusted Date: 2025-08-29
ValueRange range = ChronoField.DAY_OF_MONTH.range();
System.out.println("Day of Month Range: " + range); // 출력: Day of Month Range: 1 - 28/31
Temporal
계열 인터페이스java.time
패키지에서 날짜와 시간 작업을 효과적으로 수행하기 위해 설계된 여러 인터페이스들이 존재합니다. Temporal
계열의 인터페이스들입니다. java.time
패키지 내의 여러 클래스들이 이러한 인터페이스를 구현하고 있습니다.Temporal
계열 인터페이스의 상속 및 구현 관계TemporalAccessor
--(상속)--> Temporal
--(구현)--> LocalDateTime, ZonedDateTime, Instant, ...
TemporalAmount
--(구현)--> Period, Duration
TemporalUnit
--(구현)--> ChronoUnit
TemporalField
--(구현)--> ChronoField
TemporalAccessor
는 날짜와 시간 객체에서 특정 필드를 읽기 위한 인터페이스입니다.
TemporalAccessor
는 날짜와 시간 객체를 읽기만 할 수 있으며, 조작은 불가능합니다.주요 메서드
.isSupported(TemporalField field)
: 특정 필드가 이 객체에서 지원되는지 확인합니다..getLong(TemporalField field)
: 지정된 필드의 값을 long
타입으로 반환합니다..get(TemporalField field)
: 지정된 필드의 값을 int
타입으로 반환합니다..range(TemporalField field)
: 지정된 필드의 유효 범위를 반환합니다.import java.time.LocalDate;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.ChronoField;
TemporalAccessor date = LocalDate.of(2024, 8, 29);
int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
System.out.println("Year: " + year + ", Month: " + month); // 출력: Year: 2024, Month: 8
Temporal
은 TemporalAccessor
를 상속받아 날짜와 시간 객체를 읽을 뿐만 아니라 조작할 수 있는 기능을 추가로 제공하는 인터페이스입니다.
주요 메서드
.with(TemporalField field, long newValue)
: 지정된 필드의 값을 새로 설정한 객체를 반환합니다..plus(long amountToAdd, TemporalUnit unit)
: 지정된 단위의 값을 더한 객체를 반환합니다..minus(long amountToSubtract, TemporalUnit unit)
: 지정된 단위의 값을 뺀 객체를 반환합니다..until(Temporal endExclusive, TemporalUnit unit)
: 현재 객체와 종료 객체 사이의 차이를 지정된 단위로 계산하여 반환합니다.ChronoUnit.단위.between()
과는 약간 차이가 있는데, .until
은 더 일반적이고 다양한 단위로 차이를 계산할 수 있습니다.import java.time.LocalDate;
import java.time.temporal.Temporal;
import java.time.temporal.ChronoUnit;
Temporal date = LocalDate.of(2024, 8, 29);
Temporal nextWeek = date.plus(1, ChronoUnit.WEEKS);
System.out.println("Next Week: " + nextWeek); // 출력: Next Week: 2024-09-05
TemporalAmount
는 시간의 양을 나타내는 인터페이스로, Period
와 Duration
클래스가 이를 구현합니다.
주요 메서드
.get(TemporalUnit unit)
: 지정된 단위의 값을 반환합니다..getUnits()
: 이 시간 양에서 사용 가능한 단위의 목록을 반환합니다..addTo(Temporal temporal)
: 지정된 Temporal 객체에 이 시간 양을 더한 새로운 객체를 반환합니다..subtractFrom(Temporal temporal)
: 지정된 Temporal 객체에서 이 시간 양을 뺀 새로운 객체를 반환합니다.TemporalAmount
로 따로 쓰이지는 않기때문에 이를 구현한 Period
를 예시로 가져왔습니다.import java.time.LocalDate;
import java.time.Period;
LocalDate today = LocalDate.of(2024, 8, 29);
Period period = Period.ofDays(10);
LocalDate tenDaysLater = today.plus(period);
System.out.println("Ten Days Later: " + tenDaysLater); // 출력: Ten Days Later: 2024-09-08
TemporalAdjuster
는 java.time
패키지에서 날짜와 시간 객체를 조정하는 데 사용되는 전략 인터페이스입니다. TemporalAdjuster
는 Temporal
객체를 받아서 그 값을 조정한 후 새로운 Temporal
객체를 반환하는 함수형 인터페이스입니다..adjustInto(Temporal temporal)
: 주어진 Temporal 객체를 조정하고, 조정된 새로운 Temporal 객체를 반환합니다.TemporalAdjuster
를 구현하여 커스텀 날짜 및 시간 조정을 할 수 있습니다. 이를 통해 더욱 복잡한 조정 로직을 적용할 수 있습니다.import java.time.LocalDate;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalField;
import java.time.temporal.ChronoField;
import java.time.DayOfWeek;
import java.time.temporal.TemporalAdjusters;
public class NextMonthFirstMondayAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
// 다음 달의 첫 번째 날로 이동
Temporal firstDayOfNextMonth = temporal.with(TemporalAdjusters.firstDayOfNextMonth());
// 첫 번째 월요일로 이동
return firstDayOfNextMonth.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
}
}
// 사용 예시
LocalDate today = LocalDate.of(2024, 8, 29);
LocalDate nextMonthFirstMonday = today.with(new NextMonthFirstMondayAdjuster());
System.out.println("Next Month's First Monday: " + nextMonthFirstMonday); // 출력: Next Month's First Monday: 2024-09-02
java.time.temporal.TemporalAdjusters
클래스는 자주 사용되는 TemporalAdjuster
를 정적(static) 메서드로 제공합니다.Temporal
객체의 .with()
메서드에서 쓰입니다. TemporalAdjusters | 설명 | 예시 |
---|---|---|
.firstDayOfMonth() | 현재 날짜의 해당 월 첫 번째 일로 조정합니다. | .with(TemporalAdjusters.firstDayOfMonth()) |
.lastDayOfMonth() | 현재 날짜의 해당 월 마지막 날로 조정합니다. | .with(TemporalAdjusters.lastDayOfMonth()) |
.firstDayOfNextMonth() | 현재 날짜의 다음 달 첫 번째 날로 조정합니다. | .with(TemporalAdjusters.firstDayOfNextMonth()) |
.next(DayOfWeek) | 현재 날짜 이후의 가장 가까운 특정 요일로 조정합니다. | .with(TemporalAdjusters.next(DayOfWeek.FRIDAY)) |
.nextOrSame(DayOfWeek) | 현재 날짜 또는 이후의 가장 가까운 특정 요일로 조정합니다. | .with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY)) |
.previous(DayOfWeek dayOfWeek) | 현재 날짜 이전의 가장 가까운 특정 요일로 조정합니다. | .with(TemporalAdjusters.previous(DayOfWeek.MONDAY)) |
.previousOrSame(DayOfWeek dayOfWeek) | 현재 날짜 또는 이전의 가장 가까운 특정 요일로 조정합니다. | .with(TemporalAdjusters.previousOrSame(DayOfWeek.THURSDAY)) |
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.time.DayOfWeek;
LocalDate today = LocalDate.of(2024, 8, 29);
// 다음 월의 첫 번째 날로 조정
LocalDate firstDayOfNextMonth = today.with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println("First Day of Next Month: " + firstDayOfNextMonth); // 출력: First Day of Next Month: 2024-09-01
// 다음 금요일로 조정
LocalDate nextFriday = today.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println("Next Friday: " + nextFriday); // 출력: Next Friday: 2024-08-30
java.time
패키지에서 날짜와 시간을 조작하고 계산하는 데 필수적인 ChronoUnit
과 ChronoField
클래스, 그리고 다양한 시간 관련 작업을 수행할 수 있게 하는 Temporal
계열 인터페이스에 대해 알아보았습니다. TemporalAdjuster
와 같은 인터페이스는 날짜와 시간의 복잡한 조정 작업을 단순하고 명확하게 수행할 수 있도록 도와줍니다. TemporalAdjuster
를 구현하여 프로젝트의 특정 요구 사항에 맞는 조정을 수행할 수도 있습니다.TemporalAdjusters
를 사용하면 자주 발생하는 날짜 조정 작업을 쉽게 처리할 수 있습니다.