Skip to content

Commit

Permalink
post: [11주차_노경민] chap12. 새로운 날짜와 시간 API
Browse files Browse the repository at this point in the history
  • Loading branch information
gengminy committed Dec 31, 2023
1 parent 7e554e4 commit a55c1d3
Showing 1 changed file with 330 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
---
title: "🦊 chap12. 새로운 날짜와 시간 API"
author: gengminy
date: 2023-12-31 18:40:00 +09:00
categories: [모던 자바 인 액션, "chap12. 새로운 날짜와 시간 API"]
tags: [모던 자바 인 액션, JAVA, 11주차, 노경민]
render_with_liquid: false
math: true
---

# chap12. 새로운 날짜와 시간 API

# 12.1 LocalDate, LocalTime, Instant, Duration, Period 클래스

### 12.1.1 LocalDate와 LocalTime 사용

LocalDate 인스턴스는 시간을 제외한 날짜를 표현하는 불변 객체다.

```java
LocalDate date = LocalDate.of(2017, 9, 21);
int year = date.getYear();
Month month = date.getMonth();
int day = date.getDayOfMonth();
DayOfWeek dow = date.getDayOfWeek();
int len = date.lengthOfMonth();
boolean leap = date.isLeapYear();
```

팩토리 메서드 now 는 시스템 시계의 정보를 이용해서 현재 날짜 정보를 얻는다.

```java
LocalDate today = LocalDate.now();
```

get 메서드에 TemporalField 인터페이스를 전달해서 정보를 얻는 방법도 있다.

열거자 ChronoField 는 TemporalField 인터페이스를 정의한다.

```java
int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);
```

내장 메서드를 이용해 가독성을 높일 수도 있다.

```java
int year = date.getYear();
int month = date.getMonthValue();
int day = date.getDayOfMonth();
```

시간을 LocalTime 클래스로 표현할 수도 있다.

```java
LocalTime time = LocalTime.of(13, 45, 20);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
```

날짜와 시간 문자열로 parse 정적 메서드를 호출해 인스턴스를 만들 수도 있다.

```java
LocalDate date = LocalDate.parse("2017-09-21");
LocalTime time = LocalTime.parse("13:45:20");
```

parse 메서드에는 DateTimeFormatter 를 전달할 수도 있다.

### 12.1.2 날짜와 시간 조합

LocalDateTime 은 LocalDate 와 LocalTime 을 쌍으로 갖는 복합 클래스이다.

```java
LocalDateTime dt1 = LocalDateTime.of(2017, Month.SEPTEMBER, 21, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
```

LocalDateTime 에서 각 인스턴스를 추출할 수도 있다.

```java
LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
```

### 12.1.3 Instant 클래스 : 기계의 날짜와 시간

기계의 관점에서는 연속된 시간에서 특정 지점을 하나의 큰 수로 표현하는 것이 가장 자연스러운 시간 표현 방식이다.

`java.time.Instant` 클래스에서는 기계적인 관점에서 시간을 표현한다.

이는 유닉스 에포크 시간 `Unix epoch time, 1970-01-01 00:00:00 UTC`을 기준으로 특정 시점까지의 시간을 초로 표현한다.

```java
Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3, 0);
Instant.ofEpochSecond(2, 1_000_000_000);
Instant.ofEpochSecond(4, -1_000_000_000);
```

Instant 클래스도 사람이 확인할 수 있도록 시간을 표시해주는 정적 팩토리 메서드 now 를 제공한다.

Instant 는 초와 나노초 정보를 포함한다.

이를 읽기 위해 Duration 과 Period 클래스를 활용할 수 있다.

### 12.1.4 Duration 과 Period 정의

지금까지의 모든 클래스는 Temporal 인터페이스를 구현한다.

Duration 클래스의 정적 팩토리 메서드 between 으로 두 시간 객체 사이의 지속시간을 만들 수 있다.

```java
Duration d1 = Duration.between(time1, time2);
Duration d2 = Duration.between(dateTime1, dateTime2);
Duration d3 = Duration.between(instant1, instant2);
```

다만 LocalDateTime 은 사람이 사용하도록, Instant 는 기계가 사용하도록 만들어진 클래스로

두 인스턴스는 서로 혼합할 수 없다.

년, 월 일로 시간을 표현할 때는 Period 클래스를 사용한다.

```java
Period tenDays = Period.between(LocalDate.of(2017, 9, 11),
LocalDate.of(2017, 9, 21));
```

Duration 과 Period 클래스는 자신의 인스턴스를 만들 수 있도록 다양한 팩토리 메서드를 제공한다.

```java
Duration threeMinutes = Duration.ofMinutes(3);
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);

Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOnday = Period.of(2, 6, 1);
```

지금까지의 모든 클래스는 불변 클래스이다.

# 12.2 날짜 조정, 파싱, 포매팅

`withArrtibute` 메서드로 기존의 LocalDate 를 바꾼 버전을 직접 간단하게 만들 수 있다.

```java
LocalDate date1 = LocalDate.of(2017, 9, 21);
LocalDate date2 = date1.withYear(2011);
LocalDate date3 = date2.withDayOfMonth(25);
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 2);
```

Temproal 인터페이스는 LocalDate, LocalTime, LocalDateTime, Instant 처럼 특정 시간을 정의하고

get 과 with 메서드로 Temporal 객체의 필드값을 읽거나 고칠 수 있다.

