[07] 날짜와 시간 API

MIIIN·2023년 5월 5일
0

Modern Java

목록 보기
7/8
post-thumbnail

🐢 기존 날짜 & 시간 API의 문제

기존 자바는 java.util.Date 클래스로 날짜와 시간 관련 기능을 제공하였다.

Date 클래스의 문제점

  1. 특정 시점을 날짜가 아닌 밀리초 단위로 표현
  2. 1900년을 기준으로 하는 Offset
  3. 0에서 시작하는 Month Index
  4. mutable (가변) 클래스
// 출력 결과 : Thu Sep 21 00:00:00 KST 2017
Date date = new Date(117, 8, 21);

그 후, 대안으로 나온 java.util.Calendar 클래스도 가변 클래스이며 0으로 시작하는 Month Index는 문제점으로 계속 남아있었다.

DateFormat 클래스는 언어의 종류와 독립적으로 날짜와 시간의 형식을 조절하고 파싱할 때 사용한다.

DateFormat 클래스의 문제점

  1. Date 클래스에서만 작동되는 일부 메서드
  2. Thread에 안전하지 않음

자바 기본 API의 문제점들로 많은 Java 개발자들은 Joda-Time 같은 Third-party Library를 사용하였다.


👻 java.time 패키지

Java SE 8에서는 Joda-Time Library의 많은 기능을 java.time 패키지로 추가하였다.

LocalDate 클래스

LocalDate 인스턴스는 시간을 제외한 날짜를 표현하는 immutable (불변) 객체이며, 어떤 시간대 정보도 포함하지 않는다.
LocalDate 인스턴스는 정적 팩토리 메서드 of로 만들 수 있다.

LocalDate date = LocalDate.of(2017, 9, 21);
int year = date.getYear();
Month month = date.getMonth();
int day = date.getDayOfMonth();
DayOfWeek dow = date.getDayOfWeek();
int len = date.lengthOfMonth();
boolean leap = date.isLeapYear();

팩토리 메서드 now는 시스템 시계의 정보를 이용해서 현재 날짜 정보를 얻는다.

LocalDate today = LocalDate.now();

LocalTime 클래스

LocalTime 인스턴스는 시간을 표현하는 immutable 객체이다.

LocalTime time = LocalTime.of(13, 45, 20);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();

LocalDateTime 클래스

LocalDateTime은 LocalDate와 LocalTime을 쌍으로 갖는 복합 클래스이며, 날짜와 시간을 모두 표현할 수 있다.

// 2017-09-21T13:45:20
LocalDateTime dt1 = LocalDateTime.of(2017, Month.SEPTEMBER, 21, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);

LocalDateTime의 toLocalDate나 toLocaltime 메서드로 LocalDate나 LocalTime 인스턴스를 추출할 수 있다.

LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();

Instant 클래스

Instant 클래스에서는 기계적인 관점에서 시간을 표현하며, Unix Epoch Time (1970년 1월 1일 0시 0분 0초 UTC) 을 기준으로 특정 지점까지의 시간을 초로 표현한다.

팩토리 메서드 ofEpochSecond에 초를 넘겨줘서 Instant 클래스 인스턴스를 만들 수 있으며, nano second(10억분의 1총)의 정밀도를 제공한다.

Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3, 0);
Instant.ofEpochSecond(2, 1_000_000_000);
Instant.ofEpochSecond(4, -1_000_000_000);

Instant 클래스도 사람이 확인할 수 있도록 시간을 표시해주는 정적 팩토리 메서드 now를 제공한다.

Instant.now();

Duration & Period 클래스

MethodStaticDescription
betweenYes두 시간 사이의 간격을 생성
fromYes시간 단위로 간격을 생성
ofYes주어진 구성 요소에서 간격 인스턴스를 생성
parseYes문자열을 파싱해서 간격 인스턴스를 생성
getNo현재 간격 정보값을 읽음
isNegativeNo간격이 음수인지 확인
isZeroNo간격이 0인지 확인
addToNo현재값의 복사본을 생성한 다음에 지정된 Temporal 객체에 추가
plusNo현재값에 주어진 시간을 더한 복사본을 생성
minusNo현재값에서 주어진 시간을 뺀 복사본을 생성
multipliedByNo현재값에서 주어진 시간을 곱한 복사본을 생성
negatedNo주어진 값의 부호를 반전한 복사본을 생성
subtractFromNo지정된 Temporal 객체에서 간격을 뺌

Duration 클래스는 정적 팩토리 메서드 between으로 두 시간 객체 사이의 지속시간을 만들 수 있다.

