본문 바로가기
Language/Java

[Java API] 날짜와 시간 라이브러리

by 클레어몬트 2024. 8. 27.

https://claremont.tistory.com/entry/%EC%8D%B8%EB%A8%B8%ED%83%80%EC%9E%84%EA%B3%BC-%ED%83%80%EC%9E%84%EC%A1%B4-%EA%B3%84%EC%82%B0

 

썸머타임과 타임존 계산

ㅇ썸머 타임(일광 절약 시간: DST, Daylight Saving Time)보통 3월에서 10월은 태양이 일찍 뜨고, 나머지는 태양이 상대적으로 늦게 뜬다. 시간도 여기에 맞추어 1시간 앞당기거나 늦추는 제도를 썸

claremont.tistory.com

(참고) Java의 날짜와 시간 라이브러리 역사

https://claremont.tistory.com/entry/%EC%88%98%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9E%90%EC%84%B8%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C

 

수용하는 자세에 대해서

자바의 표준 ORM 기술인 JPA의 역사를 아는가? 과거 자바가 제공하던 표준 ORM 기술은 매우 매우 불편했다. 이에 대응해 누군가가 하이버네이트라는 ORM 오픈 소스를 만들었는데, 이 기술이 자바

claremont.tistory.com

위의 글들을 먼저 보고 개념을 잡도록 하자!

 

 

 

시간과 날짜 계산은 상상하는 것만큼 훨씬 이상으로 복잡하다. 현대 개발 환경에서는 날짜와 시간을 처리하기 위해 잘 설계된 라이브러리를 적극적으로 사용해야 한다. 이러한 라이브러리는 위에서 언급한 복잡한 계산을 추상화하여 제공하므로, 개발자는 보다 안정적이고 정확하며 효율적인 코드를 작성할 수 있다.

(주의) 모든 날짜 클래스는 불변이기 때문에 반환값을 객체로 따로 받아줘야 한다

 

 

[국내 서비스만 고려 시 사용되는 클래스]

- LocalDate: 날짜만 표현할 때 사용

 

- LocalTime: 시간만 표현할 때 사용

(ms, ns 표현 가능)

 

- LocalDateTime: 날짜 + 시간

// LocalDateTime 클래스
public class LocalDateTime {
     private final LocalDate date;
     private final LocalTime time;
     ...
}
package time.local;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class LocalMain {
    public static void main(String[] args) {
        // LocalDate 클래스
        LocalDate nowDate = LocalDate.now();
        LocalDate ofDate = LocalDate.of(2021, 7, 25);
        System.out.println("오늘 날짜 = " + nowDate);
        System.out.println("지정 날짜 = " + ofDate);
        // 계산(불변 주의!)
        ofDate = ofDate.plusDays(10);
        System.out.println("지정 날짜 + 10d = " + ofDate);
        System.out.println();

        // LocalTime 클래스
        LocalTime nowTime = LocalTime.now();
        LocalTime ofTime = LocalTime.of(9, 10, 30);
        System.out.println("현재 시간 = " + nowTime);
        System.out.println("지정 시간 = " + ofTime);
        // 계산(불변 주의!)
        LocalTime ofTimePlus = ofTime.plusSeconds(31);
        System.out.println("지정 시간 + 30s = " + ofTimePlus);
        System.out.println();

        // LocalDateTime 클래스
        LocalDateTime nowDt = LocalDateTime.now();
        LocalDateTime ofDt = LocalDateTime.of(2020, 1, 13, 13, 00, 00);
        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 ofDtPlus = ofDt.plusDays(1000);
        System.out.println("지정 날짜시간 + 1000d = " + ofDtPlus);
        LocalDateTime ofDtPlus1Year = ofDt.plusYears(1);
        System.out.println("지정 날짜시간 + 1y = " + ofDtPlus1Year);
        // 비교
        System.out.println("현재 날짜시간이 지정 날짜시간보다 이전인가? " + nowDt.isBefore(ofDt));
        System.out.println("현재 날짜시간이 지정 날짜시간보다 이후인가? " + nowDt.isAfter(ofDt));
        System.out.println("현재 날짜시간이 지정 날짜시간이 같은가? " + nowDt.isEqual(ofDt));
    }
}

(DayOfWeek 와 같이 월, 화, 수, 목, 금, 토, 일을 나타내는 클래스도 있다)

 

**isEqual() vs equals()**
- isEqual(): 단순히 시간적으로 같으면 true를 반환

e.g. 서울의 9시와 UTC의 0시는 시간적으로 같다. 이 둘을 비교하면 true를 반환한다.

 

- equals(): 객체의 타입, 타임존 등등 내부 데이터의 모든 구성요소가 같아야 true를 반환
e.g. 서울의 9시와 UTC의 0시는 시간적으로 같다. 하지만 이 둘은 타임존의 데이터가 다르기 때문에 false를 반환한다.

 

 

 

[해외 서비스도 고려시 사용되는 클래스]

- ZonedDateTime: 시간대를 고려한 날짜와 시간을 표현할 때 사용 (시간대를 표현하는 타임존이 포함, 그리고 그 타임존에 맞는 오프셋 포함)

