Link Search Menu Expand Document

섹션 5. Date와 Time

Date와 Time 소개

Java 8에 새로운 날짜와 시간 API가 생긴 이유

  • 그전까지 사용하던 java.util.Date 클래스는 mutable, 즉 객체 내부의 값이 변경 가능하기 때문에 thread-safe하지 않다.
    • 서로 다른 Thread에서 접근하여 Date 객체의 값을 서로 변경하면?
  • 클래스 이름이 명확하지 않다. Date인데 시간까지 다룬다.
  • 버그 발생할 여지가 많다. (타입 안정성이 없고, 월이 0부터 시작한다거나…)
  • 날짜 시간 처리가 복잡한 애플리케이션에서는 보통 Joda Time 을 쓰곤 했다.

예제

import java.util.Date;

public class Practice1 {
    public static void main(String[] args) throws InterruptedException{
        Date date = new Date();
        long time = date.getTime();
        System.out.println(date);
        System.out.println(time);

        Thread.sleep(1000 * 3);
        Date after3Seconds = new Date();
        System.out.println(after3Seconds);
        after3Seconds.setTime(time);
        System.out.println(after3Seconds);
    }
}
/*
Thu Apr 29 17:48:16 KST 2021
1619686096218
Thu Apr 29 17:48:19 KST 2021
Thu Apr 29 17:48:16 KST 2021 -> mutable
*/

자바 8에서 제공하는 Date-Time API

  • JSR-310 스팩 의 구현체를 제공한다.

  • 디자인 철학

    • Clear: API의 기능이 명확 (epoch time 사용하거나 getTime() 썼는데 Date 반환한다던가 하지 않음)
    • Fluent: null을 리턴하거나 null을 받는 메소드가 없음
    • Immutable: 기존의 인스턴스에 값을 더할 경우 새로운 인스턴스가 만들어짐
    LocalDateTime now = LocalDateTime.now();
    now.plus(10, ChronoUnit.DAYS); // Nothing Happens
    LocalDateTime plus = now.plus(10, ChronoUnit.DAYS);
    
    • Extensible: API를 이용하여 다양한 시간을 나타내는 클래스를 만들 수 있다.

주요 API

  • 기계용 시간 (machine time)과 인류용 시간(human time)으로 나눌 수 있다.
  • 기계용 시간은 EPOCH (1970년 1월 1일 0시 0분 0초)부터 현재까지의 타임스탬프를 표현한다.
  • 인류용 시간은 우리가 흔히 사용하는 연,월,일,시,분,초 등을 표현한다.
  • 타임스탬프는 Instant를 사용한다.
  • 특정 날짜(LocalDate), 시간(LocalTime), 일시(LocalDateTime)를 사용할 수 있다.
  • 기간을 표현할 때는 Duration (시간 기반)과 Period (날짜 기반)를 사용할 수 있다.
  • DateTimeFormatter를 사용해서 일시를 특정한 문자열로 포매팅할 수 있다.

Date와 Time API

지금 이 순간을 기계 시간으로 표현하는 방법

  • Instant.now(): 현재 UTC (GMT)를 리턴한다.
    • Universal Time Coordinated = Greenwich Mean Time
Instant now = Instant.now();
System.out.println(now);
System.out.println(now.atZone(ZoneId.of("UTC")));

ZonedDateTime zonedDateTime = now.atZone(ZoneId.systemDefault());
System.out.println(zonedDateTime);

인류용 일시를 표현하는 방법

  • LocalDateTime.now(): 현재 시스템 Zone에 해당하는(로컬) 일시를 리턴한다.
  • LocalDateTime.of(int, Month, int, int, int, int): 로컬의 특정 일시를 리턴한다.
  • ZonedDateTime.of(int, Month, int, int, int, int, Zoneid): 특정 Zone의 특정 일시를 리턴한다.
LocalDateTime now2 = LocalDateTime.now();
System.out.println(now2);
LocalDateTime birthday = 
    LocalDateTime.of(1992, 06, 04, 04, 50, 06);
ZonedDateTime nowInKorea = zonedDateTime.now(ZoneId.of("Asia/Seoul"));
System.out.println(nowInKorea);

Instant nowInstant = Instant.now();
ZonedDateTime zonedDateTime1 = nowInstant.atZone(ZoneId.of("Asia/Seoul"));
System.out.println(zonedDateTime1);

기간을 표현하는 방법

  • Period / Duration : between(), until()
Period between = Period.between(today, thisYearBirthday);
System.out.println(between.getDays());

Period until = today.until(thisYearBirthday);
System.out.println(until.get(ChronoUnit.MONTHS));
// 1
// 1 

Period는 기간을 연, 월, 일로 표현하기 때문에 30일이 넘어간 정보는 월에 담기게 된다. 전체 일수를 계산하고 싶다면 ChronoUnit이 제공하는 between(now, future)을 사용하면 된다.

기계 시간 사이의 기간을 계산할 때는 Duration, 인류용 일시 사이의 기간을 계산할 때는 Period를 사용한다.

파싱 또는 포매팅

  • LocalDateTime.parse(String, DateTimeFormatter)
DateTimeFormatter formatter =
	DateTimeFormatter.ofPattern("MM/d/yyyy");
LocalDate date = LocalDate.parse("06/04/1992", formatter);
System.out.println(date);
System.out.println(today.format(formatter));
// 1992-06-04
// 05/3/2021

레거시 API 지원

  • GregorianCalendarDate 타입의 인스턴스를 InstantZonedDateTime으로 변환 가능
  • java.util.TimeZone에서 java.time.ZoneId로 상호 변환 가능
ZoneId newZoneAPI = TimeZone.getTimeZone("PST").toZoneId();
TimeZone legacyZoneAPI = TimeZone.getTimeZone(newZoneAPI);

GregorianCalendar gregorianCalendar = new GregorianCalendar();
ZonedDateTime dateTime = gregorianCalendar.toInstant().atZone(ZoneId.systemDefault());
GregorianCalendar from = GregorianCalendar.from(dateTime);

Instant newInstant = new Date().toInstant();
Date legacyInstant = Date.from(newInstant);