개발 공부/Java & Spring

11. java.time 패키지

빵다희 2022. 12. 28. 20:25
java.time 패키지
- Date, Calendar가 가지고 있던 단점을 해소하기 위해 JDK1.8부터 추가된 패키지.
-  java.time 패키지 하위에는 chrono, format, temporal,zone의 4가지 서브패키지가 있다. 
-  java.time과 그 하위 패키지에 속한 클래스들은 값이 변경되지 않은 '불변'의 특징을 갖고 있기 때문에 날짜나 시간을 변경하는 메서드들은 기존의 객체를 변경하는 대신 항상 변경된 새로운 객체를 반환한다.
(기존 Calandar는 변경이 가능했는데 이는 멀티 쓰레드 환경에서 동시에 여러 쓰레드가 같은 객체에 접근 할 수 있기 때문에 데이터가 잘못될 가능성이 있다. 그래서 java.time 패키지의 클래스들이 Calandar 클래스보다 쓰레드에 안전하다고 볼 수 있다.)

 

TemporalUnit
- 날짜와 시간의 단위를 정의해 놓은 인터페이스.
- 열거형 클래스인 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억분의 일초(10-9)

 

TemporalField
- 년, 월, 일 등 날짜와 시간의 필드를 정의해 놓은 인터페이스.
- 열거형 클래스인 ChronoField은 TemporalField를 구현함.

*ChronoField에 정의된 상수 목록

상수명 설명
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억분의 일초(=10초-9초)
DAY_OF_YEAR 그 해의 몇번째 날
EPOCH_DAY EPOCH(1970.1.1)부터 몇번째 날
MINUTE_OF_DAY 그 날의 몇 번째 분(시간을 분으로 환산)
SECONDE_OF_DAY 그 날의 몇 번째 초(시간을 초로 환산)
MILLI_OF_DAY 그 날의 몇 번째 밀리초(=10-3초)
MICRO_OF_DAY 그 날의 몇 번째 마이크로초(=10-6초)
NANO_OF_DAY 그 날의 몇 번째 나노초(=10-9초)
ALIGNED_WEEK_OF_MONTH 그 달의 n번째 주(1~7일 1주, 8~14일 2주, ...)
ALIGNED_WEEK_OF_YEAR 그 해의 n번째 주(1~7일 1주, 8~14일 2주, ...)
ALIGNED_WEEK_OF_WEEK_IN_MONTH 요일(그 달의 1일 월요일로 간주하여 계산)
ALIGNED_WEEK_OF_WEEK_IN_YEAR 요일(그 해의 1월 1일을 월요일로 간주하여 계산)
INSTANT_SECONDS 년월일을 초단위로 환산(1970-01-01 00:00:00 UTC를 0초 계산)
Instant에만 사용가능
OFFSET_SECONDS UTC와의 시차. ZoneOffset에만 사용가능
PROLEPTIC_MONTH 년월을 월단위로 환산(2015년11월=2015*12+11)

* 상수가 가지고 있는 값의 범위를 알고 싶으면 상수.range()를 사용하면 된다.

CLOCK_HOUR_OF_DAY.range(); // 1 - 24

HOUR_OF_DAY.range(); // 0 - 23

LocalDate와 LocalTime
- LocalDate : 날짜를 표현하는 클래스
- LocalTime : 시간을 표현하는 클래스
* 날짜와 시간이 모두 필요한 경우에는 LocalDateTime클래스를 사용하면 된다.
LocalDate + LocalTime = LocalDateTime
* 주의 할 점은 Calandar와 달리 월(Month)의 범위가 1~12이고, 요월은 월요일 1, 화요일이 2, ... 일요일이 7이라는 것이다.

 

* LocalDate와 LocalTime 객체 생성 방법에는 now(), of() 두가지의 방법이 있다.

1. now()

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

2. of()

/* 1999년 12월 31 */
LocalDate birthDate = LocalDate.of(1999, 12, 31);

/* 23시 59분 59초 */
LocalTime birthTime = LocalTime.of(23, 59, 59);

/* 1999년의 365번째 날 */
LocalDate birthDate2 = LocalDate.ofYearDay(1999, 365);