타임존이 있기 때문에 그 지역에 대한 썸머 타임 적용 o

// ZonedDateTime 클래스
public class ZonedDateTime {
     private final LocalDateTime dateTime;
     private final ZoneOffset offset;
     private final ZoneId zone;
}

 

- OffsetDateTime: 시간대를 고려한 날짜와 시간을 표현할 때 사용 (타임존이 없고, UTC로부터의 시간대 차이인 고정 오프셋이 포함)

타임존이 없기 때문에 그 지역에 대한 썸머 타임 적용 x

// OffsetDateTime 클래스
public class OffsetDateTime {
     private final LocalDateTime dateTime;
     private final ZoneOffset offset;
}

 

 

package time.global;

import java.time.ZoneId;

// 자바는 타임존을 ZoneId 클래스로 제공한다
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());
        }
        System.out.println();
        System.out.println("ZoneId.systemDefault = " + ZoneId.systemDefault()); // 시스템이 사용하는 기본 ZoneId를 반환
        System.out.println("seoulZoneId = " + ZoneId.of("Asia/Seoul"));
    }
}

.

.

.

 

package time.global;

import java.time.*;

public class DateTimeMain {
    public static void main(String[] args) {
        // ZonedDateTime 클래스
        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);
        System.out.println();

        
        // OffsetDateTime 클래스
        OffsetDateTime nowOdt = OffsetDateTime.now();
        System.out.println("nowOdt = " + nowOdt);

        ldt = LocalDateTime.of(2030, 1, 1, 13, 30, 50);
        System.out.println("ldt = " + ldt);
        OffsetDateTime odt = OffsetDateTime.of(ldt, ZoneOffset.of("+01:00"));
        System.out.println("odt = " + odt);
    }
}

 

 

 

[기계 중심의 시간]

- Instant: 날짜와 시간을 나노초 정밀도로 표현하며, 197011000(UTC 기준)를 기준으로 경과한 시간이 계산된다.

쉽게 이야기해서 Instant 내부에는 초 데이터만 들어있다 (나노초 포함)

"전 세계 모든 서버 시간을 똑같이 맞출 수 있다" 항상 UTC 기준이므로 한국에 있는 Instant, 미국에 있는 Instant의 시간이 똑같다.

시간대의 변화 없이 순수하게 시간의 흐름(: 지속 시간 계산)만을 다루고 싶을 때 Instant가 적합하다. 이는 시간대 변환의 복잡성 없이 시간 계산을 할 수 있게 해준다. 또는 데이터베이스에 날짜와 시간 정보를 저장하거나, 다른 시스템과 날짜와 시간 정보를 교환할 때 Instant를 사용하면, 모든 시스템에서 동일한 기준점(UTC)을 사용하게 되므로 데이터의 일관성을 유지하기가 쉽다.

 

활용 예: 서버 로그 기록, 트랜잭션 타임스탬프, 서버 간의 시간 동기화, epoch 시간 기반 계산이 필요할 때, 간단히 두 시간의 차이를 구할 때 등

// Instant 클래스
public class Instant {
     private final long seconds;
     private final int nanos;
     ...
}
package time;

import java.time.Instant;
import java.time.ZonedDateTime;

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);
        System.out.println("later = " + later);

        // 조회
        long laterEpochSecond = later.getEpochSecond();
        System.out.println("laterEpochSecond = " + laterEpochSecond);
    }
}

 

(참고) Epoch 시간
Epoch time(에포크 시간) 또는 Unix timestamp는 컴퓨터 시스템에서 시간을 나타내는 방법 중 하나이다. 이는 197011일 00:00:00 UTC부터 현재까지 경과된 시간을 초 단위로 표현한 것이다. , Unix 시간은 197011일 이후로 경과한 전체 초의 수로, 시간대에 영향을 받지 않는 절대적인 시간 표현 방식이다.
참고로 Epoch 라는 뜻은 어떤 중요한 사건이 발생한 시점을 기준으로 삼는 시작점을 뜻하는 용어이다.

Instant는 바로 이 Epoch 시간을 다루는 클래스이다.

 

 

 

[시간의 양 2가지 개념]

 

- Period: "특정 시점의 시간"

두 날짜 사이의 간격을 년, , 일 단위로 나타낸다

// Period 클래스
public class Period {
     private final int years;
     private final int months;
     private final int days;
}
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() + "일");
    }
}

 

 

- Duration: "시간의 간격"

두 시간 사이의 간격을 시, , (나노초) 단위로 나타낸다

// Duration 클래스
public class Duration {
     private final long seconds;
     private final int nanos;
}
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() + "분");
    }
}

 

 

 

 

<날짜와 시간의 핵심 인터페이스1>

TemporalAccessor는 읽기 전용 접근을, Temporal은 읽기와 쓰기(조작) 접근 모두를 지원한다

 

 

<날짜와 시간의 핵심 인터페이스2>

시간 단위와 시간 필드

 

 

 

 

 

 

참고 및 출처: 김영한의 실전 자바 - 중급 1편

https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EC%A4%91%EA%B8%89-1