java.time패키지

  • 날짜와 시간을 다루기 위한 Date와 Calendar 단점을 해소하기 위한 패키지(JDK 1.8~)

  • 하위 패키지 4가지

    • 패키지 수는 많지만 실제 사용되는 핵심 클래스들은 그리 많지 않음
  • 특징 : 불변(immutable)

    • 날짜나 시간을 변경하는 메서드들 : 기존의 객체 자체를 변경하는 대신 항상 변경된 새로운 객체를 반환

    • 기존 Calendar클래스는 변경이 가능하므로, 멀티 쓰레드 환경에서 안전하지 못함

  • 새로운 패키지가 도입되었음에도 불구하고, 앞으로도 기존에 작성된 프로그램과의 호환성 때문에 Date와 Calendar는 여전히 사용될 것

패키지 설명
java.time 날짜와 시간을 다루는데 필요한 핵심 클래스들을 제공
java.time.chrono 표준(ISO)이 아닌 달력 시스템을 위한 클래스들을 제공
java.time.format 날짜와 시간을 파싱하고, 형식화하기 위한 클래스들을 제공
java.time.temporal 날짜와 시간의 필드(field)와 단위(unit)을 위한 클래스들을 제공
java.time.zone 시간대(time-zone)와 관련된 클래스들을 제공



쓰레드에 안전(thread-safe)하지 않은 것

  • 멀티 쓰레드 환경에서는 동시에 여러 쓰레드가 같은 객체에 접근할 수 있음

  • 변경 가능한 객체는 데이터가 잘못될 가능성이 있음 → 쓰레드에 안전하지 않음



Calendar VS java.time패키지’s 클래스

  • Calendar : 날짜와 시간, 시간대를 하나로 표현

  • java.time : 날짜와 시간을 별도의 클래스로 구분

    • LocalDate : 날짜

    • LocalTime : 시간

    • LocalDateTime : 날짜 & 시간

    • ZonedDateTime : 날짜 & 시간 & 시간대(time-zone)



Date VS java.time패키지’s 클래스

  • Date

  • Instant : 타임스탬프

    • 타임스탬프? 날짜와 시간을 초 단위(정확히는 나노초)로 표현한 것
      날짜와 시간을 하나의 정수로 표현할 수 있으므로, 날짜와 시간의 차이를 계산하거나 순서를 비교하는데 유리
      데이터베이스에 많이 사용



java.time패키지의 핵심 클래스

  • 날짜를 더 세부적으로 다룰 수 있는 클래스

    • Year, YearMonth, MonthDay
  • 날짜와 시간의 간격을 표현하기 위한 클래스

    • Period : 두 날짜 간의 차이 표현

    • Duration : 시간의 차이를 표현



java.time패키지’s 클래스 - now(), of()

  • java.time 패키지에 속한 클래스의 객체를 생성하는 가장 기본적인 방법

  • now() : 현재 날짜와 시간을 저장하는 객체를 생성

  • of() 단순히 해당 필드의 값을 순서대로 지정하면 됨

    • 클래스마다 다양한 종류의 of()가 정의
  • 예제

/* now() */
LocalDate date = LocalDate.now(); // 2019-09-14
LocalTime time = LocalTime.now(); // 21:51:01.382
LocalDateTime dateTime = LocalDateTime.now(); // 2019-09-14T21:51:01.382
ZonedDateTime dateTimeInKr = ZonedDateTime.now(); // 2019-09-14T21:51:01.382+09:00[Asia/Seoul]

/* of() */
LocalDate date = LocalDate.of(2019, 8, 13); // 2019년 9월 14일
LocalTime time = LocalTime.of(21, 51, 01); // 21시 51분 01초

LocalDateTime dateTime = LocalDateTime(date, time);
ZonedDateTime zDateTime = ZonedDateTime.of(dateTime, ZoneId.of("Asia/Seoul"));



