목차
- 자바 8에 새로운 날짜와 시간 API가 생긴 이유
- 자바 8에서 제공하는 Date-Time API 특징
- 주요 API
- API 연습
- 참고 자료
자바 8에 새로운 날짜와 시간 API가 생긴 이유
그전까지 사용하던 날짜 시간 클래스들은 문제점이 많다.
클래스 이름이 명확하지 않다. Date인데 시간까지 다룬다.
public class Practice {
public static void main(String[] args) throws InterruptedException{
Date date = new Date();
long time = date.getTime(); // Date(날짜)에서 시간을 가져온다고?
GregorianCalendar myBirthday2 = new GregorianCalendar(1998, Calendar.DECEMBER, 12);
System.out.println("myBirthday = " + myBirthday2.getTime());
}
// myBirthday = Sat Dec 12 00:00:00 KST 1998
Date(날짜) 클래스인데 시간까지 다룬다.
→ 클래스 이름이 명확하지 않다.
Date 클래스에서 가져오는 getTime() 메서드는 1970년 1월 1일 00:00:00 UTC(Unix epoch)부터 현재까지의 경과 시간을 밀리초(ms) 단위로 반환한다.
mutable 하다.
그전까지 사용하던 java.util.Date 클래스는 mutable 하기 때문에 thead safe하지 않다
public class Practice {
public static void main(String[] args) throws InterruptedException{
Date date = new Date();
long time = date.getTime(); // Date(날짜)에서 시간을 가져온다고?
System.out.println("time = " + time); // 1970년 부터 현재까지의 시간
System.out.println("date = " + date);
Thread.sleep(1000 * 3); // 3초
Date after3Seconds = new Date();
System.out.println("after3Seconds = " + after3Seconds);
after3Seconds.setTime(time); // mutable 하다. -> thread safe x
System.out.println("after3Seconds = " + after3Seconds);
}
}
// 출력
time = 1727945628552
date = Thu Oct 03 17:53:48 KST 2024
after3Seconds = Thu Oct 03 17:53:51 KST 2024
after3Seconds = Thu Oct 03 17:53:48 KST 2024
mutable : 객체의 상태를 변경할 수 있음을 의미
Date 클래스의 인스턴스를 생성한 후. 그 상태(날짜와 시간)를 변경할 수 있다.
예를 들어, setTime(long time) 메서드를 사용하면 이미 생성된 Date 객체의 시간을 변경할 수 있다.
Date after3Seconds = new Date(); // 새로운 Date 객체 생성
after3Seconds.setTime(time); // 이미 생성된 Date 객체의 시간을 변경
이와 같이, after3Seconds 객체의 시간을 time으로 설정함으로써
해당 객체의 내부 상태가 변경되었다.
이러한 특성으로 인해 여러 스레드가 같은 Date 객체를 공유하게 되면 한 스레드에서의 변경이 다른 스레드에 영향을 미치게 된다.
버그 발생할 여지가 많다.
month 0부터 시작
타입 안정성이 없다.
// month 0부터 시작 내 생일은 12월인데 0부터 시작해서 11로 표현 -> 실수할 가능성이 많다.
GregorianCalendar myBirthday1 = new GregorianCalendar(1998, 11, 12);
// Calendar.DECEMBER = 11
GregorianCalendar myBirthday2 = new GregorianCalendar(1998, Calendar.DECEMBER, 12);
// 타입 안정성이 없다. (잘못된 값이 들어올 가능성이 있음) 런타임 에러 x
GregorianCalendar wrongMonth = new GregorianCalendar(1998, -2000, 12);
GregorianCalendar 생성자
public GregorianCalendar(int year, int month, int dayOfMonth) {
this(year, month, dayOfMonth, 0, 0, 0, 0);
}
날짜 시간 처리가 복잡한 애플리케이션에서는 보통 Joda Time을 쓰곤했다
자바 8에서 제공하는 Date-Time API 특징
Java 8에서 도입된 Date-Time API는 JSR-310스펙에 기반한 새로운 날짜 및 시간 처리 방식으로, 이전의 java.util.Date 및 java.util.Calendar 클래스를 개선하기 위해 설계되었다.
이 API의 주요 디자인 철학은 Clear, Fluent, Immutable, Extensible의 네 가지 요소로 설명될 수 있다.
Clear (명확성)
- 명확한 API: Java 8 Date-Time API는 명확하고 직관적인 클래스를 제공한다.
날짜와 시간의 개념이 명확히 구분되어 있어, 예를 들어 LocalDate, LocalTime, LocalDateTime 같은 클래스가 존재 - 의미 있는 이름: 각 클래스의 이름이 해당 클래스의 기능과 목적을 명확하게 나타낸다.
예를 들어, LocalDate는 특정한 날짜를 나타내며, LocalTime은 특정한 시간을 나타낸다.
이러한 명확성 덕분에 코드를 읽고 이해하기 쉽다.
Fluent (유창성)
- 체이닝: 메서드 체이닝을 지원하여 연속적으로 메서드를 호출할 수 있다.
예를 들어, LocalDate 객체에서 날짜를 추가하거나 조정하는 작업을 메서드 체이닝으로 수행할 수 있다.
LocalDate date = LocalDate.now()
.plusDays(5)
.minusMonths(1);
- 가독성 향상: 이러한 유창한 메서드 체이닝은 코드를 더 간결하고 가독성이 높게 만들어 준다.
Immutable (불변성)
- 불변 객체: Java 8 Date-Time API의 객체들은 기본적으로 불변(immutable).
즉, 객체가 생성된 후 그 상태를 변경할 수 없습니다.
이는 스레드 안전성을 보장하고, 데이터의 일관성을 유지하는 데 큰 도움이 된다.
LocalDate date1 = LocalDate.now();
LocalDate date2 = date1.plusDays(1); // date1은 변경되지 않음 새로운 객체 반환
Extensible (확장성)
- 확장 가능한 설계: Java 8 Date-Time API는 다양한 날짜 및 시간 관련 기능을 제공하며, 필요에 따라 새로운 클래스를 쉽게 추가하거나 확장할 수 있도록 설계되었다.
- Time Zone과 Locale 지원: ZonedDateTime 및 OffsetDateTime 같은 클래스를 통해 시간대 및 오프셋에 대한 지원이 제공되며, 이는 다양한 지역의 날짜와 시간 처리를 용이하게 만든다.
주요 API
Java 8의 Date-Time API는 시간과 날짜를 처리하는 데 있어 기계용 시간과 인류용 시간을 명확히 구분할 수 있도록 다양한 클래스를 제공한다.
기계용 시간 (Machine Time)
- EPOCH: 기계용 시간은 보통 1970년 1월 1일 0시 0분 0초(UTC)부터 경과한 시간을 밀리초 또는 초 단위로 나타내는 타임스탬프이다. 이는 컴퓨터 시스템에서 시간을 측정하고 처리하는 데에 유용하다.
- Instant: 이 클래스를 사용하여 특정 시점의 타임스탬프를 표현할 수 있습니다. Instant는 UTC 기준으로 시간을 나타낸다.
Instant now = Instant.now(); // 현재 시점의 Instant 객체 생성
System.out.println(now);
// 출력
2024-10-03T10:08:08.260993900Z
인류용 시간 (Human Time)
- LocalDate: 연도, 월, 일로 표현되는 날짜를 나타낸다.
예를 들어, 특정 날짜(2024년 10월 3일)를 표현할 수 있다.
LocalDate localDate = LocalDate.of(2024, 10, 3);
System.out.println("localDate = " + localDate);
// 출력
localDate = 2024-10-03
- LocalTime: 시, 분, 초로 표현되는 시간을 나타낸다. 예를 들어, 15시 30분을 표현할 수 있다.
LocalTime localTime = LocalTime.of(15, 30);
System.out.println("localTime = " + localTime);
// 출력
localTime = 15:30
- LocalDateTime: 날짜와 시간을 함께 표현하는 클래스
특정 일시(2024년 10월 3일 15시 30분)를 표현할 수 있다.
LocalDateTime dateTime = LocalDateTime.of(2024, 10, 3, 15, 30);
System.out.println("dateTime = " + dateTime);
// 출력
dateTime = 2024-10-03T15:30
기간 표현
- Duration: 시간 기반의 기간을 표현하는 클래스.
예를 들어, 두 시점 간의 경과 시간을 초나 분, 시간 단위로 표현할 수 있다.
Duration duration = Duration.ofHours(2); // 2시간
System.out.println("duration = " + duration);
// 출력
duration = PT2H
- Period: 날짜 기반의 기간을 표현하는 클래스.
예를 들어, 두 날짜 간의 경과를 연, 월, 일 단위로 표현할 수 있다.
Period period = Period.ofDays(10); // 10일
System.out.println("period = " + period);
// 출력
period = P10D
포매팅
- DateTimeFormatter: 날짜와 시간을 특정 형식의 문자열로 변환하는 데 사용되는 클래스.
포맷을 정의하여 날짜나 시간을 사람에게 읽기 쉬운 형태로 표현할 수 있다.
LocalDateTime dateTime = LocalDateTime.of(2024, 10, 3, 15, 30);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = dateTime.format(formatter); // "2024-10-03 15:30:00"와 같은 문자열 생성
System.out.println("formattedDateTime = " + formattedDateTime);
// 출력
formattedDateTime = 2024-10-03 15:30:00
API 연습
지금 이 순간을 기계 시간으로 표현하는 방법
- Instant.now(): 현재 UTC (GMT)를 리턴한다.
- Universal Time Coordinated == Greenwich Mean Time
Instant instant = Instant.now();// 기준시: UTC GMT 반환
System.out.println("instant = " + instant);
System.out.println("UTC = " + instant.atZone(ZoneId.of("UTC")));
ZoneId zone = ZoneId.systemDefault(); // 내 컴퓨터 지역
System.out.println("zone = " + zone); // zone = Asia/Seoul
ZonedDateTime zonedDateTime = instant.atZone(zone); // Asia/Seoul 시간
System.out.println("zonedDateTime = " + zonedDateTime);
출력
instant = 2024-10-03T10:43:01.934090200Z
UTC = 2024-10-03T10:43:01.934090200Z[UTC]
zone = Asia/Seoul
zonedDateTime = 2024-10-03T19:43:01.934090200+09:00[Asia/Seoul]
인류용 일시를 표현하는 방법
- LocalDateTime.now(): 현재 시스템 Zone에 해당하는(로컬) 일시를 리턴한다.
- LocalDateTime.of(int, Month, int, int, int, int): 로컬의 특정 일시를 리턴한다.
- ZonedDateTime.of(int, Month, int, int, int, int, ZoneId): 특정 Zone의 특정 일시를 리턴한다.
LocalDateTime.now()
주의: LocalDateTime.now() 만약 배포를 미국에 있는 서버로 한다면 미국 시간을 반환한다.
LocalDateTime now = LocalDateTime.now(); // 현재 컴퓨터를 기준으로
System.out.println("now = " + now);
// now = 2024-10-03T19:52:54.900899700
LocalDateTime.of(int, Month, int, int, int, int)
해당 API는 LocalDateTime의 스테틱 메소드 of()이다. 자바 8 이전의 API와 다르게 타입 안전이 보장된다.
LocalDateTime myBirthday = LocalDateTime.of(1998, 12, 12, 0, 0, 0);
System.out.println("myBirthday = " + myBirthday);
// myBirthday = 1998-12-12T00:00
LocalDateTime myBirthday = LocalDateTime.of(1998, 13, 12, 0, 0, 0); // -> Exception 발생
// DateTimeException
Exception in thread "main" java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 13
at java.base/java.time.temporal.ValueRange.checkValidValue(ValueRange.java:319)
at java.base/java.time.temporal.ChronoField.checkValidValue(ChronoField.java:718)
ZonedDateTime.of(int, Month, int, int, int, int, ZoneId)
ZonedDateTime nowInKorea = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
System.out.println("nowInKorea = " + nowInKorea);
// 출력
nowInKorea = 2024-10-03T19:59:37.028634400+09:00[Asia/Seoul]
기간을 표현하는 방법
- Period / Duration . beteen()
Period
// 2024년 10월 3일 = today
LocalDate today = LocalDate.now();
LocalDate thisYearBirthday = LocalDate.of(2024, Month.DECEMBER, 12);
Period between = Period.between(today, thisYearBirthday);
System.out.println("between = " + between); // 월/일 표현
System.out.println("between.getDays() = " + between.getDays()); // 날짜만 가져오기
Period until = today.until(thisYearBirthday); // until API
System.out.println("until = " + until.getDays());
// 출력
between = P2M9D 2달 & 9일 남음
between.getDays() = 9
until = 9
Duration
Instant nowInstant = Instant.now();
Instant plus = nowInstant.plus(10, ChronoUnit.SECONDS);
Duration duration = Duration.between(nowInstant, plus);
System.out.println("duration.getSeconds() = " + duration.getSeconds());
// 출력
duration.getSeconds() = 10
포맷팅 / 파싱 / 기타
포매팅
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter MMddyyyy = DateTimeFormatter.ofPattern("MM/dd/yyyy");
System.out.println(now.format(MMddyyyy));
미리 정의해둔 포맷 참고
https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#predefined
파싱
DateTimeFormatter MMddyyyy = DateTimeFormatter.ofPattern("MM/dd/yyyy");
LocalDate parse = LocalDate.parse("12/12/1998", MMddyyyy);
System.out.println(parse);
// 출력
1998-12-12
레거시 API 지원
Date date = new Date();
Instant instant = date.toInstant(); // Date(레거시) -> Instant
Date newDate = Date.from(instant); // Instant -> Date
GregorianCalendar calendar = new GregorianCalendar();
// GregorianCalendar(레거시) -> ZonedDateTime
ZonedDateTime zonedDateTime = calendar.toInstant().atZone(ZoneId.systemDefault());
// ZonedDateTime -> LocalDateTime
LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
// ZonedDateTime -> GregorianCalendar(레거시)
GregorianCalendar from = GregorianCalendar.from(zonedDateTime);
// TimeZone(레거시) -> ZoneId
ZoneId zoneId = TimeZone.getTimeZone("PST").toZoneId();
// ZoneId -> TimeZone(레거시)
TimeZone timeZone = TimeZone.getTimeZone(zoneId);
마지막 주의점
LocalDateTime time = LocalDateTime.now();
// 새로운 객체를 사용해야 한다.
LocalDateTime plus = time.plus(10, ChronoUnit.YEARS); // Immutable
예전 레거시 API에서는 그냥 날짜/시간 더하기만 하면 해당 객체가 바뀌기 때문에 추가 action이 필요 없었지만
자바 8 Date-Time API는 Immutable 하기 때문에 새로운 객체를 받아서 사용해야 한다.
plus만 하고 반환하는 객체를 받지 않으면 아무 의미 없다.
참고 자료
참고
'Computer Sience > Java' 카테고리의 다른 글
[JAVA] Annotation (0) | 2024.10.07 |
---|---|
[JAVA8] CompletableFuture (0) | 2024.10.04 |
[JAVA8] Optional (0) | 2024.10.03 |
[JAVA8] Stream (0) | 2024.10.02 |
[JAVA8] 자바 8 API의 기본 메소드와 스태틱 메소드 (6) | 2024.10.02 |