일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- bean
- 알고리즘
- 탐욕법
- 자료구조
- Spring
- MySQL
- LifeCycle
- dfs
- JWT
- java
- nestjs
- winston
- typescript
- REST API
- 프로그래머스
- Interceptor
- Deep Dive
- TIL
- 인접리스트
- GraphQL
- puppeteer
- Linux
- 인접행렬
- html
- node.js
- OOP
- javascript
- css
- 코딩테스트
- Kubernetes
- Today
- Total
처음부터 차근차근
[Java] 날짜와 시간 본문
날짜와 시간 라이브러리가 필요한 이유
우리가 프로그래밍을 하면서, 로그를 남기거나, 혹은 회원 생성 시간 및 게시글 생성 시간 등 날짜와 시간에 대한 기록이 필요하다.
단순히 날짜와 시간만 기록하면 되는게 아닌가? 싶지만, 날짜와 시간을 계산하는 것은 의외로 복잡하다.
- 날짜와 시간 차이 계산
- 윤년 계산
- 일광 절약 시간 변환
- 타임존 계산
이를 위해서 Java에서는 라이브러리를 제공하고 있다.
Java 날짜와 시간 라이브러리
- *: 초는 나노초 단위의 정밀도로 캡처된다. (밀리초, 나노초 가능)
- **: 이 클래스는 이 정보를 저장하지는 않지만 이러한 단위로 시간을 제공하는 메서드가 있다.
- ***:
ZonedDateTime
에Period
를 추가하면 서머타임 또는 기타 현지 시간 차이를 준수한다.
LocalDate, LocalTime, LocalDateTime
- LocalDate: 날짜만 표현할 때 사용한다. 년, 월, 일을 다룬다. 예)
2013-11-21
- LocalTime: 시간만을 표현할 때 사용한다. 시, 분, 초를 다룬다. 예)
08:20:30.213
- 초는 밀리초, 나노초 단위도 포함할 수 있다.
- LocalDateTime:
LocalDate
와LocalTime
을 합한 개념이다. 예)2013-11-21T08:20:30.213
**Method
now()
: 현재 시간을 기준으로 생성한다.of(...)
: 특정 날짜를 기준으로 생성한다. 년, 월, 일을 입력할 수 있다.plusDays()
: 특정 일을 더한다. 다양한plusXxx()
메서드가 존재한다.- isBefore(): 다른 날짜시간과 비교한다. 현재 날짜와 시간이 이전이라면
true
를 반환한다. - isAfter(): 다른 날짜시간과 비교한다. 현재 날짜와 시간이 이후라면
true
를 반환한다. - isEqual(): 다른 날짜시간과 시간적으로 동일한지 비교한다. 시간이 같으면
true
를 반환한다.
isEqual() vs equals()
isEqual()
는 단순히 비교 대상이 시간적으로 같으면true
를 반환한다. 객체가 다르고, 타임존이 달라도 시간적으로 같으면true
를 반환한다. 쉽게 이야기해서 시간을 계산해서 시간으로만 둘을 비교한다.- 예) 서울의 9시와 UTC의 0시는 시간적으로 같다. 이 둘을 비교하면
true
를 반환한다.
- 예) 서울의 9시와 UTC의 0시는 시간적으로 같다. 이 둘을 비교하면
equals()
객체의 타입, 타임존 등등 내부 데이터의 모든 구성요소가 같아야true
를 반환한다.- 예) 서울의 9시와 UTC의 0시는 시간적으로 같다. 이 둘을 비교하면 타임존의 데이터가 다르기 때문에
false
를 반환한다.
- 예) 서울의 9시와 UTC의 0시는 시간적으로 같다. 이 둘을 비교하면 타임존의 데이터가 다르기 때문에
모든 날짜 클래스는 불변이다. 따라서 변경이 발생하는 경우 새로운 객체를 생성해서 반환하므로 반환값을 꼭 받아야 한다.
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
- 일광 절약 시간제가 적용되지 않는다.
- 예)
Year, Month, YearMonth, MonthDay
년, 월, 년월, 달일을 각각 다룰 때 사용한다. 자주 사용하지는 않는다.DayOfWeek
와 같이 월, 화, 수, 목, 금, 토, 일을 나타내는 클래스도 있다.
Instant
Instant
는 UTC(협정 세계시)를 기준으로 하는, 시간의 한 지점을 나타낸다. Instant
는 날짜와 시간을 나노초 정밀도로 표현하며, 1970년 1월 1일 0시 0분 0초(UTC)를 기준으로 경과한 시간으로 계산된다.
쉽게 이야기해서 Instant 내부에는 초 데이터만 들어있다. (나노초 포함)
Period, Duration
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);
System.out.println("더한 날짜: " + plusDate);
//기간 차이
LocalDate startDate = LocalDate.of(2023, 1, 1);
LocalDate endDate = LocalDate.of(2023, 4, 2);
Period between = Period.between(startDate, endDate);
System.out.println("기간: " + between.getMonths() + "개월 " + between.getDays() + "일");
}
}
period = P10D
현재 날짜: 2030-01-01
더한 날짜: 2030-01-11
기간: 3개월 1일
of()
: 특정 기간을 지정해서 Period
를 생성한다.
of(년, 월, 일)
ofDays()
ofMonths()
ofYears()
Period.between(startDate, endDate)
와 같이 특정 날짜의 차이를 구하면 Period
가 반환된다.
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);
//계산에 사용
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.toMinutesPart() + "분");
}
}
duration = PT30M
기준 시간 = 01:00
더한 시간 = 01:30
차이: 3600초
근무 시간: 1시간 0분
of()
: 특정 시간을 지정해서 Duration
를 생성한다.
of(지정)
ofSeconds()
ofMinutes()
ofHours()
Duration.between(start, end)
와 같이 특정 시간의 차이를 구하면 Duration
이 반환된다.
날짜와 시간의 핵심 인터페이스
날짜와 시간은 특정 시점의 시간(시각)과 시간의 간격(기간)으로 나눌 수 있다.
- TemporalAccessor 인터페이스
날짜와 시간을 읽기 위한 기본 인터페이스로, 특정 시점의 날짜와 시간 정보를 읽을 수 있는 최소한의 기능을 제공 - Temporal 인터페이스
TemporalAccessor
의 하위 인터페이스로, 날짜와 시간을 조작하기 위한 기능을 제공한다. 이를 통해 날짜와 시간을 변경하거나 조정할 수 있다. - TemporalAmount 인터페이스
시간의 간격을 나타내며, 날짜와 시간 객체에 적용하여 그 객체를 조정할 수 있다. 예를 들어 특정 날짜에 일정 기간을 더하거나 빼는 데 사용된다.
시간의 단위와 시간 필드
시간의 단위 - TemporalUnit, ChronoUnit
TemporalUnit
인터페이스는 날짜와 시간을 측정하는 단위를 나타내며, 주로 사용되는 구현체는java.time.temporal.ChronoUnit
열거형으로 구현되어 있다.ChronoUnit
은 다양한 시간 단위를 제공한다.- 여기서
Unit
이라는 뜻을 번역하면 단위이다. 따라서 시간의 단위 하나하나를 나타낸다. package time;
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("HOURS = " + 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 secondsBetween = ChronoUnit.SECONDS.between(lt1, lt2);
System.out.println("secondsBetween = " + secondsBetween);
long minutesBetween = ChronoUnit.MINUTES.between(lt1, lt2);
System.out.println("minutesBetween = " + minutesBetween);
}
}
value = Nanos
value = Micros
value = Millis
value = Seconds
value = Minutes
value = Hours
value = HalfDays
value = Days
value = Weeks
value = Months
value = Years
value = Decades
value = Centuries
value = Millennia
value = Eras
value = Forever
HOURS = Hours
HOURS.duration = 3600
DAYS = Days
DAYS.duration = 86400
secondsBetween = 600
minutesBetween = 10
`ChronoUnit` 을 사용하면 두 날짜 또는 시간 사이의 차이를 해당 단위로 쉽게 계산할 수 있다.
예제 코드에서는 두 `LocalTime` 객체 사이의 차이를 초, 분 단위로 구한다.
**시간 필드 - ChronoField**
`ChronoField` 는 날짜 및 시간을 나타내는 데 사용되는 열거형이다. 이 열거형은 다양한 필드를 통해 **날짜와 시간의 특정 부분을 나타낸다.** 여기에는 연도, 월, 일, 시간, 분 등이 포함된다.
- `TemporalField` 인터페이스는 날짜와 시간을 나타내는데 사용된다. 주로 사용되는 구현체는
- `java.time.temporal.ChronoField` 열거형으로 구현되어 있다.
- `ChronoField` 는 다양한 필드를 통해 날짜와 시간의 특정 부분을 나타낸다. 여기에는 연도, 월, 일, 시간, 분 등이 포함된다.
- 여기서 필드(Field)라는 뜻이 날짜와 시간 중에 있는 특정 필드들을 뜻한다. 각각의 필드 항목은 다음을 참고하자.
예를 들어 2024년 8월 16일이라고 하면 각각의 필드는 다음과 같다.
`YEAR` : 2024
`MONTH_OF_YEAR` : 8
`DAY_OF_MONTH` : 16
- 단순히 시간의 단위 하나하나를 뜻하는 `ChronoUnit` 과는 다른 것을 알 수 있다. `ChronoField` 를 사용해야 날짜와 시간의 각 필드 중에 원하는 데이터를 조회할 수 있다.
```Java
package time;
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
NanoOfDay, range = 0 - 86399999999999
MicroOfSecond, range = 0 - 999999
MicroOfDay, range = 0 - 86399999999
MilliOfSecond, range = 0 - 999
MilliOfDay, range = 0 - 86399999
SecondOfMinute, range = 0 - 59
SecondOfDay, range = 0 - 86399
MinuteOfHour, range = 0 - 59
MinuteOfDay, range = 0 - 1439
HourOfAmPm, range = 0 - 11
ClockHourOfAmPm, range = 1 - 12
HourOfDay, range = 0 - 23
ClockHourOfDay, range = 1 - 24
AmPmOfDay, range = 0 - 1
DayOfWeek, range = 1 - 7
AlignedDayOfWeekInMonth, range = 1 - 7
AlignedDayOfWeekInYear, range = 1 - 7
DayOfMonth, range = 1 - 28/31
DayOfYear, range = 1 - 365/366
EpochDay, range = -365243219162 - 365241780471
AlignedWeekOfMonth, range = 1 - 4/5
AlignedWeekOfYear, range = 1 - 53
MonthOfYear, range = 1 - 12
ProlepticMonth, range = -11999999988 - 11999999999
YearOfEra, range = 1 - 999999999/1000000000
Year, range = -999999999 - 999999999
Era, range = 0 - 1
InstantSeconds, range = -9223372036854775808 - 9223372036854775807
OffsetSeconds, range = -64800 - 64800
MONTH_OF_YEAR.range() = 1 - 12
DAY_OF_MONTH.range() = 1 - 28/31
날짜와 시간 조회하기
날짜와 시간을 조회하려면 날짜와 시간 항목중에 어떤 필드를 조회할 지 선택해야 한다. 이때 날짜와 시간의 필드를 뜻하는 ChronoField
가 사용된다.
package time;
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
public class GetTimeMain {
public static void main(String[] args) {
LocalDateTime dt = LocalDateTime.of(2030, 1, 1, 13, 30, 59);
System.out.println("YEAR = " + dt.get(ChronoField.YEAR));
System.out.println("MONTH_OF_YEAR = " + dt.get(ChronoField.MONTH_OF_YEAR));
System.out.println("DAY_OF_MONTH = " + dt.get(ChronoField.DAY_OF_MONTH));
System.out.println("HOUR_OF_DAY = " + dt.get(ChronoField.HOUR_OF_DAY));
System.out.println("MINUTE_OF_HOUR = " + dt.get(ChronoField.MINUTE_OF_HOUR));
System.out.println("SECOND_OF_MINUTE = " + dt.get(ChronoField.SECOND_OF_MINUTE));
System.out.println("편의 메서드 사용");
System.out.println("YEAR = " + dt.getYear());
System.out.println("MONTH_OF_YEAR = " + dt.getMonthValue());
System.out.println("DAY_OF_MONTH = " + dt.getDayOfMonth());
System.out.println("HOUR_OF_DAY = " + dt.getHour());
System.out.println("MINUTE_OF_HOUR = " + dt.getMinute());
System.out.println("SECOND_OF_MINUTE = " + dt.getSecond());
System.out.println("편의 메서드에 없음");
System.out.println("MINUTE_OF_DAY = " + dt.get(ChronoField.MINUTE_OF_DAY));
System.out.println("SECOND_OF_DAY = " + dt.get(ChronoField.SECOND_OF_DAY));
}
}
TemporalAccessor.get(TemporalField field)LocalDateTime
을 포함한 특정 시점의 시간을 제공하는 클래스는 모두 TemporalAccessor
인터페이스를 구현한다.TemporalAccessor
는 특정 시점의 시간을 조회하는 기능을 제공한다.get(TemporalField field)
을 호출할 때 어떤 날짜와 시간 필드를 조회할 지 TemporalField
의 구현인 ChronoField
를 인수로 전달하면 된다.
편의 메서드 사용get(TemporalField field)
을 사용하면 코드가 길어지고 번거롭기 때문에 자주 사용하는 조회 필드는 간단한 편의 메서드를 제공한다.dt.get(ChronoField.DAY_OF_MONTH))
dt.getDayOfMonth()
편의 메서드에 없음
자주 사용하지 않는 특별한 기능은 편의 메서드를 제공하지 않는다.
편의 메서드를 사용하는 것이 가독성이 좋기 때문에 일반적으로는 편의 메서드를 사용하고, 편의 메서드가 없는경우 get(TemporalField field)
을 사용하면 된다.
날짜와 시간 조작하기
날짜와 시간을 조작하려면 어떤 시간 단위(Unit)를 변경할 지 선택해야 한다. 이때 날짜와 시간의 단위를 뜻하는 ChronoUnit
이 사용된다.
import java.time.*;
import java.time.temporal.ChronoUnit;
public class ChangeTimePlusMain {
public static void main(String[] args) {
LocalDateTime dt = LocalDateTime.of(2018, 1, 1, 13, 30, 59);
System.out.println("dt = " + dt);
// plus는 숫자를 넣고, 단위를 넣을 수 있다.
LocalDateTime plusDt1 = dt.plus(10, ChronoUnit.YEARS);
System.out.println("plusDt1 = " + plusDt1);
System.out.println("편의 메서드 사용");
LocalDateTime plusDt2 = dt.plusYears(10);
System.out.println("plusDt2 = " + plusDt2);
System.out.println("Period 사용");
Period period = Period.ofYears(10);
LocalDateTime plusDt3 = dt.plus(period);
System.out.println("plusDt3 = " + plusDt3);
}
}
Temporal plus(long amountToAdd, TemporalUnit unit)**LocalDateTime
을 포함한 특정 시점의 시간을 제공하는 클래스는 모두 Temporal
인터페이스를 구현한다.Temporal
은 특정 시점의 시간을 조작하는 기능을 제공한다.plus(long amountToAdd, TemporalUnit unit)
를 호출할 때 더하기 할 숫자와 시간의 단위(Unit)를 전달하면 된다. 이때 TemporalUnit
의 구현인 ChronoUnit
을 인수로 전달하면 된다.
불변이므로 반환 값을 받아야 한다.
참고로 minus()
도 존재한다.
편의 메서드 사용
자주 사용하는 메서드는 편의 메서드가 제공된다.dt.plus(10, ChronoUnit.YEARS)
dt.plusYears(10)
Period를 사용한 조작Period
나 Duration
은 기간(시간의 간격)을 뜻한다. 특정 시점의 시간에 기간을 더할 수 있다.
현재 타입에 필요한 정보가 있는지 확인하는 메서드
package time;
import java.time.LocalDate;
import java.time.temporal.ChronoField;
public class IsSupportedMain2 {
public static void main(String[] args) {
LocalDate now = LocalDate.now(); // LocalDate는 년, 월, 일만 다룬다.
boolean supported = now.isSupported(ChronoField.SECOND_OF_MINUTE);
System.out.println("supported = " + supported);
if (supported) {
int minute = now.get(ChronoField.SECOND_OF_MINUTE);
System.out.println("minute = " + minute);
}
}
}
with() 메서드를 통한 조작
package time;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAdjusters;
public class ChangeTimeWithMain {
public static void main(String[] args) {
LocalDateTime dt = LocalDateTime.of(2018, 1, 1, 13, 30, 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("같은 달의 마지막 일요일 = " + with2);
}
}
dt = 2018-01-01T13:30:59
changedDt1 = 2020-01-01T13:30:59
changedDt2 = 2020-01-01T13:30:59
기준 날짜: 2018-01-01T13:30:59
다음 금요일: 2018-01-05T13:30:59
같은 달의 마지막 일요일 = 2018-01-28T13:30:59
Temporal with(TemporalField field, long newValue)Temporal.with()
를 사용하면 날짜와 시간의 특정 필드의 값만 변경할 수 있다.
불변이므로 반환 값을 받아야 한다.
편의 메서드
자주 사용하는 메서드는 편의 메서드가 제공된다.dt.with(ChronoField.YEAR, 2020)
dt.withYear(2020)
TemporalAdjuster 사용with()
는 아주 단순한 날짜만 변경할 수 있다. 다음주 금요일, 이번 달의 마지막 일요일 같은 복잡한 날짜를 계산하고 싶다면 TemporalAdjuster
를 사용하면 된다.
원래대로 하면 이 인터페이스를 직접 구현해야겠지만, 자바는 이미 필요한 구현체들을 TemporalAdjusters
에 다 만들어 두었다. 우리는 단순히 구현체들을 모아둔 TemporalAdjusters
를 사용하면 된다.TemporalAdjusters.next(DayOfWeek.FRIDAY)
: 다음주 금요일을 구한다.TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY)
: 이번 달의 마지막 일요일을 구한다.
날짜와 시간 문자열 파싱과 포멧팅
- 포맷팅: 날짜와 시간 데이터를 원하는 포맷의 문자열로 변경하는 것,
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 parseDate = LocalDate.parse(input, formatter);
System.out.println("문자열 파싱 날짜와 시간 " + parseDate);
}
}
포멧팅과 파싱을 하기 위해서는 Formatter 패턴을 알아두어야 한다.
package time;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class FormattingMain2 {
public static void main(String[] args) {
// 포맷팅: 날짜와 시간을 문자로
LocalDateTime now = LocalDateTime.of(2024, 12, 31, 13, 30, 59);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = now.format(formatter);
System.out.println("날짜와 시간 포맷팅: " + formattedDateTime);
// 파싱: 문자를 날짜와 시간으로
String dateTimeString = "2030-01-01 11:30:00";
LocalDateTime parsedDateTime = LocalDateTime.parse(dateTimeString, formatter);
System.out.println("문자열 파싱 날짜와 시간: " + parsedDateTime);
}
}
참고
'Language > Java' 카테고리의 다른 글
[Java] Enum (0) | 2024.08.09 |
---|---|
[Java] Wrapper Class (0) | 2024.07.31 |
[Java] String 클래스 (0) | 2024.07.31 |
[Java] 불변 객체 (0) | 2024.07.26 |
[Java] Object 클래스 (0) | 2024.07.24 |