java.time패키지’s 인터페이스

  • Temporal, TemporalAccessor, TemporalAdjuster 인터페이스를 구현

    • 날짜와 시간을 표현하기 위한 클래스

    • (예) LocalDate, LocalTime, LocalDateTime, ZonedDateTime

  • TemporalAmount 인터페이스를 구현

    • Duration, Period
  • 매개변수의 타입이 Temporal로 시작할 경우, 대부분 날짜와 시작을 위한 것

    • TemporalAmount인지 아닌지만 확인할 것
  • temporal과 chrono의 의미가 모두 시간(time)인데도, time 대신 굳이 해당 단어를 사용한 이유는 시간(시분초)와 더 큰 개념의 시간(년월시분초)를 구분하기 위함임

  • TemporalUnit 인터페이스 : 날짜와 시간의 단위를 정의해 놓은 것

    • 열거형 ChronoUnit : TemporaUnit을 구현한 것
  • TemporalField 인터페이스 : 년, 월, 일 등 날짜와 시간의 필드를 정의해 놓은 것

    • 열거형 ChronoField : TemporalField 인터페이스를 구현
LocalTime now = LocalTime.now(); // 현재 시간

int minute = now.getMinute(); // 현재 시간에서 분(minute)만 뽑아냄
// int minute = now.get(ChronoField.MINUTE_OF_HOUR); // 위 문장과 동일



java.time패키지’s 메서드

  • get(), get으로 시작하는 이름의 메서드

    • 날짜와 시간에서 특정 필드의 값만을 얻을 때 이용

    • get() : int get(TemporalField field)

  • plus() 또는 minus()와 함께 열거형 ChronoUnit 사용

    • 특정 날짜와 시간에서 지정된 단위의 값을 더하거나 뺄 때

    • plus() : LocalDate plus(long amountToAdd, TemporalUnit unit)

  • TemporalField나 TemporalUnit을 사용할 수 있는지 확인하는 메서드

    • boolean isSupported(TemporalUnit unit) // Temporal에 정의

    • boolean isSupported(TemporalField field) // TemporalAccessor에 정의



LocalDate와 LocalTime

  • java.time패키지의 가장 기본이 되는 클래스

    • 나머지 클래스들은 이들의 확장임



LocalDate와 LocalTime - 생성

  • 객체를 생성하는 방법

    • static now() : 현재의 날짜와 시간을 LocalDate와 LocalTime으로 각각 변환

    • static of() : 지정된 날짜와 시간으로 LocalDate와 LocalTime객체를 생성

/* now()와 of() */
LocalDate today = LocalDate.now(); // 오늘 날짜
LocalTime now = LocalTime.now();   // 현재 시간

LocalDate sunnyDate = LocalDate.of(2019, 9, 14); // 2019년 9월 14일
LocalTime sunnyTime = LocalTime.of(22, 20, 24);  // 22시 20분 24초

/* of()는 여러가지 버전이 제공됨 */
static LocalDate of(int year, Month month, int dayOfMonth)
static LocalDate of(int year, int month,   int dayOfMonth)

static LocalTime of(int hour, int min)
static LocalTime of(int hour, int min, int sec)
static LocalTime of(int hour, int min, int sec, int nanoOfSecond)

/* 일 단위나 초 단위로도 지정할 수 있음 */
// 일 단위
LocalDate sunnyDate = LocalDate.ofYearDay(2019, 365); // 2019년 12월 31일
// 초 단위
LocalTime sunnyTime = LocalTime.ofSecondDay(86399);   // 23시 59분 59초


/* parse()를 이용하여 문자열을 날짜와 시간으로 변환하기 */
LocalDate sunnyDate = LocalDate.parse("2019-09-14"); // 2019년 09월 14일
LocalTime sunnyTime = LocalTime.parse("22:26:23");   // 22시 26분 23초



LocalDate와 LocalTime - get(), getXXX()

  • 특정 필드의 값 가져오기

  • 특정 필드의 값을 가져올 때는 아래의 표에 있는 메서드를 사용

  • 주의) Calendar와 다름!

    • java.time : 월(month) 1~12, 요일(weekday) 월1~일7

    • Calendar : 월(month) 9~11, 요일(weekday) 일1~토7

  • LocalDate 클래스 메서드