/* 오늘의 0시 0분 0초부터 86399초가 지난 시간(하루는 86400초) => 23시 59분 59초*/
LocalTime birthTime2 = LocalTime.ofSecondDay(86399);

 

3. 특정 필드의 값 가져오기 - get(), get~()

클래스 메서드 설명(예시 1999-12-31 23:59:59)
LocalDate int getYear() 년도(1999)
int getMonthValue() 월(12)
Month getMonth() 월(DECEMBER) getMonth.getValue() = 12
int getDayOfMonth() 일(31)
int getDayOfYear() 같은 해의 1월 1일부터 몇번째 일(365)
DayOfWeek getDayOfWeek() 요일(FRIDAY) getDayOfWeek().getValue() = 5
int lengthOfMonth() 같은 달의 총 일수(31)
int lengthOfYear() 같은 해의 총 일수(365), 윤년이면 366
boolean isLeapYear() 윤년여부 확인(false)
LocalTime int getHour() 시(23)
int getMinute() 분(59)
int getSecond() 초(59)
int getNano() 나노초(0)
공통 int get(TemporalField) 원하는 필드를 인자값으로 받아 값 리턴(클래스 특징에 맞는 필드만 사용)
int getLong(TemporalField) 특정 필드들은 int타입의 범위를 넘을 수 있으로 그럴 경우에 사용.

4. 필드 값 변경하기 - with(), plus(), minus()

* 날짜와 시간에서 특정 필드 값을 변경하려면, with() 혹은 with로 시작하는 메서드를 사용하면 된다.

클래스  메서드 설명
LocalDate LocalDate withYear(int year) 연도를 변경한다
LocalDate withMonth(int month) 월을 변경한다.
LocalDate withDayOfMonth(int dayOfMonth) 일을 변경한다.
LocalDate withDayOfYear(int dayOfYear) 그 해의 일로 변경한다. (예시 withDayOfYear(1) 은 1월 1일)
LocalTime LocalTime withHour(int hour) 시를 변경한다.
LocalTime withMinute(int minute) 분을 변경한다.
LocalTime withSecond(int second) 초를 변경한다.
LocalTime withNano(int nanoOfSecond) 나노초를 변경한다.
공통 LocalDate with(Temporalfield field, long newValue)
LocalTime with(Temporalfield field, long newValue)
원하는 필드를 지정하여 값을 변경한다.

* 날짜와 시간에서 특정 필드에 값을 더하거나 빼려면 plus(),minus()가 있다. 

* 마이너스는 메소드명을 plus 대신 minus로 변경하여 사용. 표에는 plus메소드만 명시. 

클래스 메서드 설명
LocalDate LocalDate plusYears(long yearsToAdd) 연도를 더한다
LocalDate plusMonths(long monthToAdd) 월을 더한다.
LocalDate plusDays(long daysToAdd) 일을 더한다.
LocalDate plusWeeks(long weeksToAdd) 주를 더한다.
LocalTime LocalTime plusHours(long hoursToAdd) 시를 더한다.
LocalTime plusMinutes(long minutesToAdd) 분을 더한다.
LocalTime plusSeconds(long secondsToAdd) 초를 더한다.
LocalTime plusNanos(long nanosToAdd) 나노초를 더한다.
공통 LocalDate plus(TemporalAmount amountToAdd)
LocalDate plus(long amountToAdd, TemporalUnit unit)
LocalTime plus(TemporalAmount amountToAdd)
LocalTime plus(long amountToAdd, TemporalUnit unit)
원하는 필드를 지정하여 값을 더한다.

5. truncatedTo()

* LocalTime에만 있는 메소드로 지정된 것보다 작은 단위의 필드를 0으로 만든다.

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

* LocalDate에 없는 이유는 LocalDate의 필드인 년,월,일은 0이 될 수 없기 때문이다. 

 

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

클래스 메소드명 설명
LocalDate isEqual(비교할 날짜 또는 시간 객체) 인자값과 같은 날짜인지 계산, 연표에 관계없이 오직 날짜만 비교한다.
공통 isAfter(비교할 날짜 또는 시간 객체) 인자값보다 이후이면 true
isBefore(비교할 날짜 또는 시간 객체) 인자값보다 이전이면 true

 