만약 지정된 필드를 지원하지 않으면 `UnsupportedTemporalTypeException` 이 발생한다.

선언형으로 LocalDate 를 사용하는 방법도 있다.

```java
LocalDate date1 = LcoalDate.of(2017, 9, 21);
LocalDate date2 = date1.plusWeeks(1);
LocalDate date3 = date2.minusYears(6);
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
```

plus, minus 메서드도 Temporal 인터페이스에 정의되어 있다.

이를 활용해 특정 시간만큼 앞뒤로 이동시킬 수 있다.

숫자와 TemporalUnit 을 인수로 활용할 수 있다.

### 12.2.1 TemporalAdjusters 사용하기

`TemporalAdjuster` 는 with 메서드에 좀 더 다양한 동작을 수행할 수 있도록 하는 기능을 제공한다.

```java
import static java.time.tempoal.TemporalAdjusters.*;

LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.with(nextOfSame(DayOfWeek.SUNDAY));
LocalDate date3 = date2.with(lastDayOfMonth());
```

필요한 기능이 정의되어 있지 않을 때는 비교적 쉽게 커스텀 TemporalAdjuster 구현을 만들 수 있다.

```java
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
```

TemporalAdjuster 인터페이스 구현은 Temporal 객체를 어떻게 다른 Temporal 객체로 변환할 지 정의한다.

### 12.2.2 날짜와 시간 객체 출력과 파싱

포매팅과 파싱 전용 패키지인 `java.time.format` 이 추가되었다.

가장 중요한 클래스는 `DateTimeFormatter` 이다.

이를 이용해서 날짜나 시간을 특정 형식의 문자열로 만들 수 있다.

```java
LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); // 20140318
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2014-03-18
```

반대로 문자열을 파싱해서 날짜 객체를 다시 만들 수 있다.

```java
LocalDate date1 = LocalDate.parse("20140318", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);
```

기존 `java.util.DateFormat` 과 달리 모든 `DateTimeFormatter` 는 스레드 안전한 클래스이다.

또한 특정 패턴으로 포매터를 만들 수 있는 정적 팩토리 메서드도 제공한다.

```java
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);
```

`ofPattern` 메서드도 Locale 로 포매터를 만들 수 있도록 오버로드된 메서드를 제공한다.

```java
DateTimeFormatter italianFormatter =
DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
...
```

DateTimeFormatterBuilder 클래스로 복합적인 포매터를 정의해서 세부적으로 제어할 수도 있다.

```java
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
```

# 12.3 다양한 시간대와 캘린터 활용 방법

기존의 `java.util.TimeZone` 을 대체할 수 있는 `java.time.ZoneId` 클래스가 새롭게 등장했다.

이 클래스를 이용하면 써머타임(DST) 같은 복잡한 사항이 자동으로 처리된다.

ZoneId 클래스는 불변이다.

### 12.3.1 시간대 사용하기

`ZoneRules` 클래스에는 약 40개 정도의 시간대가 있고, getRules() 를 이용해 해당 시간대의 규정을 획득할 수 있다.

```java
ZoneId romeZone = ZoneId.of("Europe/Rome");
```

지역 ID 는 `지역/도시` 형식으로 이루어지고 `IANA Time Zone Datebase` 에서 제공하는 지역 집합 정보를 사용한다.

ZoneId 객체를 얻은 다음에는 LocalDate, LocalDateTime, Instant 를 이용해서 ZoneDateTime 인스턴스로 변환할 수 있다.

```java
LocalDate date = LocalDate.of(2014, Month.MARCH, 18);
ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
ZonedDateTime zdt2 = dateTime.atZone(romeZone);
Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(romeZone);
```

ZoneId 를 이용해서 LocalDateTime 을 Instant 로 바꿀 수 있다.

```java
Instant instant = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);
```

### 12.3.2 UTC/Greenwich 기준의 고정 오프셋

때로는 협정 세계시(UTC)/그리니치 표준시(GMT) 를 기준으로 표현하기도 한다.

ZoneId 의 서브클래스인 ZoneOffset 클래스로 런던의 그리니치 0도 자오선과 시간값의 차이를 표현할 수 있다.

```java
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
```

이는 서머타임을 제대로 처리할 수 없어 권장하지 않는다.

ISO-8601 캘린더 시스템에서 정의하는 UTC/GMT 와 오프셋으로 날짜와 시간을 표현하는 OffsetDateTime 을 만드는 방법도 있다.

```java
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(date, newYorkOffset);
```

### 12.3.3 대안 캘린더 시스템 사용하기

자바 8에서는 추가로 4개의 캘린더 시스템을 제공한다.

ThaiBuddhistDate, MinguoDate, JapaneseDate, HijrahDate 이다.

이 클래스와 LocalDate 클래스는 ChronoLocalDAte 인터페이스를 구현하는데 이는 임의의 연대기에서 특정 날짜를 표현할 수 있는 기능을 제공하는 인터페이스이다.

```java
LocalDate date = LocalDate.of(2014, Month.MARCH, 18);
JapaneseDate japaneseDate = JapaneseDate.from(date);
```

특정 Locale 과 Locale 에 대한 날짜 인스턴스로 캘린더 시스템을 만들수도 있다.

```java
Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN);
ChronoLocalDate now = japaneseChronology.dateNow();
```

날짜와 시간 API 의 설계자는 ChronoLocalDate 보다는 LocalDate 를 사용하라고 권고한다.

0 comments on commit a55c1d3

Please sign in to comment.