자바 날짜와 시간 라이브러리
LocalDate, LocalTime, LocalDateTime
- LocalDate: 날짜만 표현할 때 사용한다. 년, 월, 일을 다룬다. 예)2014-12-12
- LocalTim: 시간만 표현할 때 사용한다. 시,분,초를 다룬다. 예) 08:20:30.213
- LocalDateTime: LocalDate와 LocalTime을 합한 개념이다. 예) 2014-12-12T08:20:30.213
- 앞에 Local이 붙는 이유는 세계 시간대를 고려하지 않아 타임존이 적용되지 않기 때문이다.
- 특정 지역의 날짜와 시간만 고려할 때 사용한다.
ZonedDateTime, OffsetDateTime
- ZonedDateTime: 시간대를 고려한 날짜와 시간을 표현할 때 사용한다. 여기에는 시간대를 표현하는 타임존이 포함된다.
- 예) 2013-11-21T08:20:30.213+9:00[Asia/Seoul]
- +9:00은 UTC(협정 세계시)로 부터의 시간대 차이이다. 오프셋이라 한다. 한국은 UTC보다 +9:00 시간
- Asia/Seoul ` 은 타임존이라 한다. 이 타임존을 알면 오프셋과 일광 절약 시간제에 대한 정보를 알 수 있다. 일광 절약 시간제가 적용된다.
- OffsetDateTime : 시간대를 고려한 날짜와 시간을 표현할 때 사용한다. 여기에는 타임존은 없고, UTC로 부터 의 시간대 차이인 고정된 오프셋만 포함한다.
- 예) 2013-11-21T08:20:30.213+9:00
- 일광 절약 시간제가 적용되지 않는다.
- ZonedDateTime은 시간대를 고려해야 할 때 실제 사용하는 날짜와 시간 정보를 나타내는데 더 적합하고,
OffsetDateTime은 UTC로부터의 고정된 오프셋만 고려해야 할 때 유용하다.
Year, Month, YearMonth, MonthDay
- 년, 월, 년월, 달일을 각각 다룰 때 사용한다. 자주 사용하지는 않는다.
- DayOfWeek 같이 월, 화, 수, 목, 금, 토, 일을 나타내는 클래스도 있다.
Instant
- Instant 는 UTC(협정 세계시)를 기준으로 하는, 시간의 한 지점을 나타낸다. Instant 는 날짜와 시간을 나노초 정밀도로 표현하며, 1970년 1월 1일 0시 0분 0초(UTC)를 기준으로 경과한 시간으로 계산된다.
- 쉽게 이야기해서 Instant 내부에는 초 데이터만 들어있다. (나노초 포함) 따라서 날짜와 시간을 계산에 사용할 때는 적합하지 않다.
Period, Duration
시간의 개념은 크게 2가지로 표현할 수 있다.
- 특정 시점의 시간(시각)
- 이 프로젝트는 2013년 8월 16일 까지 완료해야해
- 다음 회의는 11시 30분에 진행한다.
- 내 생일은 8월 16일이야.
- 시간의 간격(기간)
- 앞으로 4년은 더 공부해야 해
- 이 프로젝트는 3개월 남았어
- 라면은 3분 동안 끓어야 해
- Period, Duration
Period
두 날짜 사이의 간격을 년, 월, 일 단위로 나타낸다.
Duration
두 시간 사이의 간격을 시, 분, 초(나노초) 단위로 나타낸다
기본 날짜와 시간 - LocalDateTime
생성
- now(): 현재 날짜와 시간으 기준으로 생성한다.
- of( . . . ): 특정 날짜와 시간을 기준으로 생성한다.
분리
날자(LocalDate)와 시간(LocalTime)을 toXxx()메서드로 분리할 수 있다.
합체
LocalDateTime.of(localDate, localTime)
날짜와 시간을 사용해야 LocalDateTime을 만든다
계산
- plusXxx(): 특정 날짜와 시간을 더한다. 다양한 plusXxx() 메서드가 존재한다.
비교
- isBefore(): 다른 날짜시간과 비교한다. 현재 날짜와 시간이 이전이라면 true를 반환
- isAfter(): 다른 날짜시간과 비교한다. 현재 날짜와 시간이 이후라면 true를 반환
- isEqual(): 다른 날짜시간과 비교한다. 현재 날짜와 시간이 같다면 true를 반환
타임존 - ZonedDateTime
생성
Zonid.systemDefault(): 시스템이 사용하는 기본 ZoneId를 반환한다.
ZoneId.of(): 타임존을 직접 제공해서 ZoneId를 반환한다.
ZoneId는 내부에 일광 절약 시간 관련 정보, UTC와의 오프셋 정보를 포함하고 있다.
ZonedDateTime
LocalDateTime + ZoneId
예) 2013-11-21T08:20:30.213+9:00[Asia/Seoul]
생성
- now(): 현재 날짜와 시간을 기준으로 생성한다. 이때 ZoneId는 현재 시스템을 따른다. (ZoneId.systemDefault())
- of(. . . ): 특정 날짜와 시간을 기준으로 생성한다. ZoneId를 추가해야 한다.
타임존 변경
- withZoneSameInstant(ZoneId): 타임존을 변경한다. 타임존에 맞추어 시간도 함께 변경된다.
OffsetDateTime
: LocalDateTime + ZoneOffSetTime(UTC와의 시간 차이)
ZonedDateTime vs OffsetDateTime
- zonedDateTime은 구체적인 지역 시간대를 다룰 때 사용하며, 일광 절약 시간을 자동으로 처리할 수 있다. 사용자 지정 시간대에 따른 시간 계산이 필요할 때 적합하다.
- OffsetDateTime은 UTC와의 시간 차이만을 나타낼 때 사용하며, 지역 시간대의 복잡성을 고려하지 않는다. 시간대 변환이 없이 로그릴 기록하고, 데이터를 저장하고 처리할 때 적합하다.
기계 중심의 시간 - Instant
: Instant는 UTC를 기준으로 하는, 시간의 한 지점을 나타낸다. 내부에는 초 데이터만 들어있다. (나노초 포함)
Intant 특징
장점
- 시간대 독립성: Instant는 UTC를 기준으로 하므로, 시간대 영향을 받지 않는다.
- 고정된 기준점: 모든 Instant는 1970년 1월 1일 UTC를 기준으로 하기 때문에 일관된다.
단점
- 사용자 친화적이지 않음: 사람이 읽고 이해하기 직관적이지 않다.
- 시간대 정보 부재: 특정 지역의 날짜와 시간으로 변환하려면 추가 작업이 필요
사용 예
- 전 세계적인 시간 기준 필요 시
- 시간대 변환 없이 시간 계산 필요 시
- 데이터 저장 및 교환
생성
- now(): UTC를 기준 현재 시간의 Instant를 생성한다.
- from(): 다른 타입의 날짜와 시간을 기준으로 Instant를 생성한다. 참고로 Istant는 UTC를 기준으로 하기 때문에 시간대 정보가 필요하다. 따라서 LocalDateTime은 사용할 수 없다.
- ofEpochSecond(): 에포크 시간을 기준으로 Instatnt를 생성한다. 0초를 생성하면 에포크 시간인 1970년 1월 1일 0시 0분 0초로 생성된다.
참고 - Epoch 시간
Epoch time(에포크 시간), 또는 Unix timestamp는 컴퓨터 시스템에서 시간을 나타내는 방법 중 하나이다. 이는 1970년 1월 1일 00:00:00 UTC부터 현재까지 경과된 시간을 초 단위로 표현한 것이다. 즉, Unix 시간은 1970년 1월 1일 이후로 경과한 전체 초의 수로, 시간대에 영향을 받지 않는 절대적인 시간 표현 방식이다. 참고로 Epoch라는 뜻은 어떤 중요한 사건이 발생한 시점을 기준으로 삼는 시작점을 뜻하는 용어다. ` Instant` 는 바로 이 Epoch 시간을 다루는 클래스이다
계산
- plusSeconds(): 초를 더한다. 초, 밀리초, 나노초 정도만 더하는 간단한 메서드가 제공된다.
조회
- getEpochSecond(): 에포크 시간인 UTC 1970년 1월 1일 0시 0분 0초를 기준으로 흐른 초를 반환한다.
정리
- 조금 특별한 시간, 기계 중심, UTC 기준
- 에포크 시간으로부터 흐른 시간을 초 단위로 저장
- 전세계 모든 서버 시간을 똑같이 맞출 수 있음
- 서버로그 epoch 시간 기반 계산이 필요할 때
- 단점: 초 단위의 간단한 연산은 가능하지만 복잡한 연산은 못함
- 대안: 날짜 계산이 필요하면 LocalDateTime 또는, ZoneIdDateTime 사용
기간, 시간의 간격 - Duration, Period
Period
package time;
import java.time.LocalDate;
import java.time.Period;
public class PeriodMain {
public static void main(String[] args) {
//생성
Period period = Period.ofDays(10);
System.out.println("period = " + period);
//계산에 사용
LocalDate currentDate = LocalDate.of(2030, 1, 1);
LocalDate plusDate = currentDate.plus(period);
System.out.println("currentDate = " + currentDate);
System.out.println("plusDate = " + plusDate);
//기간 차이
LocalDate startDate = LocalDate.of(2024, 1, 1);
LocalDate endDate = LocalDate.of(2024, 5, 5);
Period between = Period.between(startDate, endDate);
System.out.println("between = " + between);
System.out.println("기간: " + between.getMonths() + "개월, " + between.getDays() + "일");
}
}
//결과
period = P10D
currentDate = 2030-01-01
plusDate = 2030-01-11
between = P4M4D
기간: 4개월, 4일
Duration
package time;
import java.time.Duration;
import java.time.LocalTime;
public class DurationMain {
public static void main(String[] args) {
Duration duration = Duration.ofMinutes(30);
System.out.println("duration = " + duration);
LocalTime lt = LocalTime.of(1, 0);
System.out.println("lt = " + lt);
//계산에 사용
LocalTime plusTime = lt.plus(duration);
System.out.println("더한 시간: " + plusTime);
//시간 차이
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(10, 0);
Duration between = Duration.between(start, end);
System.out.println("차이: " + between.getSeconds()+"초");
System.out.println("근무 시간: " + between.toHours() + "시간, " + between.toMinutes() + "분");
}
}
//결과
duration = PT30M
lt = 01:00
더한 시간: 01:30
차이: 3600초
근무 시간: 1시간, 60분
날짜와 시간
핵심 인터페이스
- 특정 시점의 시간: Temporal(TeporalAccessor 포함) 인터페이스를 구현한다.
- 구현으로 LocalDateTime, LocalDate, LocalTime, ZoneIdDateTime, OffsetDateTime, Istant 등이 있다.
- 시간의 간격(기간): TemporalAmount 인터페이스를 구현한다.
- 구현으로 Period, Duration이 있다.
시간의 단위와 시간 필드
시간의 단위 - TemporalUnit(ChronoUnit)
- TemporalUnit 인터페이스는 날짜와 시간을 측정하는 단위를 나타내며, 주로 사용되는 구현체는 java.time.tamporal.ChronoUnit 열거형으로 구현되어 있다.
- ChronoUnit은 다양한 시간 단위를 제공한다.
package time.ChronoUnit;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
public class ChronoUnitMain {
public static void main(String[] args) {
ChronoUnit[] values = ChronoUnit.values();
for (ChronoUnit value : values) {
System.out.println("value = " + value);
}
System.out.println("HOUR = " + ChronoUnit.HOURS);
System.out.println("HOURS.duration = " + ChronoUnit.HOURS.getDuration().getSeconds());
System.out.println("DAYS = " + ChronoUnit.DAYS);
System.out.println("DAYS.duration = " + ChronoUnit.DAYS.getDuration().getSeconds());
//차이 구하기
LocalTime lt1 = LocalTime.of(1, 10, 0);
LocalTime lt2 = LocalTime.of(1, 20, 0);
long secondBetween = ChronoUnit.SECONDS.between(lt1, lt2);
System.out.println("secondBetween = " + secondBetween);
long minutesBetween = ChronoUnit.MINUTES.between(lt1, lt2);
System.out.println("minutesBetween = " + minutesBetween);
}
}
결과
value = Nanos
value = Micros
.
.
.
value = Millennia
value = Eras
value = Forever
HOUR = Hours
HOURS.duration = 3600
DAYS = Days
DAYS.duration = 86400
secondBetween = 600
minutesBetween = 10
시간 필드 - TemporalField(ChronoField)
- 날짜 및 시간을 나타내는 데 사용되는 열거형이다. 다양한 필드를 통해 날짜와 시간의 특정 부분을 나타낸다.
- TemporalFiel 인터페이스는 날짜와 시간을 나타내는데 사용된다. 주로 사용되는 구현체는 java.time.temporal.ChronoField 열거형으로 구현되어 있다.
- ChronoField는 다양한 필드를 통해 날짜와 시간이 특정 부분을 나타낸다. 여기에는 연도, 월, 일, 시간 , 분들이 포함된다.
package time.ChronoUnit;
import java.time.temporal.ChronoField;
public class ChronoFieldMain {
public static void main(String[] args) {
ChronoField[] values = ChronoField.values();
for (ChronoField value : values) {
System.out.println(value + ", range = " + value.range());
System.out.println("MONTH_OF_YEAR.range() = " + ChronoField.MONTH_OF_YEAR.range());
System.out.println("DAY_OF_MONTH.range() = " + ChronoField.DAY_OF_MONTH.range());
}
}
}
결과
NanoOfSecond, range = 0 - 999999999
MONTH_OF_YEAR.range() = 1 - 12
DAY_OF_MONTH.range() = 1 - 28/31
NanoOfDay, range = 0 - 86399999999999
.
.
.
DAY_OF_MONTH.range() = 1 - 28/31
OffsetSeconds, range = -64800 - 64800
MONTH_OF_YEAR.range() = 1 - 12
DAY_OF_MONTH.range() = 1 - 28/31
날짜와 시간 조작하기
조회
package time;
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
public class GetTimeMain {
public static void main(String[] args) {
LocalDateTime ldt = LocalDateTime.of(2030, 1, 1, 13, 30, 59);
System.out.println("YEAR = " + ldt.get(ChronoField.YEAR));
System.out.println("MONTH_OF_YEAR = " + ldt.get(ChronoField.MONTH_OF_YEAR));
System.out.println("DAY_OF_MONTH = " + ldt.get(ChronoField.DAY_OF_MONTH));
System.out.println("HOUR_OF_DAY = " + ldt.get(ChronoField.HOUR_OF_DAY));
System.out.println("MINUTE_OF_HOUR = " + ldt.get(ChronoField.MINUTE_OF_HOUR));
System.out.println("편의 메서드 제공");
System.out.println("YEAR = " + ldt.getYear());
System.out.println("MONTH_OF_YEAR = " + ldt.getMonthValue());
System.out.println("DAY_OF_MONTH = " + ldt.getDayOfMonth());
System.out.println("HOUR_OF_DAY = " + ldt.getHour());
System.out.println("MINUTE_OF_HOUR = " + ldt.getMinute());
System.out.println("편의 메서드에 없음");
System.out.println("MINUTE_OF_DAY = " + ldt.get(ChronoField.MINUTE_OF_DAY));
System.out.println("SECOND_OF_DAY = " + ldt.get(ChronoField.SECOND_OF_DAY));
}
}
- TemporalAccessor.get(TemporalField field)
- 편의 메서드가 가독성이 좋기 때문에 일반적으로 dt.getXxx()를 사용한다.
조작
package time;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.temporal.ChronoUnit;
public class ChangeTimePlusMain {
public static void main(String[] args) {
LocalDateTime ldt = LocalDateTime.of(2024, 1, 1, 13, 30, 59);
System.out.println("ldt = " + ldt);
LocalDateTime plusDt1 = ldt.plus(10, ChronoUnit.YEARS);
System.out.println("plusDt1 = " + plusDt1);
LocalDateTime plusDt2 = ldt.plusYears(10);
System.out.println("plusDt2 = " + plusDt2);
Period period = Period.ofYears(10);
LocalDateTime plusDt3 = ldt.plus(period);
System.out.println("plusDt3 = " + plusDt3);
}
}
Temporal plus(long amountToAdd, TemporalUnit unit)
- 불변이므로 반환 값을 받아야 한다.
- minus()도 존재
- 편의 메서드 -> dt.plusYears()
정리
- 시간을 조죄하고 조작하는 부분은 TemporalAccessor.get(), Temporal.plus() 와 같은 인터페이스를 통해 특정 구현 클래스와 무관하게 일관성 있는 조회 조작 기능을 제공한다.
- 하지만 모든 시간 필드를 다 조회할 수는 없다. 이는 boolean isSupported(TemporalField field) 와 boolean isSupported(TemporalUnit unit) 메서드를 통해 확인할 수 있다.
with()
package time;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
public class ChangeTimeWithMain {
public static void main(String[] args) {
LocalDateTime dt = LocalDateTime.of(2019, 1, 1, 13, 39, 59);
System.out.println("dt = " + dt);
LocalDateTime changeDt1 = dt.with(ChronoField.YEAR, 2020);
System.out.println("changeDt1 = " + changeDt1);
LocalDateTime changeDt2 = dt.withYear(2020);
System.out.println("changeDt2 = " + changeDt2);
//TemporalAdjuster 사용
//다음주 금요일
LocalDateTime with1 = dt.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println("기준 날짜 = " + dt);
System.out.println("다음주 금요일 = " + with1);
//이번 달의 마지막 일요일
LocalDateTime with2 = dt.with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));
System.out.println("기준 날짜 = " + dt);
System.out.println("이번 달의 마지막 일요일 = " + with2);
}
}
Temporal with(TemporalField, long newValue)
temporal.with()를 사용하면 특정 날짜와 시간의 특정 필드의 값만 변경할 수 있다.
불변이므로 반환 값을 받아야한다.
편의 메서드
dt.withYear()
TemporalAdjuster()
with()는 단순한 날짜변경만 가능하다. 복잡한 날짜계산은 temporalAdjuster를 사용하자.
날짜와 시간 문자열 파싱과 포맷팅
포맷팅: 날짜와 시간 데이터를 원하는 포맷의 문자열로 변경하는것, Date -> String
파싱: 문자열을 날짜와 시간 데이터로 변경하는 것, String -> Date
package time;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class FormattingMain1 {
public static void main(String[] args) {
// 포맷팅: 날짜를 문자로
LocalDate date = LocalDate.of(2024, 12, 31);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
String formattedDate = date.format(formatter);
System.out.println("날짜와 시간 포맷팅: " + formattedDate);
// 파싱: 문자를 날짜로
String input = "2030년 01월 01일";
LocalDate parsedDate = LocalDate.parse(input, formatter);
System.out.println("문자열 파싱 날짜와 시간: " + parsedDate);
}
}
출처 - 김영한의 실전 자바 중급 1편
'Backend > Java' 카테고리의 다른 글
중첩 클래스, 내부 클래스 - 2 (0) | 2024.08.13 |
---|---|
중첩 클래스, 내부 클래스 (0) | 2024.08.11 |
열거형 - ENUM (0) | 2024.08.09 |
Class, System 클래스 (0) | 2024.08.08 |
래퍼 클래스 (0) | 2024.08.08 |