메서드 설명(1999-12-31 23:59:59)
int getYear() 년도(1999)
int getMonthValue() 월(12)
Month getMonth() 월(DECEMBER) getMonth().getValue() = 12
int getDayOfMonth() 일(31)
int getDayOfYear() 같은 해의 1월 1일부터 N번째 일(365)
DayOfWeek getDayOfWeek() 요일(FRIDAY) getDayOfWeek().getValue() = 5
int lengthOfMonth() 같은 달의 총 일수(31)
int lengthOfYear() 같은 해의 총 일수(365), 윤년이면(366)
boolean isLeapYear() 윤년여부 확인(false)


  • LocalTime 클래스 메서드
메서드 설명(1999-12-31 23:59:59)
int getHour() 시(23)
int getMinute() 분(59)
int getSecond() 초(59)
int getNano() 나노초(0)


  • 위의 메서드 외에도 get(), getLong()이 있음

    • 원하는 필드를 직접 지정 가능

    • int get(TemporalField field)

    • long getLong(TemporalField field) → int를 넘을 때, 아래 상수 표의 *에 해당

  • 상수

    • ChronoField에 정의된 모든 상수를 아래에 나열 (ChronoField.상수 형식으로 사용)

    • 사용할 수 있는 필드는 클래스마다 다름

    • 해당 클래스가 지원하지 않는 필드 사용시, UnsupportedTemporalTypeException 발생

    • (예) LocalDate는 날짜를 표현하기 위한 것으로, MINUTE_OF_HOUR과 같은 시간에 관련된 필드를 사용할 수 없음

    • 종류

상수 의미
ERA 시대
YEAR_OF_ERA, YEAR
MONTH_OF_YEAR
DAY_OF_WEEK 요일(1: 월요일, 2: 화요일, … , 7: 일요일)
DAY_OF_MONTH
AMPM_OF_DAY 오전/오후
HOUR_OF_DAY 시간(0~23)
CLOCK_HOUR_OF_DAY 시간(1~24)
HOUR_OF_AMPM 시간(0~11)
CLOCK_HOUR_OF_AMPM 시간(1~12)
MINUTE_OF_HOUR
SECOND_OF_MINUTE
MILLI_OF_SECOND 천분의 일초 (=10^-3초)
MICRO_OF_SECOND * 백만분의 일초 (=10^-6초)
NANO_OF_SECOND * 십억분의 일초(=10^-9초)
DAY_OF_YEAR 그 해의 N번째 날
EPOCH_DAY * EPOCH(1970.1.1)부터 N번째 날
MINUTE_OF_DAY 그 날의 N 번째 분(시간을 분으로 환산)
SECOND_OF_DAY 그 날의 N 번째 초(시간을 초로 환산)
MILLI_OF_DAY 그 날의 N 번째 밀리초(=10^-3초)
MICRO_OF_DAY * 그 날의 N 번째 마이크로초(=10^-6초)
NANO_OF_DAY * 그 날의 N 번째 나노초(=10^-9초)
ALIGNED_WEEK_OF_MONTH 그 달의 n번째 주(1~7일 1주, 8~14일 2주, …)
ALIGNED_WEEK_OF_YEAR 그 해의 n번째 주(1월 1~7일 1주, 8~14일 2주, …)
ALIGNED_DAY_OF_WEEK_IN_MONTH 요일(그 달의 1일을 월요일로 간주하여 계산)
ALIGNED_DAY_OF_WEEK_IN_YEAR 요일(그 해의 1월 1일을 월요일로 간주하여 계산)
INSTANT_SECONDS 년월일을 초단위로 환산
(1970-01-01 00:00:00 UTC를 0초로 계산)
Instant에만 사용 가능
OFFSET_SECONDS UTC와의 시차, ZoneOffset에만 사용 가능
PROLEPTIC_MONTH 년월을 월단위로 환산
(2019년 8월 = 2019 * 12 + 8)


  • 필드의 값 변경하기 - with(), plus(), minus()

  • with() : 날짜와 시간에서 특정 필드 값을 변경할 때 with로 시작하는 메서드 사용

    • LocalDate withYear(int year)
      LocalDate withMonth(int month)
      LocalDate withDayOfMonth(int dayOfMonth)
      LocalDate withDayOfYear(int dayOfYear)
      LocalTime withHour(int hour)
      LocalTime withMinute(int minute)
      LocalTime withSecond(int second)
      LocalTime withNano(int nanoOfSecond)

    • with()를 이용하면 필드를 직접 지정할 수 있음
      LocalDate with(TemporalField field, long newValue)

    • 필드를 변경하는 메서드들은 항상 새로운 객체를 생성해서 반환하기 때문에 대입 연산자를 반드시 같이 써줘야 함
      (예시 - O) date = date.withYear(2020);
      (예시 - X) date.withYear(2020);

  • plus() / minus() : 특정 필드에 값을 더하거나 빼는 plus() / minus()

    • LocalTime plus(TemporalAmount amountToAdd)
      LocalTime plus(long amountToAdd, TemporalUnit unit)
      LocalDate plus(TemporalAmount amountToAdd)
      LocalDate plus(long amountToAdd, TemporalUnit unit)
      minus()도 위와 동일

    • plus로 만든 메서드
      LocalDate plusYears(long yearsToAdd)
      LocalDate plusMonths(long monthsToAdd)
      LocalDate plusDays(long daysToAdd)
      LocalDate plusWeeks(long weeksToAdd)
      LocalTime plusHours(long hoursToAdd)
      LocalTime plusMinutes(long minutesToAdd)
      LocalTime plusSeconds(long secondsToAdd)
      LocalTime plusNanos(long nanosToAdd)

  • LocalTime의 truncatedTo()

    • 지정된 것보다 작은 단위 필드를 0으로 만들어버림

    • 예시
      LocalTime time = LocalTime.of(12, 34, 56); // 12시 34분 56초
      time = time.truncatedTo(ChronoUnit.HOURS); // 시보다 작은 단위를 0으로 만듦
      System.out.println(time); // 12:00

    • LocalDate는 사용할 수 없음 ← 년, 월, 일은 0이 될 수 없기 때문

    • LocalTime의 turncatedTo()의 매개변수는 아래 표 중 시간과 관련된 필드만 사용 가능
      표 - 열거형 ChronoUnit에 정의된 상수 목록

