일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 | 31 |
- 자바의 정석 기초편 ch4
- 스프링 입문(무료)
- 자바의 정석 기초편 ch13
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch8
- 자바의 정석 기초편 ch14
- 코드로 시작하는 자바 첫걸음
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch9
- 스프링 mvc2 - 검증
- 스프링 mvc2 - 타임리프
- 스프링 db2 - 데이터 접근 기술
- 게시글 목록 api
- 자바의 정석 기초편 ch12
- 스프링 mvc2 - 로그인 처리
- 스프링 mvc1 - 서블릿
- 스프링 mvc1 - 스프링 mvc
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch6
- @Aspect
- 자바의 정석 기초편 ch1
- 스프링 db1 - 스프링과 문제 해결
- jpa - 객체지향 쿼리 언어
- 자바 기본편 - 다형성
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch7
- 자바 중급1편 - 날짜와 시간
- jpa 활용2 - api 개발 고급
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch2
- Today
- Total
나구리의 개발공부기록
날짜와 시간, 날짜와 시간 라이브러리가 필요한 이유와 라이브러리 소개, 기본 날짜와 시간 - LocalDateTime, 타임존 - ZonedDateTime, 기계 중심의 시간 - Instant, 기간, 시간의 간격 - Duration, Period 본문
날짜와 시간, 날짜와 시간 라이브러리가 필요한 이유와 라이브러리 소개, 기본 날짜와 시간 - LocalDateTime, 타임존 - ZonedDateTime, 기계 중심의 시간 - Instant, 기간, 시간의 간격 - Duration, Period
소소한나구리 2025. 1. 21. 15:03출처 : 인프런 - 김영한의 실전 자바 - 중급1편 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 날짜와 시간 라이브러리가 필요한 이유와 라이브러리 소개
1) 날짜와 시간 라이브러리가 필요한 이유
(1) 날짜와 시간 차이 계산
- 특정 날짜에서 다른 날짜까지의 정확한 일수를 계산하는 것은 생각보다 복잡함
- 윤년, 각 달의 일수 등을 모두 고려해야하고 간단한 뺄셈 연산으로는 정확한 결과를 얻기 어려움
- 예를 들어 2024년 1월 1일에서 2024년 2월 1일까지 며칠인지 계산을 하려고 할 때 각각의 월마다 날짜가 다르기 때문에 1월은 31일까지라는 점을 고려해야 함
(2) 윤년 계산
- 지구가 태양을 한 바퀴 도는데 걸리는 평균 시간은 대략 365.2425일 즉 365일 5시간 48분 45초 정도임
- 우리가 사용하는 그레고리력(현재 전세계적으로 널리 사용되는 달력)은 1년이 보통 365일로 설정되어있으므로 둘의 시간이 정확하게 맞지 않음
- 이런 문제를 해결하기 위해 4년마다 하루(2월 29일)를 추가하는 윤년(leap year)을 도입하고 있어 28일까지 있는 2월이 4년마다 29일이 됨
- 윤년 계산은 이것 말고도 4년마다 한번씩 발생하고, 100년 단위는 윤년이 아니고 400년 단위일때는 다시 윤년이라는 매우 복잡한 규칙을 가지고 있음
- 예를 들어 2024년 1월 1일에서 2024년 3월 1일까지 계산하려고 하면 매 월마다 날짜가 다른것도 고려해야 함과 동시에 윤년여부도 고려해야 함
(3) 일광 절약 시간(Daylight Saving Time, DST) 변환
- 보통 3월에서 10월을 태양이 일찍뜨고 나머지는 태양이 상대적으로 늦게 뜨는데 시간도 여기에 맞추어 1시간 앞당기거나 늦추는 제도를 일광 절약 시간제 또는 서머타임이라고 함
- 우리나라는 1988년 이후로는 시행하지 않지만 일광 절약 시간은 국가나 지역에 따라 적용 여부와 시작 및 종료 날짜가 다르기 때문에 이를 국가별로 정확히 계산하는 것은 매우 복잡함
- 예를 들어 특정 지역에서는 3월의 마지막 일요일에 DST가 시작되어 10월의 마지막 일요일에 종료된다고 가정하면 이 기간 동안 발생하는 모든 날짜와 시간 계산은 1시간을 추가하거나 빼는 로직을 포함해야 함
(4-1) 타임존 계산
- 세계는 다양한 타임존으로 나뉘어 있으며 각 타임존은 UTC(협정 세계시)로부터 시간 차이로 정의됨
- 타임존 간의 날짜와 시간 변환을 정확히 계산하는 것 역시 복잡함
- 예를 들어 서울에 있는 사람과 베를린에 있는 사람이 미팅을 계획하려고하면 각각의 타임존을 찾아서 계산을 해야하고 여기에 DST가 적용되는 여부까지 포함해야 제대로된 시차를 구할 수 있음
(4-2) 타임존 목록
Europe/London
GMT
UTC
US/Arizona -07:00
America/New_York -05:00
Asia/Seoul +09:00
Asia/Dubai +04:00
Asia/Istanbul +03:00
Asia/Shanghai +08:00
Europe/Paris +01:00
Europe/Berlin +01:00
(4-3) GMT, UTC
- London/ UTC/ GMT는 세계 시간의 기준이 되는 00:00 시간대임
(4-4) GMT(그리니치 평균시, Greenwich Mean Time)
- 처음 세계 시간을 만들 때 영국 런던에 있는 그리니치 천문대를 기준으로 했음
- 태양이 그리니치 천문대를 통과할 때를 정오로 함
(4-5) UTC(협정 세계시, Universal Time Coordinated)
- GMT를 대체하기 위해 도입되었으며 UTC도 마찬가지로 그리니치 천문대를 기준으로하기 때문에 실질적으로 같은 시간대를 나타냄
- 원자 시계를 사용하여 측정한 국제적으로 합의된 시간 체계로 지구의 자전 속도가 변화하는 것을 고려하여 윤초를 추가하거나 빼는 방식으로 시간을 조정하여 더 정확한 시간을 유지함
- 우리가 일반적으로 사용할 때는 GMT와 UTC는 거의 차이가 없어서 종종 같은 의미로 사용되기도 하지만 정밀한 시간 측정과 국제적인 표준에 관해서는 UTC가 선호됨
(5) 정리
- 이러한 복잡성 때문에 대부분의 현대 개발 환경에서는 날짜와 시간을 처리하기 위해 잘 설계된 라이브러리를 사용해야 함
- 이러한 라이브러리는 위에서 언급한 복잡한 계산을 추상화하여 제공하므로 개발자는 보다 안정적이고 정확하며 효율적인 코드를 작성할 수 있음
2) 자바 날짜와 시간 라이브러리의 역사
(1) JDK 1.0 - java.util.Date
- 문제점
- 타임존 처리 부족: 초기 Date 클래스는 타임존(time zone)을 제대로 처리하지 못함
- 불편한 날짜 시간 연산: 날짜 간 연산이나 시간의 증감 등을 처리하기 어려움
- 불변 객체 부재: 가변 객체여서 데이터가 쉽게 변경될 수 있었고 이로 인한 사이드 이펙트가 발생하기 쉬웠음 - 해결책
- JDK 1.1에서 java.util.Calendar 클래스 도입으로 타임존 지원 개선
- 날짜 시간 연산을 위한 추가 메소드 제공
(2) JDK 1.1 - java.util.Calendar
- 문제점
- 사용정 저하: Calendar는 사용하기 복잡하고 직관적이지 않음
- 성능 문제: 일부 사용 사례에서 성능이 저하되는 문제가 발견되었음
- 불변 객체 부재: 마찬가지로 가변 객체이므로 사이드 이펙트 문제가 있었고 스레드 안정성 문제가 있었음 - 해결책
- Joda-Time 오픈소스 라이브러리를 도입하여 위의 문제들을 해결했음
(3) Joda-Time
- 문제점
- 표준 라이브러리가 아님: 문제점은 아니긴하지만 Joda-Time은 외부 라이브러리이기 때문에 자바 표준에 포함되지 않아 프로젝트에 별도로 추가해야 함 - 해결책
- 자바 8에서 java.time 패키지(JSR-310)를 표준 API 도입함
(4) JDK 8(1.8) - java.time 패키지
- java.time 패키지는 이전 API의 문제점을 해결하면서 사용성, 성능, 스레드 안정성, 타임존 처리 등에서 크게 개선되었음
- 불변 객체로 설계되어 사이드 이펙트와 스레드 안정성을 보장하고 보다 직관적인 API를 제공하여 날짜와 시간 연산을 단순화 함
- LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant 등의 클래스를 포함하고 Joda-Time의 많은 기능을 표준 자바 플랫폼으로 가져옴
** 참고
- 자바가 표준으로 제공했던 Date, Calendar는 사용성이 너무 떨어지고 문제가 많은 라이브러리였음
- 이런 문제를 해결하기위해 결국 Joda-Time 이라는 오픈소스 라이브러리가 등장하였고 Joda-Time의 편리함과 사용성 덕분에 크게 대중화되었음
- 자바가 기존에 제공하던 API의 설계를 반성하고 Joda-Time을 만든 개발자를 데려와서 JSR-310이라는 새로운 자바 표준 날자와 시란 라이브러리를 정의하였는데 이것이 java.time 패키지임
- 실용적인 Joda-Time에 많은 자바 커뮤니티의 의견을 반영하여 좀 더 안정적이고 표준적인 날짜와 시간 라이브러리인 java.time 패키지가 성공적으로 완성 되었음
- 참고로 자바 표준 ORM 기술인 JPA도 비슷한 역사를 가지고 있음(JPA 강의에서 나옴)
- java.time, JPA 모두 큰 성공을 거두어 자바의 메인 표준 기술로 완전히 자리 잡았으며 자바는 지금도 지속적으로 업데이트를 하고 있음
3) 날짜와 시간 라이브러리 소개
(1) 자바 날짜와 시간 라이브러리
- 원문링크
- *: 초는 나노초 단위의 정밀도로 캡처됨(밀리초, 나노초 가능)
- **: 이 클래스는 이 정보를 저장하지는 않지만 이러한 단위로 시간을 제공하는 메서드가 있음
- ***: ZonedDateTime에 Period를 추가하면 서머타임 또는 기타 현지 시간 차이를 준수함
(2) LocalDate, LocalTime, LocalDateTime
- LocalDate: 날짜만 표현할 때 사용하며 년, 월, 일을 다룸
- 예) 2013-11-21 - LocalTime: 시간만을 표현할 때 사용하며 시, 분 초를 다룸
- 예) 08:20:30.213 - LocalDateTime: LocalDate와 LocalTime을 합한 개념으로 실제 내부에 LocalDate와 LocalTime이 있음
- 예) 2013-11-21T08:20:30.213 - 앞에 Local이 붙는 이유는 세계 시간대를 고려하지 않아서 타임존이 적용되지 않기 때문으로 특정 지역의 날짜와 시간만 고려할 때 사용함
-예) 애플리케이션 개발 시 국내 서비스만 고려할 때, 나의 생일을 표현할 때 등
(3) ZonedDateTime, OffsetDateTime
- ZonedDateTime: 시간대를 고려한 날짜와 시간을 표현할 때 사용하며 시간대를 표현하는 타임존이 포함됨
- 예) 2013-11-21T08:20:30.213+9:00[Asia/Seoul]
- +9:00은 UTC로부터의 시간대 차이를 표현한 것으로 오프셋이라고 함, 한국은 UTC보다 +9:00시간임
- Asia/Seoul을 타임존이라고하며 타임존을 알면 오프셋과 일광 절약 시간제에 대한 정보를 알 수 있음
- 일광 절약 시간제가 적용됨 - OffsetDateTime: ZonedDateTime과 동일하지만 타임존이 없어서 고정된 오프셋만 포함함
- 예) 2013-11-21T08:20:30.213+9:00
- 일광 절약 시간제가 적용되지 않음 - Asia/Seoul 같은 타임존 안에는 일광 절약 시간제에 대한 정보와 UTC+9:00와 같은 UTC로부터 시간 차이인 오프셋 정보를 모두 포함하고 있으므로 일광 절약 시간제를 알려면 타임존을 알아야 함
- 그래서 타임존이 포함된 ZonedDatetime은 일광 절약 시간제를 함께 처리하는 반면 타임존을 알 수 없는 OffsetDateTime은 일광 절약 시간제를 처리하지 못함
- ZonedDateTime은 시간대를 고려해야 할 때 일상에서 실제 사용하는 날짜와 시간 정보를 나타내는데 더 적합하고 OffSetDateTime은 UTC로부터의 고정된 오프셋만 고려해야할 때 유용함
(4) Year, Month, TearMonth, MonthDay
- 년, 월, 년월, 달일을 각각 다룰 때 사용하지만 자주 사용하진않음
- 월, 화, 수, 목, 금, 토, 일을 나타내는 DayOfWeek 클래스(ENUM)도있음
(5) Instant
- UTC를 기준으로하는 시간의 한 지점을 나타냄
- 날짜와 시간을 나노초 정밀도로 표현하며 1970년 1월 1일 0시 0분 0초(UTC)를 기준으로 경과한 시간으로 계산됨
- 즉, Instant 내부에는 초 데이터(나노초 포함)만 들어으므로 날짜와 시간을 계산에 사용할 때는 적합하지는 않음
(6) Period, Duration
- 시간의 개념은 크게 특정 시점의 시간(시각)과 시간의 간격(기간) 2가지로 표현할 수 있는데, Period, Duration은 시간의 간격(기간)을 표현하는데 사용됨
- 시간의 간격은 영어로 amount of time(시간의 양)으로 불림
- Period: 두 날짜 사이의 간격을 년, 월, 일 단위로 나타냄
- Duration: 두 시간 사이의 간격을 시, 분, 초(나노초) 단위로 나타냄
2. 기본 날짜와 시간 - LocalDateTime
1) LocalDate, LocalTime, LocalDateTime - 가장 기본인 클래스들
(1) LocalDateMain
- now(): 현재 날짜를 기준으로 생성
- of(...): 년, 월, 일을 입력하여 특정 날짜를 기준으로 생성
- plusDays(): 특정 일을 더하는 메서드로 다양한 계산을 하는 메서드가 존재함
package time;
public class LocalDateMain {
public static void main(String[] args) {
LocalDate nowDate = LocalDate.now();
LocalDate ofDate = LocalDate.of(123, 12, 22);
System.out.println("오늘 날짜 = " + nowDate);
System.out.println("지정 날짜 = " + ofDate);
// 계산(불변) - 불변이므로 계산 후 반환값을 받아야 함
LocalDate ofDatePlusDay = ofDate.plusDays(10);
System.out.println("지정 날짜+10d(반환값 x, 계산 안됨) = " + ofDate);
System.out.println("지정 날짜+10d(반환값 o, 계산 됨) = " + ofDatePlusDay);
}
}
/* 실행 결과
오늘 날짜 = 2025-01-21
지정 날짜 = 0123-12-22
지정 날짜+10d(반환값 x, 계산 안됨) = 0123-12-22
지정 날짜+10d(반환값 o, 계산 됨) = 0124-01-01
*/
(2) LocalTimeMain
- now(): 현재 시간을 기준으로 생성
- of(...): 시, 분 초, 나노초를 입력하여 특정 시간을 기준으로 생성함
- plusSeconds(): 특정 초를 더하는 메서드, 마찬가지로 다양한 계산을 할 수 있는 메서드가 존재함
package time;
public class LocalTimeMain {
public static void main(String[] args) {
LocalTime nowTime = LocalTime.now();
LocalTime ofTime = LocalTime.of(22, 12, 30);
System.out.println("현재 시간 = " + nowTime);
System.out.println("지정 시간 = " + ofTime);
// 계산(불변)
LocalTime ofTimePlusSec = ofTime.plusSeconds(30);
System.out.println("지정 시간 + 30s = " + ofTimePlusSec);
}
}
/* 실행 결과
현재 시간 = 11:04:55.958343
지정 시간 = 22:12:30
지정 시간 + 30s = 22:13
*/
(3) LocalDateTimeMain
- LocalDate와 LocalTime을 내부에 가지고 날짜와 시간을 모두 표현하도록 설계 되어 있음
- now(), of(...): 동일하게 now()와 of()메서드로 현재기준과 특정날짜와 시간을 기준으로 생성할 수 있음
- 분리 및 합체: 생성된 LocalDateTime 인스턴스를 날짜(LocalDate)와 시간(LocalTime)으로 메서드를 통하여 분리할 수 있으며 of()메서드는 LocalDate와 LocalTime을 인자로 하여 LocalDateTime로 합칠 수도 있음
- 계산: LocalDate와 LocalTime에 있는 다양한 계산 메서드를 사용할 수 있음
- 비교: isBefore(), isAfter(), isEqual() 메서드로 비교 대상보다 날짜와 시간이 이전인지, 이후인지, 같은지 여부를 반환할 수 있음
package time;
public class LocalDateTimeMain {
public static void main(String[] args) {
LocalDateTime nowDt = LocalDateTime.now();
LocalDateTime ofDt = LocalDateTime.of(2016, 8, 16, 8, 10, 1);
System.out.println("현재 날짜시간 = " + nowDt);
System.out.println("지정 날짜시간 = " + ofDt);
// 날짜와 시간 분리
LocalDate localDate = ofDt.toLocalDate();
LocalTime localTime = ofDt.toLocalTime();
System.out.println("localDate = " + localDate);
System.out.println("localTime = " + localTime);
// 날짜와 시간 합체
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
System.out.println("localDateTime = " + localDateTime);
// 계산(불변)
LocalDateTime ofDtPlusDay = ofDt.plusDays(1000);
System.out.println("지정 날짜시간+1000d = " + ofDtPlusDay);
LocalDateTime ofDtPlusYear = ofDt.plusYears(1);
System.out.println("지정 날짜시간+1년 = " + ofDtPlusYear);
// 비교
System.out.println("현재 날짜시간이 지정 날짜시간보다 이전인가? " + nowDt.isBefore(ofDt));
System.out.println("현재 날짜시간이 지정 날짜시간보다 이후인가? " + nowDt.isAfter(ofDt));
System.out.println("현재 날짜시간이 지정 날짜시간보다 같은가? " + nowDt.isEqual(ofDt));
}
}
/* 실행 결과
현재 날짜시간 = 2025-01-21T11:34:01.772118
지정 날짜시간 = 2016-08-16T08:10:01
localDate = 2016-08-16
localTime = 08:10:01
localDateTime = 2016-08-16T08:10:01
지정 날짜시간+1000d = 2019-05-13T08:10:01
지정 날짜시간+1년 = 2017-08-16T08:10:01
현재 날짜시간이 지정 날짜시간보다 이전인가? false
현재 날짜시간이 지정 날짜시간보다 이후인가? true
현재 날짜시간이 지정 날짜시간보다 같은가? false
*/
(4) isEqual() vs equals()
- isEquals(): 단순히 비교 대상이 시간적으로 같으면 true를 반환하므로 객체, 타임존이 달라도 시간적으로 같으면 true를 반환함
- equlas(): 객체의 타입, 타임존 등등 내부 데이터의 모든 구성요소가 같아야 true를 반환
- 예를 들어 서울의 9시와 UTC의 0시는 시간적으로 같으므로 이 둘을 isEqual()로 비교하면 true를 반환하지만 equals()비교를 하면 타임존 데이터가 다르기 때문에 fasle를 반환함
** 주의 - 불변
- 모든 날짜 클래스는 불변이기 때문에 변경이 발생하는 경우 새로운 객체를 생성해서 반환하므로 반환값을 꼭 받아야함
** 참고
- 대부분은 국내 애플리케이션을 개발하므로 LocalDateTime을 사용하며 글로벌 서비스가 필요하다면 타임존에 대한 것만 붙이면 되기 때문에 LocalDateTime을 잘 이해하는 것이 중요함
3. 타임존 - ZonedDateTime
1) ZoneId, ZonedDateTime, OffsetDateTime
(1) ZoneIdMain
- 자바는 일광 절약 시간제에 대한 정보와 오프셋 정보를 모두 포함하고있는 타임존을 ZoneId 클래스로 제공함
- ZoneId.systemDefault(): 시스템이 사용하는 기본 ZoneId를 반환
- ZoneId.of(): 타임존을 직접 입력하여 ZoneId를 반환
package time;
public class ZoneIdMain {
public static void main(String[] args) {
// 전체 타임존 출력
for (String availableZoneId : ZoneId.getAvailableZoneIds()) {
ZoneId zoneId = ZoneId.of(availableZoneId);
System.out.println(zoneId + " | " + zoneId.getRules());
}
// 운영체제의 달력을 기반으로한 타임존
ZoneId zoneId = ZoneId.systemDefault();
System.out.println("zoneId.systemDefault = " + zoneId);
// 문자열로 직접 입력
ZoneId seoulZoneId = ZoneId.of("Asia/Seoul");
System.out.println("seoulZoneId = " + seoulZoneId);
}
}
/* 출력 결과가 많으므로 생략 */
(2) ZonedDateTimeMain
- LocalDateTime에 시간대 정보인 ZoneId가 합쳐진 것으로 내부에 ZonedId와 ZoneOffSet을 가지고 있으며 일광 절약 시간제가 적용됨
- ZoneId만으로도 오프셋을 구할 수 있지만 해당 클래스가 편리하게 계산하기 위하여 ZoneOffSet을 따로 가지고 있음
- 생성: now()메서드는 날짜 시간을 기준으로 생성하고 of(...)는 특정 날짜와 시간을 기준으로 생성하며, of는 기존에 생성된 LocalDateTime에 ZoneId를 추가해서 생성 할 수도 있음, 둘 다 타임존은 현재 시스템을 기준으로 생성됨
- 타임존 변경: withZoneSameInstant(ZoneId): 타임존을 변경하여 변경된 타임존에 맞춰 시간도 변경되므로 다른 해당 메서드를 호출한 인스턴스의 시간을 기준으로 다른나라의 시간을 확인할 수 있음
package time;
public class ZonedDateTimeMain {
public static void main(String[] args) {
ZonedDateTime nowZdt = ZonedDateTime.now();
System.out.println("nowZdt = " + nowZdt);
LocalDateTime ldt = LocalDateTime.of(2030, 1, 1, 13, 30, 50);
ZonedDateTime zdt1 = ZonedDateTime.of(ldt, ZoneId.of("Asia/Seoul"));
System.out.println("zdt1 = " + zdt1);
ZonedDateTime zdt2 = ZonedDateTime.of(2030, 1, 1, 13, 30, 50, 0, ZoneId.of("Asia/Seoul"));
System.out.println("zdt2 = " + zdt2);
ZonedDateTime utcZdt = zdt2.withZoneSameInstant(ZoneId.of("UTC"));
System.out.println("utcZdt = " + utcZdt);
}
}
/* 실행 결과
nowZdt = 2025-01-21T13:07:19.423359+09:00[Asia/Seoul]
zdt1 = 2030-01-01T13:30:50+09:00[Asia/Seoul]
zdt2 = 2030-01-01T13:30:50+09:00[Asia/Seoul]
utcZdt = 2030-01-01T04:30:50Z[UTC]
*/
(3) OffsetDateTimeMain
- LocalDateTime에 오프셋 정보인 ZoneOffSet이 합쳐진 것으로 타임존이 없으므로 일광 절약 시간제가 적용되지 않으며 UTC로부터의 고정된 오프셋만 포함됨
- 사용방법은 거의 ZonedDateTime과 비슷하며 of()의 인자에 타임존이 아닌 ZoneOffset을 입력하는 부분만 다름
package time;
public class OffsetDateTimeMain {
public static void main(String[] args) {
OffsetDateTime nowOdt = OffsetDateTime.now();
System.out.println("nowOdt = " + nowOdt);
LocalDateTime ldt = LocalDateTime.of(2030, 1, 1, 13, 30, 50);
System.out.println("ldt = " + ldt);
OffsetDateTime odt = OffsetDateTime.of(ldt, ZoneOffset.of("+02:00"));
System.out.println("odt = " + odt);
}
}
/* 실행 결과
nowOdt = 2025-01-21T13:15:25.984172+09:00
ldt = 2030-01-01T13:30:50
odt = 2030-01-01T13:30:50+02:00
*/
(4) ZonedDateTime vs OffsetDateTime
- ZonedDateTime: 구체적인 지역 시간대를 다룰 때 사용하며 일광 절약 시간을 자동으로 처리할 수 있으므로 사용자 지정 시간대에 따른 시간 계산이 필요할 때 적합함
- OffsetDateTime: UTC와의 시간 차이만을 나타낼 때 사용하며 지역 시간대의 복잡성을 고려하지 않으므로 시간대 변환없이 로그를 기록하거나 데이터를 저장하고 처리할 때 적합함
** 참고
- ZonedDateTime이나 OffsetDateTime은 글로벌 서비스를 하지 않으면 잘 사용하지 않으므로 너무 깊이있게 파기 보다는 대략 이런 것이 있다 정도로만 알아두고 실무에서 개발하면서 글로벌 서비스를 개발할 기회가 있을 때 필요한 부분을 찾아서 깊이있게 학습하면 됨
4.기계 중심의 시간 - Instant
1) Instant
(1) 특징
- Instant 내부를 보면 long과 int로 seconds와 nanos필드만 있음 즉, 초와 나노초 정보만 들어있음
- UTC 기준 1970년 1월 1일 0시 0분 0초라면 seconds에 0이 들어가고 1970년 1월 1일 0시 1분 0 초라면 seconds에 60이 들어감
- 장점
- 시간대 독립성: UTC를 기준으로 하므로 시간대에 영향을 받지 않아 전 세계 어디서나 동일한 시점을 가리키는데 유용함
- 고정된 기준점: 1970년 1월 1일 UTC를 기준으로 하기 때문에 시간 계산 및 비교가 명확하고 일관됨 - 단점
- 사용자 친화적이지 않음: 기계적인 시간 처리에는 적합할 수 있지만 사람이 읽고 이해하기에는 직관적이지 않아 날짜와 시간을 계산하고 사용하는데 필요한 기능이 부족함
- 시간대 정보 부재: 시간대 정보가 포함되어있지 않으므로 특정 지역의 날짜와 시간으로 변환하려면 추가적인 작업이 필요함
(2) 사용 예
- 전 세계적인 시간 기준 필요 시: UTC를 기준으로 하므로 전 세계적으로 일관된 시점을 표현해야 할 때 사용하기 좋음, 로그 기록이나, 트랜잭션 타임스탬프, 서버 간의 시간 동기화 등이 이에 해당함
- 시간대 변환 없이 시간 계산 필요 시: 시간대의 변화 없이 순수하게 시간의 흐름(지속 시간 계산 등)만을 다루고 싶을 때에도 적합함
- 데이터 저장 및 교환: 데이터베이스에 날짜와 시간 정보를 저장하거나 다른 시스템과 날짜와 시간 정보를 교환할 때 사용하면 모든 시스템에서 동일한 기준점(UTC)를 사용하게 되므로 데이터의 일관성을 유지하기 쉬움
- 일반적으로 날짜와 시간을 사용할 때는 LocalDateTime이나 ZonedDateTime등을 사용하면 되며 특정 날짜 계산이 어려운 Instant는 사용 예와 같이 특별한 경우에 한정해서 사용하면 됨
** 참고 - Epoch Time
- Epoch Time(에포크 시간) 또는 Unix timestamp는 컴퓨터 시스템에서 시간을 나타내는 방법 중 하나이며 이는 1970년 1월 1일 00:00:00 UTC부터 현재까지 경과된 시간을 초 단위로 표현한 것임
- 즉, Unix 시간은 1970년 1월 1일 이후로 경과한 전체 초의 수로 시간대에 영향을 받지 않는 절대적인 시간 표현 방식임
- Epoch라는 뜻은 어떤 중요한 사건이 발생한 시점을 기준으로 삼는 시작점을 뜻하는 용어이며 Instant는이 Epoch Time을 다루는 클래스임
- Unix가 1969년에 개발되어 1970년 초반에 초기버전이 완성이 되었는데 시간의 표준화가 중요했던 시기이므로 국제 표준인 UTC를 기준으로 하여 의미 있는 시작점인 1970년 1월 1일로 선택했다는 설이 있음(추측)
(3) InstantMain
- now(): UTC를 기준으로하는 현재 시간의 Instant를 생성
- from(): 다른 타입의 날짜와 시간을 기준으로 Instant를 생성 단, UTC를 기준으로 하기 때문에 시간대 정보가 없는 LocalDateTime은 사용 불가
- ofEpochSecond(): 인자에 초를 입력하여 에포크 시간에 인자에 입력된 초를 더한 Instant를 생성되며 0으로 입력하면 에포크 시간이 생성됨
- plusSeconds(): 초를 더하는 메서드로 초, 밀리초, 나노초 정도만 더하거나 빼는 등의 간단한 메서드만 제공됨
- getEpochSecond(): 에포크 시간을 기준으로 흐른 초를 반환
package time;
public class InstantMain {
public static void main(String[] args) {
Instant now = Instant.now(); // UTC 기준
System.out.println("now = " + now);
ZonedDateTime zdt = ZonedDateTime.now();
Instant from = Instant.from(zdt);
System.out.println("from = " + from);
Instant epochStart = Instant.ofEpochSecond(0); // 에포크 시간에 초를 지정하여 생성
System.out.println("epochStart = " + epochStart);
// 계산
Instant later = epochStart.plusSeconds(3600); // 1시간 더하기
System.out.println("later = " + later);
// 조회
long laterEpochSecond = later.getEpochSecond(); // 에포크 시간으로부터 지나간 시간
System.out.println("laterEpochSecond = " + laterEpochSecond);
}
}
/* 실행 결과
now = 2025-01-21T05:00:10.322398Z
from = 2025-01-21T05:00:10.335574Z
epochStart = 1970-01-01T00:00:00Z
later = 1970-01-01T01:00:00Z
laterEpochSecond = 3600
*/
(4) 정리
- UTC를 기준으로하는 기계중심의 특별한 시간으로 에포크 시간으로부터 흐른 시간을 초 단위로 저장함
- 항상 UTC 기준이므로 전세계 모든 서버 시간을 똑같이 맞출 수 있음
- 서버 로그, 간단히 두 시간의 차이를 구할 때 등 사용할 수 있음
- 단점: 초 단위의 간단한 연산만 가능하므로 복잡한 연산을 하기 어려움
- 대안: 날짜 계산이 필요한 경우 LocalDateTime, ZonedDateTime을 사용
5. 기간, 시간의 간격 - Duration, Period
1) Duration, Period
(1) Period, Duration 비교
- 시간의 간격 즉, 기간을 나타냄
구분 | Period | Duration |
단위 | 년, 월, 일 | 시간, 분, 초, 나노초 |
사용 대상 | 날짜 | 시간 |
주요 메소드 | getYears(), getMonths(), getDays() | toHours(), toMinutes(), getSeconds(), getNano() |
(2) PeriodMain
- 두 날짜 사이의 간격을 년, 월, 일 단위로 나타내며 Period 내부에 years, months, days의 값을 가지고 있음
- of(): 특정 기간을 지정하여 Period를 생성하며 ofDays(), ofMonths(), ofYears()로 일, 년, 월을 지정하여 생성할 수도 있음
- 계산: 날짜 인스턴스에 생성한 Period를 더하거나, 빼는 등의 계산에 활용할 수 있음
- 기간 차이: Period.between()으로 두 날짜 인스턴스의 기간 차이를 구할 수 있음
package time;
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(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
currentDate = 2030-01-01
plusDate = 2030-01-11
기간: 3개월 1일
*/
(3) DurationMain
- 두 시간 사이의 간격을 시, 분 초(나노초)단위로 나타내며 Duration 내부에는 seconds, nanos 처럼 초 단위를 저장하는 필드가 있음
- Duration의 메서드 일부를 보면 get으로 시작하는 것과 to로 시작하는 것이 있는데, get은 듀레이션의 필드에서 바로 가져오는 것이고 to는 내부에서 필드의 값을 가지고 계산해서 반환하는 메서드임을 뜻하기 위해 구분지어서 명명되어 있음
- of(): 특정 시간을 지정하여 Duration을 생성하며 ofSeconds(), ofMinutes(), ofHours() 메서드들도 있음
- 계산: Period와 마찬가지로 시간 인스턴스나 시간이 포함된 날짜 인스턴스와의 계산에 Duration을 사용하여 계산할 수 있음
- 시간차이: Duration.between()으로 두 날짜나 시간 인스턴스의 차이를 구하면 Duration이 반환됨
- toMinutes() vs toMinutesPart() 차이
- toHours(), toMinutes() 등: Duration을 순수하게 시간, 분, 초 등의 단위로 환산한 값을 반환하는 메서드로 Duration이 3660초라면 toMinutes()의 결과는 61분이 반환됨
- toHoursPart(), toMinutesPart() 등: Duration을 메서드가 뜻하는 부분(시간이면 시간, 분이면 분)만 반환하는 메서드로 Duration이 3660초라면 toMinutesPart()의 결과는 메서드가 뜻하는 '분'만 반환되므로 시간은 무시되고 1분만 반환됨
package time;
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(5, 23);
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
차이: 16620초
차이(시간, 분): 4시간37분
*/