// LocalTime, LocalDateTime, Instant
Duration d1 = Duration.between(time1, time2);
Duration d2 = Duration.between(dateTime1, dateTime2);
Duration d3 = Duration.between(instant1, instant2);

Duration 클래스는 초와 나노초로 시간 단위를 표현하므로 between 메서드에 LocalDate를 전달할 수 없다.

(년, 월, 일) 로 시간을 표현할 때는 Period 클래스를 사용한다.

Period tenDays = Period.between(LocalDate.of(2017, 9, 11), LocalDate.of(2017, 9, 21));

💊 날짜 조정, Parsing, Formatting

MethodStaticDescription
fromYes주어진 Temporal 객체를 이용해서 클래스의 인스턴스를 생성
nowYes시스템 시계롤 Temporal 객체를 생성
ofYes주어진 구성 요소에서 Temporal 객체의 인스턴스를 생성
parseYes문자열을 파싱해서 Temporal 객체를 생성
atOffsetNo시간대 Offset과 Temporal 객체를 합침
atZoneNo시간대 Offset과 Temporal 객체를 합침
formatNo지정된 Formatter를 이용해서 Temporal 객체를 문자로 변환
getNoTemporal 객체의 상태를 읽음
plusNo특정 시간을 더한 Temproral 객체의 복사본을 생성
minusNo특정 시간을 뺀 Temproral 객체의 복사본을 생성
withNo일부 상태를 바꾼 Temproral 객체의 복사본을 생성
LocalDate date1 = LocalDate.of(2017, 9, 21);

// with : 절대적인 방식으로 속성 바꾸기
// 2011-09-21
LocalDate date2 = date1.withYear(2011);
// 2011-09-25
LocalDate date3 = date2.withDayOfMonth(25);
// 2011-02-25
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 2);

// plus, minus : 상대적인 방식으로 속성 바꾸기
// 2017-09-28
LocalDate date5 = date1.plusWeeks(1);
// 2011-09-28
LocalDate date6 = date5.minusYears(6);
// 2012-03-28
LocalDate date7 = date6.plus(6, ChronoUnit.MONTHS);

TemporalAdjusters

TemporalAdjusters에서 정의하는 정적 팩토리 매서드로, 다양한 상황에서 사용할 수 있는 다양한 TemporalAdjuster를 제공한다.

MethodDescription
dayOfWeekInMonth(서수, 요일)에 해당하는 날짜를 반환 (음수를 사용하면 월의 끝에서 거꾸로 계산)
firstDayOfMonth현재 달의 첫번째 날짜를 반환
firstDayOfNextMonth다음 달의 첫번째 날짜를 반환
firstDayOfYear올해의 첫 번째 날짜를 반환
firstDayOfNextYear내년의 첫 번째 날짜를 반환
firstInMonth현재 달의 첫 번째 요일에 해당하는 날짜를 반환
lastDayOfMonth현재 달의 마지막 날짜를 반환
lastDayOfYear올해의 마지막 날짜를 반환
lastInMonth현재 달의 마지막 요일에 해당하는 날짜를 반환
next현재 달에서 현재 날짜 이후로 지정한 요일이 처음으로 나타나는 날짜를 반환
nextOrSame현재 달에서 현재 날짜 이후로 지정한 요일이 처음으로 나타나는 날짜를 반환 (현재 날짜도 포함)
previous현재 달에서 현재 날짜 이전으로 지정한 요일이 처음으로 나타나는 날짜를 반환
previousOrSame현재 달에서 현재 날짜 이전으로 지정한 요일이 처음으로 나타나는 날짜를 반환 (현재 날짜도 포함)
LocalDate date1 = LocalDate.of(2014, 3, 18);

// 2014-03-23
LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
// 2014-03-31
LocalDate date3 = date2.with(TemporalAdjusters.lastDayOfMonth());

Custom TemporalAdjuester

TemporalAdjuester 인터페이스를 구현하여 Custom TemporalAdjuster를 만들 수 있다.

TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuester(
	temporal -> {
		DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK);
		int dayToAdd = 1;
		if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;
		else if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
		return temporal.plus(dayToAdd, ChronoUnit.DAYS);
	});

LocalDate nextDay = date.with(nextWorkingDay);

DateTimeFormatter

Formatting과 Parsing 전용 패키지인 java.time.format이 추가되었으며, 이 패키지 중 DateTimeFormmater 클래스가 있다.
DateTimeFormatter 클래스는 BASIC_ISO_DATE와 ISO_LOCAL_DATE 등의 상수를 미리 정의하고 있으며, 날짜나 시간을 특정 형식의 문자열로 만들 수 있다.

