Java - 날짜와 시간

유현수·2024년 2월 20일
post-thumbnail

날짜와 시간

Java에서 날짜와 시간을 다루기 위해 Date는 JDK1.0부터, Calendar는 JDK1.1부터 제공되어 왔습니다. 그리고 시간이 지나 JDK1.8부터 몇가지 단점을 개선한 java.time 패키지가 등장하게 됩니다.

각 클래스, 패키지의 사용법을 알아보고 서로 어떻게 다른지도 알아봅시다.

Calendar와 Date

java.util.Date

Date 클래스는 대부분의 메서드가 deprecated 되어 잘 사용되지 않습니다. 하지만 여전히 많은 API, 메서드들이 Date 클래스를 파라미터나 리턴 타입으로 사용합니다. 따라서 DateCalender 클래스 간의 변환 방법 정도만 알아봅시다.

// Calendar를 Date로 변환
Calendar cal = Calendar.getInstance();
Date d = new Date(cal.getTimeInMillis());

// Date를 Calendar로 변환
Date date = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);

java.util.Calendar

Calendar 클래스는 날짜와 시간을 다루기 위해 매우 자주 사용하는 클래스입니다. 자주 사용하는 메서드를 아래 예제로 알아봅시다.

import java.util.Calendar;

public class Main {
    public static void main(String[] args) {
        // getInstance로 얻은 인스턴스는 기본적으로 현재 시스템의 날짜와 시간에 대한 정보를 담고 있다.
        Calendar calendar1 = Calendar.getInstance();
        Calendar calendar2 = Calendar.getInstance();

        // set 메서드를 사용해 원하는 날짜나 시간을 설정할 수 있다.
        // calendar의 1 ~ 12월은 0 ~ 11로 표현된다.
        calendar1.set(2024, 3, 1);      // 2024.4.1
        calendar2.set(2024, 3, 30);     // 2024.4.31

        // get 메서드를 사용해 연, 월, 일 등을 조회할 수 있다.
        int year = calendar1.get(Calendar.YEAR);
        int month = calendar1.get(Calendar.MONTH);
        int date = calendar1.get(Calendar.DATE);
        int hour = calendar1.get(Calendar.HOUR);                // 0 ~ 11
        int hourOfDay = calendar1.get(Calendar.HOUR_OF_DAY);    // 0 ~ 23
        int minute = calendar1.get(Calendar.MINUTE);
        int second = calendar1.get(Calendar.SECOND);
        int millisec = calendar1.get(Calendar.MILLISECOND);

        // getTimeInMillis로 변한하여 차이를 구할 수 있다. 단위는 millisec
        long diff = calendar2.getTimeInMillis() - calendar1.getTimeInMillis();
        System.out.println(diff);

        // add로 지정한 필드의 값을 변경할 수 있다.
        calendar1.add(Calendar.DATE, 29);    // 4월 30일이 된다.
        calendar1.add(Calendar.DATE, 1);     // 5월 1일이 된다. add는 다른 필드에도 영향을 미친다.

        // roll로 지정한 필드의 값을 변경할 수 있다.
        calendar2.roll(Calendar.DATE, 1);    // 4월 1일이 된다. roll은 다른 필드에 영향을 미치지 않는다.
        calendar2.roll(Calendar.DATE, 29);   // 4월 30일이 된다.
        calendar2.roll(Calendar.MONTH, -2);   // 2월 29일이 된다. 예외적으로 Date가 말일인 경우 Month를 변경하면 Date에 영향을 미친다.
    }

    public static String toString(Calendar calendar) {
        return calendar.get(Calendar.YEAR) + "." + (calendar.get(Calendar.MONTH) + 1) + "." + calendar.get(Calendar.DATE);
    }
}

SimpleDateFormat

DateCalendar 만으로 날짜를 출력하기에는 번거로움이 많습니다. 이를 해결하기 위한 클래스가 SimpleDateFormat 입니다. SimpleDateFormat 클래스를 사용해 다음과 같이 날짜 데이터 <-> 문자열 간 변환을 손쉽게 할 수 있습니다.

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class SimpleDateFormatEx {
    public static void main(String[] args) {
        // Calendar -> Date
        Calendar cal = Calendar.getInstance();
        cal.set(2024, 4, 1);
        Date date = cal.getTime();

        // Date -> String
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
        String dateTimeString = sdf.format(date);
        System.out.println(dateTimeString);

        // String -> Date
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date d = df.parse("2024.5.12");
            System.out.println(sdf.format(d));
        } catch (ParseException e) {}
    }
}