Instant
- Instant는 에포크타임(1970-01-01 00:00:00 UTC)부터 결과된 시간을 나노초 단위로 표현한다. 
- 단일 진법으로만 다루기 때문에 계산하기가 쉽다. 
- Instant는 시간을 초 단위와 나노초 단위로 나누어서 저장한다. 
- 오라클 데이터베이스의 타임스탬프(timestamp)처럼 밀리초 단위의 EPOCHTIME을 필요로 하는 경우를 위해 toEpochMilli() 메소드가 정의되어있다. 
- Instant는 항상 UTC(+00:00)를 기준으로 하기 때문에 LocalTime과 차이가 있을 수 있으니 시간대를 고려해야하는 경우는 OffsetDateTime 클래스를 사용하는 것이 더 나을 수 있다. 

* Instant의 객체 생성

Instant now = Instant.now();
Instant now2 = Instant.ofEpochSecond(now.getEpochSecond());
Instant now3 = Instant.ofEpochSecond(now.getEpochSecond(), now.getNano());

/* Instant와 Date간의 변환 */
Date d = Date.from(now);
Instant i = d.toInstant();
LocalDateTime과 ZonedDateTime
- LocalDate + LocalTime = LocalDateTime
- LocalDateTime + 시간대 = ZonedDateTime

1. LocalDateTime 객체 생성하기

/* LocalDate와 LocalTime으로 LocalDateTime 만들기 */

LocalDate date = LocalDate.of(2015, 12, 31);
LocalTime time = LocalTime.of(12, 34, 56);

LocalDateTime dt = LocalDateTime.of(date,time);
LocalDateTime dt2 = date.atTime(time);
LocalDateTime dt3 = time.atDate(date);
LocalDateTime dt4 = date.atTime(12, 34, 56);
LocalDateTime dt5 = time.atDate(LocalDate.of(2015, 12, 31));
LocalDateTime dt6 = date.atStartOfDay(); // date.atTime(0, 0, 0) 과 결과가 같다.

/* 날짜와 시간을 직접 지정하여 LocalDateTime 만들기 */

// 2015년 12월 31일 12시 34분 56초
LocalDateTime dateTime = LocalDateTime.of(2015,12,31,12,34,56);
LocalDateTime today = LocalDateTime.now();

2. LocalDateTime으로 ZonedDateTime 만들기

/* 1.LocalDateTime에 ZoneId라는 클래스를 추가하여 ZonedDateTime 객체 생성 */

ZoneId zid = ZoneId.of("Asia/Seoul"); /* zonedId 구하기 */
ZonedDateTime zdt = dateTime.atZone(zid);
System.out.println(zdt); /* 2015-11-27T17:47:50.451+09:00[Asia/Seoul] */

/* 2.LocalDate의 atStartOfDay() 메소드를 이용하여 ZonedDateTime 객체 생성 */

ZonedDateTime zdt = LocalDate.now().atStartOfDay(zid);
System.out.println(zdt); /* 2015-11-27T00:00:00+09:00[Asia/Seoul] */

/* 3. 다른 zoneID로 ZonedDateTime 객체 변경 */
ZoneId nyId = ZoneId.of("America/New_York"); /* zonedId 구하기 */
ZonedDateTime nyTime = ZonedDateTime.now().withZoneSameInstant(nyId);

* 사용가능한 ZoneId의 목록은 ZoneId.getAvailableZoneIds()로 얻을 수 있다. 

 

3. ZoneOffset 클래스 

/* ZoneOffSet : UTC로부터 얼마만큼 떨어져 있는지를 표현한다. */
/* ZoneOffSet 객체 생성 */

ZoneOffSet krOffset = ZonedDateTime.now().getOffset();
// 이렇게도 가능하다 
// ZoneOffSet krOffset = krOffset.of("+9");

// ZoneOffSet을 초로 변환하는 방법
int krOffsetInSec = krOffset.get(ChronoField.OFFSET_SECONDS); // 9시간 == 32400초

4. OffsetDateTime 클래스 