LocalDate date = LocalDate.now();
// yyyyMMdd -> 20230422
String d1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
// yyyy-MM-dd -> 2023-04-22
String d2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
// yyyy-MM-dd -> 2023-04-22
String d3 = date.format(DateTimeFormatter.ISO_DATE);
// yyyy-DAYOFYEAR -> 2023-112
String d4 = date.format(DateTimeFormatter.ISO_ORDINAL_DATE);
// yyyy-WEEK-DOW -> 2023-W16-6
String d5 = date.format(DateTimeFormatter.ISO_WEEK_DATE);

LocalTime time = LocalTime.now();
// HH:mm:ss -> 23:52:23.030569
String t1 = time.format(DateTimeFormatter.ISO_LOCAL_TIME);
// HH:mm:ss -> 23:52:23.030569
String t2 = time.format(DateTimeFormatter.ISO_TIME);

LocalDateTime dateTime = LocalDateTime.now();
// yyyy-MM-ddTHH:mm:ss -> 2023-04-22T23:52:23.0315801
String dt1 = dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
// yyyy-MM-ddTHH:mm:ss -> 2023-04-22T23:52:23.0315801
String dt2 = dateTime.format(DateTimeFormatter.ISO_DATE_TIME);

OffsetDateTime offsetDateTime = OffsetDateTime.now();
// yyyy-MM-dd+TIMEZONE -> 2023-04-22+09:00
String odt1 = offsetDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE);
// HH:mm:ss+TIMEZONE -> 23:52:23.032563+09:00
String odt2 = offsetDateTime.format(DateTimeFormatter.ISO_OFFSET_TIME);
// yyyy-MM-ddTHH:mm:dd+TIMEZONE -> 2023-04-22T23:52:23.032563+09:00
String odt3 = offsetDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);

ZonedDateTime zdt = ZonedDateTime.now();
// yyyy-MM-ddTHH:mm:ss+TIMEZONE[AREA] -> 2023-04-22T23:52:23.032563+09:00[Asia/Seoul]
String zdt1 = zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
// yyyy-MM-ddTHH:mm:ssZ -> 2023-04-22T14:52:23.032563Z
String zdt2 = zdt.format(DateTimeFormatter.ISO_INSTANT);

또한, 특정 패턴으로 포매터로 문자열을 만들거나 문자열을 파싱하여 다시 날짜를 생성할 수 있다.

LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
// format 메서드로 특정 패턴의 문자열 생성 -> 23/04/2023
String text = date.format(formatter);
// parse 메서드로 문자열을 파싱하여 LocalDate 객체 생성 -> 2023-04-23
LocalDate parsedDate = LocalDate.parse(text, formatter);

DatetimeFormatterBuilder 클래스로 복합적인 포매터를 정의해서 좀 더 세부적으로 포매터를 제어할 수 있다.

DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
		.appendText(ChronoField.DAY_OF_MONTH)
		.appendLiteral(". ")
		.appendText(ChronoField.MONTH_OF_YEAR)
		.appendLiteral(" ")
		.appendText(ChronoField.YEAR)
		.parseCaseInsensitive()
		.toFormatter(Locale.ITALIAN);
// d. MMMM yyyy -> 23. aprile 2023
String italianText = date.format(italianFormatter);

⌚ TimeZone

기존의 java.util.TimeZone을 대체하는 java.time.ZoneId 클래스가 추가되었다.
ZoneId 클래스를 사용하면 서머타임(DST) 같은 복잡한 사항이 자동으로 처리되며, Immutable(불변) 클래스이다.

표준 시간이 같은 지역을 묶어서 Time Zone 규칙 집합을 정의하며, ZoneId 클래스의 getRules() 메서드를 이용해서 해당 시간대의 규정을 획득 할 수 있다. 지역 ID는 '{지역}/{도시}' 형식으로 이루어지며 IANA Time Zone Database에서 제공하는 지역 집합 정보를 사용한다.

ZoneId romeZone = ZoneId.of("Europe/Rome");
ZoneId zoneId = TimeZone.getDefault().toZoneId();

LocalDate date = LocalDate.now();
ZonedDateTime zdt1 = date.atStartOfDay(romeZone);

LocalDateTime dateTime = LocalDateTime.now();
ZonedDateTime zdt2 = dateTime.atZone(romeZone);

Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(romeZone);

-- ZoneId를 이용한 Instant에서 LocalDateTime으로 변환
LocalDateTime timeFromeInstant = LocalDateTime.ofInstant(instant, romeZone);

profile
백엔드개발자

0개의 댓글