TemporalUnit(ChronoUnit) 설명
FOREVER Long.MAX_VALUE초(약 3천억년)
ERAS 1,000,000,000년
MILLENNIA 1,000년
CENTURIES 100년
DECADES 10년
YEARS
MONTHS
WEEKS
DAYS
HALF_DAYS 반나절
HOURS
MINUTES
SECONDS
MILLIS 천분의 일초(=10^-3)
MICROS 백만분의 일초(=10^-6)
NANOS 십억분의 일초(=10^-9)


  • 날짜와 시간의 비교 - isAfter(), isBefore(), isEqual()

  • ompareTo()

    • LocalDate와 LocalTime도 comareTo()가 적절히 오버라이딩 되어 있음

    • int result = date1.compareTo(date2); // 같으면 0, date1이 이전이면 -1, 이후면 1

  • 보다 편리하게 비교할 수 있는 메서드들이 추가로 제공

    • boolean isAfter(ChronoLocalDate other)

    • boolean isBefore(ChronoLocalDate other)

    • boolean isEqual(ChronoLocaldate other) // LocalDate에만 있음

    equals()가 있지만 isEqual()을 제공하는 이유

    isEqual()은 오직 날짜만 비교

    연표(chronology)가 다른 두 날짜를 비교하기 위해서

    대부분의 경우 equals와 isEqual()의 결과는 같음



