날짜와 시간의 핵심 인터페이스
날짜와 시간은 특정 시점의 시간(시각)과 간격(기간)으로 나눌 수 있다고 했다.
- 특정 시점의 시간(시각) : 이 프로젝트 기한은 8월 13일 까지야, 다음 회의는 11시 30분이야, 내 생일은 11월 28일이야
- 시간의 간격 (기간, 시간의 양) : 앞으로 4년은 더해야 해, 이 프로젝트는 3개월 남았어, 라면은 3분 동안 끓여야 해
- 특정 시점의 시간은 Temporal(TemporalAccessor 포함) 인터페이스를 구현한다.
- 시간의 간격은 TemporalAmount 인터페이스를 구현한다.
TemporalAccessor 인터페이스
- 날짜와 시간을 읽기 위한 기본 인터페이스
- 특정 시점의 날짜와 시간 정보를 읽을 수 있는 최소한의 기능이 있다.
Temporal 인터페이스
- TemporalAccessor의 하위 인터페이스로, 날짜와 시간을 조작(추가, 빼기 등) 하기 위한 기능을 제공한다.
TemporalAmount 인터페이스
- 시간의 간격(양, 기간)을 나타내며, 날짜와 시간 객체에 적용해 그 객체를 조정할 수 있다. 특정 날짜에 일정 기간을 조정하는데 사용된다.
시간 단위와 시간 필드
시간의 단위를 뜻하는 TemporalUnit과 시간의 각 필드를 뜻하는 TemporalField가 있다.
시간의 단위 - ChronoUnit
시간의 단위를 나타내는 TemporalUnit의 구현체는 ChronoUnit이다.
ChronoUnit은 다양한 시간의 단위를 제공한다.
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 between = ChronoUnit.SECONDS.between(lt1, lt2);
System.out.println("between = " + between);
long between1 = ChronoUnit.MINUTES.between(lt1, lt2);
System.out.println("between1 = " + between1);
}
}
ChronoUnit을 사용하면 두 날짜 또는 시간 사이의 차이를 해당 단위로 쉽게 계산이 가능하다.
위 코드에선 getDuration()과 between()을 사용해 단위 계산을 쉽게 할 수 있었다.
시간 필드 - ChronoField
ChronoField는 날짜 및 시간을 나타내는데 사용되는 enum type이다. 날짜와 시간의 특정 부분을 나타낸다.
해당 unit의 range를 나타낸다고 보면 된다.
Field라는 뜻이 날짜와 시간 중에 있는 특정 필드를 뜻하는데, 2024년 8월 16일이라고 하면 필드는 다음과 같다.
YEAR: 2024
MONTH_OF_YEAR: 8
DAY_OF_MONTH: 16
이렇게 표현해야하는 이유는 DAY는 한달중의 15일 일지, 단순하게 센 50일 일지, 1000일 일지 명확하지 않기 때문이다.
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)는 단독으로 사용하기 보다 주로 날짜와 시간을 조회하거나
조작할 때 사용한다.
날짜와 시간 조회 및 조작하기
날짜와 시간을 조회하려면 ChronoField가 사용된다. (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("OUR_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.getMonth());
System.out.println("DAY_OF_MONTH = " + dt.getDayOfMonth());
System.out.println("OUR_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));
}
}
get(TemporalField field)를 사용해 해당 필드를 조회할 수 있다. 그 중에 자주 사용되는 메서드는 일일이 ChronoField를 참조하지 않고
getYear, getMonth등 편리하게 사용할 수 있는 메서드가 제공된다.
LocalDateTime을 포함한 특정 시점의 시간을 제공하는 클래스는 모두 TemporalAccessor 인터페이스를 구현한다.
(위에서 TemporalAccesor는 읽기에 사용되는 인터페이스라고 설명했다.)
날짜와 시간 조작
날짜와 시간을 조작하려면 어떤 시간 단위(Unit)를 변경할 지 선택해야 한다. 이때 ChronoUnit이 사용된다.
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);
System.out.println("period = " + period);
LocalDateTime plusDt3 = dt.plus(period);
System.out.println("plusDt3 = " + plusDt3);
}
}
Temperal plus(long amountToAdd, TemperalUnit unit)
- LocalDateTime을 포함한 특정 시점의 시간을 제공하는 클래스는 모두 Temporal 인터페이스를 구현한다. (위 그림 참조)
- Temporal은 특정 시점의 시간을 조작하는 기능을 제공
- plus()를 호출할 때 더할 숫자와 단위를 전달하면 된다. 이 때 ChronoUnit을 인수로 전달하면 된다.
조회와 마찬가지로 편의 메서드가 제공된다. (plusYears 등)
Period, Duration을 사용해 특정 시점의 시간에 기간을 더할 수 있다.
시간을 조회하고 조작하는 부분을 보면 TemporalAccessor.get(), Temporal.plus()와 같은 인터페이스를 통해 특정
구현 클래스와 무관하게 일관성 있는 조회, 조작을 할 수 있었다. 덕분에 LocalDateTime, LocalDate, LocalTime, 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();
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);
}
}
}
LocalDate는 날짜 정보만 가지고 있기 때문에 분에 대한 정보는 없다. 따라서 LocalDate에 분에 대한 정보를 조회하려고 하면
예외가 발생한다. 이런 문제를 방지 하기 위해 TemporalAccessor와 Temporal 인터페이스는 현재 타입에서 특정 유닛이나 필드를
사용할 수 있는지 확인할 수 있는 메서드를 제공한다. (isSupported)
with()를 사용한 조작
이전에 불변인 값을 변경할 때 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 changeDt1 = dt.with(ChronoField.YEAR, 2020);
System.out.println("changeDt1 = " + changeDt1);
LocalDateTime changeDt2 = dt.withYear(2020);
System.out.println("changeDt2 = " + changeDt2);
//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 newValue)를 사용하면 특정 필드의 값만 변경할 수 있다. (불변이므로 반환값 필요)
with() 또한 편의 메서드가 제공된다.
with()는 아주 단순한 날짜 변경만 할 수 있다. 조금 더 복잡한 값이 필요할 땐 TemporalAdjuster를 사용해보자.
(다음 오는 금요일, 이번달의 마지막 일요일 구하기 등)
TemporalAdjuster가 제공하는 기능은 다양하기 때문에 필요하면 검색해서 사용하자
날짜와 시간 파싱, 포맷팅
포맷팅: 날짜와 시간 데이터를 원하는 포맷의 문자열로 변경함 (Date -> String)
파싱: 문자열을 날짜와 시간 데이터로 변경함 (String -> Date)
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);
//파싱
String dateTimeString = "2030-01-01 11:30:00";
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime parsedDateTime = LocalDateTime.parse(dateTimeString, formatter2);
System.out.println("문자열 파싱 날짜와 시간: " + parsedDateTime);
}
}
LocalDateTime같은 날짜와 시간 객체를 원하는 형태의 문자로 변경하려면 DateTimeFormatter를 사용하면 된다.
여기서 ofPattern으로 원하는 포맷을 지정하면 된다.
DateTimeFormatter 패턴 사용법은 공식 사이트가 있으니 참고해서 사용하도록 하자
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#patterns
날짜와 시간 객체에.parse()를 사용하면 파싱을 할 수 있다. 이 때도 DateTimeFormatter를 사용한다.
포맷팅과 파싱은 자주 사용될 수 있으니 잘 숙지하는게 좋을 것 같다.
'Java' 카테고리의 다른 글
중첩 클래스, 내부 클래스 (Local, Anonymous) (0) | 2024.11.19 |
---|---|
중첩 클래스, 내부 클래스 (Static, Inner) (0) | 2024.11.15 |
날짜와 시간 라이브러리(1) (1) | 2024.11.12 |
열거형 - ENUM (2) | 2024.11.05 |
래퍼 클래스, Class 클래스 (3) | 2024.10.17 |