본문 바로가기
Computer Sience/Java

[JAVA8] Date & Time

by 제우제우 2024. 10. 3.

목차

  • 자바 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만 하고 반환하는 객체를 받지 않으면 아무 의미 없다.


참고 자료 

인프런 백기선님 더 자바, Java 8

 

더 자바, Java 8 강의 | 백기선 - 인프런

백기선 | 자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합

www.inflearn.com

참고

'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