백기선 님의 <더 자바, Java 8>를 보고 공부한 내용을 기록합니다.
1. 배경
•
그동안 우리는 다양한 방법으로 시간과 날짜를 사용해왔다. 하지만 각각의 방법들은 나름대로 문제점들이 있었다.
•
java.util.Date
◦
클래스 이름이 명확하지 않았다. Date인데 시간까지 다룬다.
◦
thread safe 하지 않다.
▪
mutable 하기 때문에 setDate() 등으로 날짜를 마구 변경 가능
•
java.util.Calendar
◦
타입 안정성이 없어서 버그가 발생할 여지가 높다.
▪
월은 0부터 시작하고 day 는 1부터 시작한다.
•
◦
그래서 자바8 이전까지는 대부분의 날짜 시간 처리가 복잡한 어플리케이션에서 보통 joda-time API 를 사용해왔다.
멀티 쓰레드 환경에서 Date 객체에 동시에 접근하여 변경하는 경우를 보여준다.
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.12.2</version>
</dependency>
XML
복사
public class BeforeJava8Test { @Test void DateApiTest() { Date date = new Date(); System.out.println("date = " + date); //Thu Mar 16 14:16:15 KST 2023 long time = date.getTime(); System.out.println("time = " + time); //1678943775380 date.setTime(time - 1); //1678944050334 System.out.println("changed time = " + date.getTime()); //1678944050333 } @Test void CalendarApiTest() { Calendar calendar = new GregorianCalendar(-1000, 2, 15); //month는 0부터 시작하므로 숫자를 바로 쓸 수 없음 assertThat(calendar.getWeekYear()).isEqualTo(-1000); Calendar calendar2 = new GregorianCalendar(2002, Calendar.FEBRUARY, 15); System.out.println(calendar2); } @Test void jodaTimeApiTest() { DateTime dateTime = new DateTime(); assertThat(dateTime.getYear()).isEqualTo(2023); assertThat(dateTime.getMonthOfYear()).isEqualTo(3); assertThat(dateTime.getDayOfMonth()).isEqualTo(16); }}
Java
복사
2. Java8 부터 Date-Time API 의 등장
•
JSR-310 스펙의 구현체를 제공한다.
•
디자인 철학
◦
Clear : 명확한 이름
◦
Immutable : 불변
◦
Fluent : Null type 을 반환하지 않아서 chaining 형태로 계속 확장하여 사용 가능
◦
Extensible
2-1. 주요 API 특징
•
기계용 시간과 인류용 시간, 두 분류로 나눈다.
•
기계용 시간은 EPOCK 부터 현재까지의 타임스탬프를 표현한다.
◦
EPOCK : 리눅스가 시간을 처음 세기 시작했던 1970년 1월 1일 0시 0분 0초
•
인류용 시간은 우리가 흔히 사용하는 년, 월, 일, 시, 분, 초 등을 표현한다.
•
타임스탬프는 Instant 를 사용한다.
•
특정날짜(LocalDate), 시간(LocalTime), 일시(LocalDateTime)을 사용할 수 있다.
•
기간을 표현할 때는 Duration(시간기반), Period(날짜기반)을 사용할 수 있다.
•
DateTimeFormatter 를 사용해서 일시를 특정한 문자열로 포매팅할 수 있다.
2-2. 코드로 살펴보기
•
파싱 혹은 포메팅 : 미리 정의해둔 포맷 참고
•
LocalDateTime.parse(String, DateTimeFormatter);
public class AfterJava8Test { @Test void instant로_기계용_시간_표현하기() { System.out.println("Instant.now() = " + Instant.now()); System.out.println("Instant.now().atZone(ZoneId.of(\"UTC\")) = " + Instant.now().atZone(ZoneId.of("UTC"))); //Instant.now() = 2023-03-16T05:52:42.740808Z //Instant.now().atZone(ZoneId.of("UTC")) = 2023-03-16T05:52:42.753796Z[UTC] } @Test void zonedDateTIme으로_타임존_표현하기() { ZonedDateTime zonedDateTime = ZonedDateTime.now(); System.out.println("zonedDateTime = " + zonedDateTime); //2023-03-16T14:52:15.378961+09:00[Asia/Seoul] ZonedDateTime zonedDateTime2 = ZonedDateTime.now(ZoneId.systemDefault()); System.out.println("zonedDateTime2 = " + zonedDateTime2); //2023-03-16T14:52:15.383428+09:00[Asia/Seoul] } @Test void localDateTime으로_인류용_시간_표현하기() { LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("localDateTime = " + localDateTime); //localDateTime = 2023-03-16T14:53:32.503884 LocalDateTime localDateTime1 = LocalDateTime.of(2002, 2, 15, 11, 23, 30); System.out.println("localDateTime1 = " + localDateTime1); //localDateTime1 = 2002-02-15T11:23:30 ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime1, ZoneId.of("Asia/Seoul")); System.out.println("zonedDateTime = " + zonedDateTime); //zonedDateTime = 2002-02-15T11:23:30+09:00[Asia/Seoul] } @Test void period로_기간표현하기() { System.out.println("LocalDate.now() = " + LocalDate.now()); Period period = Period.between(LocalDate.of(1994, 8, 11), LocalDate.now()); System.out.println("period = " + period); //P-28Y-7M-5D System.out.println("period.getYears() = " + period.getYears()); //28 System.out.println("period.getMonths() = " + period.getMonths()); //7 Period period2 = Period.between(LocalDate.now(), LocalDate.of(2023, 8, 11)); System.out.println("period2.getYears() = " + period2.getYears()); //0 System.out.println("period2.getMonths() = " + period2.getMonths()); //4 System.out.println("period2.getDays() = " + period2.getDays()); //26 System.out.println("period2.get(ChronoUnit.DAYS) = " + period2.get(ChronoUnit.DAYS)); //26 } @Test void duration으로_기간표현하기() { Instant now = Instant.now(); Instant plus = now.plus(10, ChronoUnit.SECONDS); Duration duration = Duration.between(now, plus); assertThat(duration.getSeconds()).isEqualTo(10); } @Test void parsing하는법() { DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); System.out.println("LocalDate.now().format(dateTimeFormatter) = " + LocalDate.now().format(dateTimeFormatter)); //LocalDate.now().format(dateTimeFormatter) = 2023-03-16 LocalDate parsed = LocalDate.parse("1998-08-30", dateTimeFormatter); System.out.println("parsed = " + parsed); //parsed = 1998-08-30 (localDate) } @Test void legacyAPI지원() { ZoneId pst = TimeZone.getTimeZone("PST").toZoneId(); System.out.println("pst = " + pst); //America/Los_Angeles TimeZone legacyZoneAPI = TimeZone.getTimeZone(ZoneId.of("Asia/Seoul")); System.out.println("legacyZoneAPI = " + legacyZoneAPI); //legacyZoneAPI = sun.util.calendar.ZoneInfo[id="Asia/Seoul",offset=32400000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null] Instant newInstant = new Date().toInstant(); System.out.println("newInstant = " + newInstant); //newInstant = 2023-03-16T07:09:46.767Z Date legacyInstant = Date.from(newInstant); System.out.println("legacyInstant = " + legacyInstant); //legacyInstant = Thu Mar 16 16:09:46 KST 2023 }}
Java
복사
3. 참고
•
◦
java8 이전까지 날짜, 시간 관련 API 가 어떤 점이 문제인지 상세하게 설명