java.time 패키지

JDK1.8에서 DateCalendar의 단점을 개선한 java.time 패키지가 등장합니다.

java.time 패키지의 가장 큰 특징은 immutable 하다는 것입니다. DateCalendar 클래스는 객체를 직접 수정하는 방식을 사용했습니다. 따라서 thread-safe하지 못하다는 단점이 있었죠. 하지만 java.time 패키지는 객체를 수정하면 매번 새롭게 생성한 객체를 반환합니다. 따라서 thread-safe 합니다.

우선 java.time 패키지의 핵심 클래스와 사용법을 알아보고 그 뒤에 thread 관점에서 Date, Calendarjava.time 패키지의 클래스를 비교해보겠습니다.

java.time 패키지의 핵심 클래스

날짜와 시간, 시간대를 모두 가지고 있는 Calendar 클래스와 달리 java.time 패키지에서는 날짜와 시간, 시간대를 별도의 클래스로 분리해 놓았습니다.

날짜는 LocalDate, 시간은 LocalTime 클래스를 사용합니다. 둘 다 필요할 때는 LocalDateTime 클래스를 사용합니다.

1

여기에 시간대(time-zone)까지 다뤄야 한다면, ZonedDateTime 클래스를 사용합니다.

2

Date와 유사한 클래스로는 Instant 클래스가 있습니다. 날짜와 시간을 나노초 단위로 표현합니다. 날짜와 시간을 초단위로 표현하는 타임스탬프는 날짜와 시간을 정수로 표현할 수 있기 때문에 날짜와 시간의 차이 계산, 순서 비교에 유리해 데이터베이스에 많이 사용됩니다.

날짜, 시간의 간격을 표현하는 Period, Duration 클래스도 있습니다.

3

이제 각 클래스가 어떤 인터페이스를 상속하는지, 어떻게 사용하는지 등을 알아봅시다.

Temporal, TemporalAmount

앞서 소개한 클래스들의 상속관계는 다음과 같습니다.

4

이후 살펴볼 메서드들은 파라미터 타입이 Temporal로 시작하거나 TemporalAmount인 것들이 많습니다. 여기서 Temporal로 시작하는지, TemporalAmount인지만 구분하여도 날짜, 시간을 매개변수로 받는지, 날짜/시간의 간격을 받는지 구분할 수 있습니다.

LocalDate, LocalTime

LocalDate, LocalTime 클래스의 주요 사용법은 다음과 같습니다.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;

public class JavaTimePkgEx {
    public static void main(String[] args) {
        // now 메서드로 현재 날짜, 시간 얻기
        LocalDate today = LocalDate.now();
        LocalTime now = LocalTime.now();

        // of 메서드로 날짜, 시간 지정하기
        LocalDate someDay = LocalDate.of(2024, 5, 1);
        LocalTime someTime = LocalTime.of(23, 59, 59);

        // withXXX 메서드로 특정 필드 값 변경하기
        someDay = someDay.withYear(2025);
        someTime = someTime.withHour(12);

        // parse 메서드로 문자열 -> LocalDate, LocalTime 변환
        LocalDate anotherDay = LocalDate.parse("2024-06-01");
        LocalTime anotherTime = LocalTime.parse("23:59:59");

        // getXXX 메서드로 원하는 값 얻기
        int year = someDay.getYear();
        int month = someDay.getMonthValue();

        // get, getLong 메서드로 지정한 필드의 값 얻기
        int day = someDay.get(ChronoField.DAY_OF_MONTH);
        long sec = someTime.get(ChronoField.SECOND_OF_MINUTE);

        System.out.println(day);
        System.out.println(sec);
    }
}

LocalDateTime, ZonedDateTime

LocalDateTimeZonedDateTime의 주요 사용법은 다음과 같습니다.