ZonedDateTime은 ZoneId로 구역을 표현하는데, OffsetDateTime은 ZoneId가 아닌 ZoneOffset을 사용한다.
ZoneId는 일광절약시간(써머타임)처럼 시간대와 관련된 규칙들을 포함하고 있는데, 이처럼 계절별로 시간을 더했다 뺐다 하는 행위는 컴퓨터에게 위험하다.
그래서 아무런 변화없이 일관된 시간체제를 유지하는 것이 컴퓨터 입장에서는 더 안전하다.
같은 지역에서의 데이터 통신이라면 LocalDateTime으로 충분하겠지만, 서로 다른 시간대에 존재하는 컴퓨터간의 통신에는 OffsetDateTime이 필요하다.
/* OffsetDateTime 객체 생성 */

OffsetDateTime odt = OffsetDateTime.of(date, time, krOffset);
ZonedDateTime zdt = ZonedDateTime.of(date, time, zid); 

// ZonedDateTime -> OffsetDateTime 변환
OffsetDateTime odt = zdt.toOffsetDateTime();

5. ZonedDateTime의 변환

ZonedDateTime zdt = ZonedDateTime.of(date, time, zid); 

LocalDate ld = zdt.toLocalDate();
LocalTime lt = zdt.toLocalTime();
LocalDateTime ldt = zdt.toLocalDateTime();
OffsetDateTime odt = zdt.toOffsetDateTime();
long l = zdt.toEpochSecond();
Instant i = zdt.toInstant();

/* GregorianCalandar와의 변환 */
// ZonedDateTime -> GregorianCalandar
GregorianCalandar g = GregorianCalandar.from(zdt);

// GregorianCalandar -> ZonedDateTime 
ZonedDateTime gzdt = g.toZonedDateTime();
TemporalAdjusters
plus(), minus() 와 같은 메소드로 날짜와 시간을 계산할 수 있지만,
지난주 토요일의 날짜, 이번달 3번째주 금요일의 날짜와 같은 계산을 하기에 불편하다. 
그래서 자주 쓰일만한 날짜 계산들을 대신 해주는 메서드를 정의해 놓은 것이 TemporalAdjusters이다.

* TemporalAdjusters 메서드

메서드 설명
firstDayOfNextYear() 다음 해의 첫 날
firstDayOfNextMonth() 다음 달의 첫 날
firstDayOfYear() 올 해의 첫 날
firstDayOfMonth() 이번 달의 첫 날
lastDayOfYear() 올 해의 마지막 날
lastDayOfMonth() 이번 달의 마지막 날
firstInMonth (DayOfWeek dayOfWeek) 이번 달의 첫번째 ? 요일
lastInMonth (DayOfWeek dayOfWeek) 이번 달의 마지막 ? 요일
previous (DayOfWeek dayOfWeek) 지난 ?  요일(당일 미포함)
previousOrSame (DayOfWeek dayOfWeek) 지난 ?  요일(당일 포함)
next (DayOfWeek dayOfWeek) 다음 ?  요일(당일 미포함)
nextOrSame (DayOfWeek dayOfWeek) 다음 ?  요일(당일 포함)
dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek) 이번 달의 n번째 ? 요일

* 사용 예시

LocalDate today = LocalDate.now();
LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY));

/* 정의된 메소드 말고 직접 구현하기 */
/* TemporalAdjusters를 구현받아 메소드를 커스텀 해보자 */

class DayAfterTomorrow implements TemporalAdjusters {
	@Override
    public Temporal adjustInto(Temporal temporal){
    	return temporal.plus(2, ChronoUnit.DAYS); // 2일을 더한다. 
    }
}

LocalDate dayAfterTomorrow = today.with(new DayAfterTomorrow());

 

Period와 Duration 
- Period:  두 날짜간의 차이를 표현하기 위한 클래스
- Duration : 두 시간간의 차이를 표현하기 위한 클래스 

1. 날짜, 시간 차이를 구하는 메소드 - between(), until()

/* 날짜 간의 차이 구하기 */

LocalDate d1 = LocalDate.of(2014,1,1);
LocalDate d2 = LocalDate.of(2015,12,31);

/* d1이 d2보다 날짜 상으로 이전이면 양수, 이후면 음수로 Period 객체가 생성된다. */
Period pe = Period.between(d1,d2); // 양수