Instant

  • 에포크 타임(EPOCH TIME, 1970-01-01 00:00:00 UTC)부터 경과된 시간을 나노초 단위로 표현

    • 사람에게는 불편하지만, 단일 진법으로만 다루기 때문에 계산하기 쉬움

    • 사람이 사용하는 날짜와 시간은 여러 진법이 섞여있기 때문에 계산이 어려움

  • 생성 - now()와 ofEpochSecond 사용

    • Instant now1 = Instant.now();

    • Instant now2 = Instant.ofEpochSecond(now.getEpochSecond());

    • Instant now3 = Instant.ofEpochSecond(now.getEpochSecond(), nowgetNano());

  • 필드에 저장된 값 가져오기

    • long epochSec = now.getEpochSecond();

    • int nano = now.getNano();

    • Instant는 시간을 초 단위와 나노초 단위로 나누어 저장

  • toEpochMilli()

    • 오라클 데이터베이스의 타임스탬프(timestamp)처럼 밀리초 단위의 EPOCH TIME을 필요로 하는 경우를 위함

    • long toEpochMilli();

  • Instant vs LocalTime

    • Instant : 항상 UTC(+00:00)를 기준

    • LocalTime : 한국은 시간대가 ‘+09:00’이므로 Instant와 9시간의 시간차이가 발생

    • 시간대를 고려해야하는 경우 OffsetDateTime을 사용하는 것이 더 나은 선택일 수 있음

  • UTC란?

    • ‘Coordinated Universal Time’ : 세계 협정시

    • 1972년 1월 1일부터 시행된 국제 표준시

    • 이전에 사용되던 GMT(Greenwich Mean Time)와 UTC는 거의 같지만 UTC가 좀더 정확함

  • instant와 Date간의 변환

    • Instant는 기존의 java.util.Date를 대체하기 위한 것

    • Instant → Date
      static Date from(Instant instant)

    • Date → Instant
      Instant toInstant()



LocalDateTime과 ZonedDateTime

  • LocalDateTime & ZonedDateTime

    • LocalDateTime ← LocalDate + LocalTime

    • ZonedDateTime ← LocalDateTime + 시간대

  • LocalDate와 LocalTime으로 LocalDateTime만들기1

    • now()

    • LocalDateTime today = LocalDateTime.now();

  • LocalDate와 LocalTime으로 LocalDateTime만들기2

    • of()

    • 다양한 버전의 of()가 정의되어 있음

    • 예) 2019년 9월 14일 8시 19분 13초

  - LocalDateTime dateTime = LocalDateTime.of(2019, 9, 14, 8, 19, 13);
  • LocalDate와 LocalTime으로 LocalDateTime만들기3

    • LocalDate와 LocalTime 합치기
LocalDate date = LocalDate.of(2019, 9, 14);
LocalTime time = LocalTime.of(8, 19, 13);
  
LocalDateTime dt1 = LocalDateTime.of(date, time);
LocalDateTime dt2 = date.atTime(time);
LocalDateTime dt3 = time.atDate(date);
LocalDateTime dt4 = date.atTime(8, 9, 13);
LocalDateTime dt5 = time.atDate(LocalDate.of(2019, 9, 14));
LocalDateTime dt6 = date.atStartOfDay(); // dt6 = date.atTime(0,0,0);
  • LocalDateTime의 변환

    • LocalDateTime을 LocalDate 또는 LocalTime으로 변환할 수 있음
LocalDateTime dt = LocalDateTime.of(2019, 9, 14, 8, 19, 13);
  
// LocalDateTime → LocalDate
LocalDate date = dt.toLocalDate();
  
// LocalDateTime → LocalTime
LocalTime time = dt.toLocalTime();
  • LocalDateTime으로 ZonedDateTime 만들기

    • ZonedDateTime = LocalDateTime + 시간대(time-zone)

    • ZoneId
      기존에는 TimeZone 시간대를 사용했으나, 새로운 time 패키지에서는 ZoneId라는 클래스를 사용
      ZoneId는 일광 절약시간(DST, Daylight Saving Time)을 자동으로 처리해주기 때문에 더 편리함
      사용 가능한 ZoneId의 목록은 ZoneId.getAvailableZonelds()로 얻을 수 있음

    • LocalDateTime → ZonedDateTime
      LocalDateTime의 atZone()으로 시간대 정보(ZoneId)를 추가하면 됨

    • LocalDate → ZoneDateTime
      LocalDate의 atStartOfDay()라는 메서드의 매개변수로 ZoneId를 지정하면 됨
      시간이 0시 0분 0초로 세팅

    • 특정 시간대의 시간을 알고 싶다면 (ex)뉴욕
      ZonedDateTime.now().withZoneSameInstant(ZoneId.of(“America/New_York”));