import java.time.*;
import java.time.temporal.ChronoField;

public class JavaTimePkgEx {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2024, 5, 1);
        LocalTime time = LocalTime.of(12, 34, 56);

        // of, now 메서드로 초기화
        LocalDateTime dt = LocalDateTime.of(date, time);
        LocalDateTime dt2 = LocalDateTime.of(2024, 5, 1, 12, 34, 56);
        LocalDateTime dt3 = LocalDateTime.now();

        // LocalDateTime -> ZonedDateTime

        // 방법 1
        LocalDateTime dateTime = LocalDateTime.now();
        ZoneId zid = ZoneId.of("Asia/Seoul");
        ZonedDateTime zdt = dateTime.atZone(zid);

        // 방법 2 - 0시 0분 0초로 초기화
        ZonedDateTime zdt2 = LocalDate.now().atStartOfDay(zid);

        // 방법 3 - 현재 시각으로 다른 시간대 초기화
        ZoneId nyId = ZoneId.of("America/New_York");
        ZonedDateTime zdt3 = ZonedDateTime.now().withZoneSameInstant(nyId);

        // ZoneOffset
        // UTC로부터 얼마만큼 떨어져있는지 표현.
        ZoneOffset krOffset = ZonedDateTime.now().getOffset();  // 서울은 '+9'

        // OffsetDateTime
        // ZonedDateTime은 써머타임 적용 O, OffsetDateTime은 써머타임 적용 X
        // 따라서 서로 다른 시간대의 컴퓨터 간 통신에는 OffsetDateTime 사용
        ZonedDateTime zdt4 = ZonedDateTime.of(date, time, zid);
        OffsetDateTime odt = OffsetDateTime.of(date, time, krOffset);
        
        // ZonedDateTime을 다른 클래스로 변환
        LocalDate localDate = zdt.toLocalDate();
        LocalTime localTime = zdt.toLocalTime();
    }
}

Period, Duration

날짜의 차이를 계산하기 위한 Period와 시간의 차이를 계산하기 위한 Duration의 주요 사용법은 다음과 같습니다.

import java.time.*;
import java.time.temporal.ChronoUnit;

public class JavaTimePkgEx {
    public static void main(String[] args) {
        LocalDate date1 = LocalDate.of(2014, 1, 1);
        LocalDate date2 = LocalDate.of(2015, 12, 31);

        Period pe = Period.between(date1, date2);
        long year = pe.get(ChronoUnit.YEARS);
        long month = pe.get(ChronoUnit.MONTHS);
        long day = pe.get(ChronoUnit.DAYS);

        LocalTime time1 = LocalTime.of(00, 00, 00);
        LocalTime time2 = LocalTime.of(12, 34, 56);

        Duration du = Duration.between(time1, time2);

        long sec = du.get(ChronoUnit.SECONDS);
        long nano = du.get(ChronoUnit.NANOS);
    }
}

파싱과 포맷

formatting과 관련된 클래스들은 java.time.format 패키지에 들어있는데 이 중 DateTimeFormatter가 핵심입니다. 이 클래스에는 자주 쓰이는 다양한 형식들을 기본적으로 정의하고 있으며, 그 외의 형식이 필요하다면 직접 정의해서 사용할 수도 있습니다.

DateTimeFormatter의 주요 사용법은 다음과 같습니다.

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;

public class JavaTimePkgEx {
    public static void main(String[] args) {
        // 로케일에 종속된 포매팅
        DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
        String shortFormat = formatter1.format(LocalDate.now());

        // 날짜, 시간 -> 문자열
        DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
        LocalDateTime dateTime = LocalDateTime.now();
        String format = formatter2.format(dateTime);

        // 문자열 -> 날짜, 시간
        DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy.MM.dd");
        LocalDate newDate = LocalDate.parse("2024.03.01", formatter3);
    }
}

thread-safe 여부 비교하기

https://woonys.tistory.com/entry/JavaSimpleDateFormat%EC%9D%84-%EC%93%B0%EB%A9%B4-%EC%95%88%EB%90%9C%EB%8B%A4%EA%B3%A0-featThread-Safe

profile
"Life isn't about finding yourself. Life is about creating yourself."

0개의 댓글