/* 시간 간의 차이 구하기 */

LocalTime t1 = LocalTime.of(00,00,00);
LocalTime t2 = LocalTime.of(12,34,56);

/* t1이 t2보다 시간 상으로 이전이면 양수, 이후면 음수로 Duration 객체가 생성된다. */
Duration du = Duration.between(t1,t2); // 양수

/* until()은 between()과 동일한 기능을 하는 메소드이지만 인스턴스 메서드라는 차이점이 있다. */

Period pe = d1.until(d2); // 양수

/* 시간 계산에도 until을 사용할 수 있지만 Duration 객체를 반환하는 until은 없다. */

long sec = LocalTime.now().until(endTime, ChronoUnit.SECONDS);

2. 특정 필드 값을 가져오는 메소드 - get()

/* period */

long year = pe.get(ChronoUnit.YEARS);
long month = pe.get(ChronoUnit.MONTHS);
long day = pe.get(ChronoUnit.DAYS);
int y = pe.getYears();
int m = pe.getMonth();
int d = pe.getDays();

/*duration*/

long sec = du.get(ChronoUnit.SECONDS);
int nano = du.get(ChronoUnit.NANOS);
long s = du.getSeconds();
int n = du.getNano();

/* 초와 나노초로 시, 분 변환 */

long hour = du.getSeconds() / 3600;
long min = (du.getSeconds() - hour*3600) / 60;
long sec = (du.getSeconds() - hour*3600 - min * 60) % 60;
int nano = du.getNano();

/* LocalTime으로 변환하여 시,분,초 가져오기 */

LocalTime tmpTime = LocalTime.of(0,0).plusSeconds(du.getSeconds);
int hour = tmpTime.getHour();
int min = tmpTime.getMinute();
int sec = tmpTime.getSecond();
int nano = tmpTime.getNano();

 

3. 특정값을 지정하여 객체 생성하는 of()와 값변경을 할 수 있는 with()

Period에는 of(), ofYears(), ofMonths(), ofWeeks(), ofDays() 가 있고
Duration에는 of(), ofDays(), ofHours(), ofMinutes(), ofSeconds() 등이 있는데 사용법은 LocalDate, LocalTime과 같다.
특정 필드의 값을 변경하는 with() 메소드의 사용법 역시 LocalDate, LocalTime과 동일하다.

4. 사칙연산, 비교연산, 기타메서드

Period pe = Period.of(1, 12, 31); // 1년 12개월 31일 
Duration du = Duration.of(60, ChronoUnit.SECONDS); // 60초 

/* Period는 날짜의 기간을 표현하기 위한 것이므로 나눗셈을 위한 메서드는 없다. */

pe = pe.minusYears(1).multipliedBy(2); // 1년을 빼고, 2배를 곱한다.
du = du.plusHours(1).divideBy(60);     // 1시간을 더하고 60으로 나눈다.

/* 음수인지 확인하는 isNegative()와 0인지 확인하는 isZero() */
 
boolean sameDate = Period.between(date1, date2).isZero();
boolean isBefore = Duration.between(time1, time2).isNegative();

/* 부호를 반대로 변경하는 nagate()와 부호를 없애는 abs() */
du = du.abs();

/* Period는 abs() 가 없어서 아래와 같이 해야한다. */
if(pe.isNegative(0){
	pe = pe.nagate();
}

/* 넘어가는 월 계산을 하는 normalized() , 일은 변경하지 않는다. */

pe = Period.of(1,13,32).normalized(); // 1년 13개월 32일 -> 2년 1개월 32일

5. 다른 단위로 변환 - toTotalMonths(), toDays(), toHours(), toMinutes()

클래스 메서드 설명
Period long toTotalMonths() 년월일을 월단위로 변환해서 반환(일 단위는 무시)
Duration long toDays() 일단위로 변환해서 반환
long toHours() 시간단위로 변환해서 반환
long toMinutes() 분단위로 변환해서 반환
long toMillis() 천분의 일초 단위로 변환해서 반환
long toNanos() 나노초 단위로 변환해서 반환

 

 

728x90
반응형