// LocalDateTime → ZonedDateTime
ZoneId zid = ZoneId.of("Asia/Seoul");
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt); // 2019-09-14T08:39:30.710
ZonedDateTime zdt = dateTime.atZone(zid);
System.out.println(zdt); // 2019-09-14T08:39:30.710+09:00[Asia/Seoul]

// LocalDate → ZoneDateTime
ZoneId zid = ZoneId.of("Asia/Seoul");
ZonedDateTime zdt = LocalDate.now().atStartOfDay(zid);   //of()사용하면 날짜와 시간 지정 가능
System.out.println(zdt); // 2019-09-14T00:00+09:00[Asia/Seoul]

//현재 시간대의 뉴욕을 알고싶다면
ZoneId nyId = ZoneId.of("America/New_York");
ZonedDateTime nyTime = ZonedDateTime.now().withZoneSameInstant(nyId); //of를 사용하면 날짜와 시간 지정 가능
System.out.println(nyTime); // 2019-08-13T19:50:00.379-04:00[America/New_York]
  • ZoneOffset

    • UTC로부터 얼마만큼 떨어져있는지를 ZoneOffset으로 표현

    • 서울은 +9, 즉 UTC보다 9시간(32400초 = 60 * 60 * 9초)가 빠름

ZoneOffset krOffset = ZoneDateTime.now().getOffset();
// ZoneOffset krOffset = ZoneOffset.of("+9"); // ±h, ±hh, ±hhmm, ±hh:mm
int krOffsetInSec = krOffset.get(ChronoField.OFFSET_SECONDS); // 32400초
  • ZonedDateTime의 변환

    • LocalDateTime처럼 날짜와 시간에 관련된 다른 클래스로 변환하는 메서드들을 가지고 있음

    • LocalDate toLocalDate()
      LocalTime toLocalTime()
      LocalDateTime toLocalDateTime()
      offsetDateTime toOffsetDateTime()
      long toEpochSecond()
      Instant toInstant()

  • ZonedDateTime은 GregorianCalendar와 가장 유사함

    • ZonedDateTime → GregorianCalendar
      GregorianCalendar from(ZonedDateTime zdt)

    • GregorianCalendar → ZonedDateTime
      ZonedDateTime toZonedDateTime()


OffsetDateTime

  • VS ZonedDateTime

    • ZonedDateTime : ZoneId로 구역을 표현
      ZoneId : 일광절약시간처럼 시간대와 관련된 규칙들을 포함

    • OffsetDateTime : ZoneOffset을 사용
      ZoneOffset : 단지 시간대를 시간의 차이로만 구분
      → 컴퓨터에게 일광절약시간처럼 계절별로 시간을 더했다 뺐다 하는 행위는 위험함
      → 아무런 변화 없이 일관된 시간체계를 유지하는 것이 더 안전함

  • 같은 지역내의 컴퓨터간 데이터를 주고받을 때, 전송시간 : LocalDateTime이면 충분
    다른 지역의(다른 시간대에) 존재하는 컴퓨터간의 통신 : OffSetDateTime이 필요

  • 생성 1 - LocalDate + LocalTime + ZoneOffset

    • 예시
LocalDate date = LocalDate.now();      // 2019-09-14
LocalTime time = LocalTime.now();      // 08:56:28.610
ZoneId zid = ZoneId.of("Asia/Seoul");   // Asia/Seoul
ZoneOffset krOffset = ZonedDateTime.now().getOffset(); // +09:00

// ZonedDateTime : 2019-09-14T08:56:28.610+09:00[Asia/Seoul]
ZonedDateTime zdt = ZonedDateTime.of(date, time, zid); 
// OffsetDateTime : 2019-09-14T08:56:28.610+09:00
OffsetDateTime odt = OffsetDateTime.of(date, time, krOffset);
  • 생성 2 - ZonedDateTime → OffsetDateTime

    • ZonedDateTime.toOffsetDateTime()

    • 예시

ZonedDateTime zdt = ZonedDateTime.now();   // 2019-09-14T08:58:00.780+09:00[Asia/Seoul]
OffsetDateTime odt = zdt.toOffsetDateTime();  // 2019-09-14T08:58:00.780+09:00