[Java] Java의 정석 | Chapter 10 날짜와 시간 & 형식화 date, time and formatting

숙취엔 꿀물.·2023년 12월 13일

Java

목록 보기
10/13
post-thumbnail

👉 1. 날짜와 시간

1.1 Calendar와 Date

Date는 날짜와 시간을 다룰 목적으로 JDK 1.0부터 제공되어온 클래스이다.
JDK 1.8부터 java.time 패키지 로 기존의 단점들을 개선한 새로운 클래스들이 추가되었어도

Calendar 와 Date의 예제를 이해하고 필요할 때 활용할 정도로는 알아두자.

Calendar와 GregorianCalendar

Calendar는 추상클래스이기 때문에 직접 객체를 생성할 수 없고, 메서드를 통해서 완전히 구현된 클래스의 인스턴스를 얻어야 한다.

Calendar cal = new Calendar();	// 에러!! 추상클래스는 인스턴스를 생성할 수 없다.

// Ok, getInstance() 메서드는 Calendar 클래스를 구현한 클래스의 인스턴스를 반환한다.
Calendar cal = Calendar.getInstance();

Calendar를 상속받아 완전히 구현한 클래스 : GregorianCalendar , BuddhistCalendar

getInstance() 는 시스템의 국가와 지역설정을 확인해서 태국인 경우에는 BuddhistCalendar 의 인스턴스를 반환하고, 그 외에는 GregorianCalendar 의 인스턴스를 반환한다.

class MyApplication {
	public static void main(String args[]) {
    	Calendar cal = new GregorianCalendar();	// 경우에 따라 이 부분을 변경해야한다.
        ...
    }
}
만일 위와 같이 특정 인스턴스를 생성하도록 프로그램이 작성되어 있다면,

다른 종류의 역법(calendar)을 사용하는 국가에서 실행한다던가, 새로운 역법이 추가된다던가
-> 즉, 다른 종류의 인스턴스를 필요로 하는 경우에 MyApplication을 변경해야 하는데

아래와 같이 메서드를 통해 인스턴스를 얻어오도록 하면 MyApplication을 변경하지 않아도 된다.
class MyApplication {
	public static void main(String args[]) {
    	Calendar cal = Calendar.getInstance();
        ...
	}
}

Date 와 Calendar 간의 변환

Calendar가 새로 추가되면서 Date는 대부분 메서드가 deprecated 되어 잘 사용되지 않는다.

-> 그럼에도 Date를 필요로 하는 메서드들이 있기 때문에 서로 변환할 일이 생긴다.

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

  2. Date를 Calendar로 변환
    Date d = new Date();
    ...
    Calendar cal = Calendar.getInstance();
    cal.setTime(d);
import java.util.Calendar;

public class CalendarEx1 {
    public static void main(String[] args) {
        // 기본적으로 현재날짜와 시간으로 설정된다.
        Calendar today = Calendar.getInstance();
        System.out.println("이 해의 년도 : " + today.get(Calendar.YEAR));
        System.out.println("월(0~11, 0:1월) : " + today.get(Calendar.MONTH));
        System.out.println("이 해의 몇 쨰 주 : " + today.get(Calendar.WEEK_OF_YEAR));
        System.out.println("이 달의 몇 째 주 : \n" + today.get(Calendar.WEEK_OF_MONTH));

        // DATE와 DAY_OF_MONTH는 같다.
        System.out.println("이 달의 몇 일 : " + today.get(Calendar.DATE));
        System.out.println("이 달의 몇 일 : " + today.get(Calendar.DAY_OF_MONTH));
        System.out.println("이 해의 몇 일 : " + today.get(Calendar.DAY_OF_YEAR));
        System.out.println("요일(1~7, 1:일요일) : " + today.get(Calendar.DAY_OF_WEEK));  // 1:일요일, ... 7:토요일
        System.out.println("이 달의 몇 쨰 요일 : " + today.get(Calendar.DAY_OF_WEEK_IN_MONTH));
        System.out.println("오전_오후(0:오전, 1:오후) : " + today.get(Calendar.AM_PM));
        System.out.println("시간(0~11) : " + today.get(Calendar.HOUR));
        System.out.println("시간(0~23) : " + today.get(Calendar.HOUR_OF_DAY));
        System.out.println("분(0~59) : " + today.get(Calendar.MINUTE));
        System.out.println("초(0~59) : " + today.get(Calendar.SECOND));
        System.out.println("1000분의 1초(0~999) : \n" + today.get(Calendar.MILLISECOND));

        // 천분의 1초를 시간으로 표시하기 위해 3600000으로 나누었다. (1시간 = 60 * 60초)
        System.out.println("TimeZone(-12~+12) : " + (today.get(Calendar.ZONE_OFFSET) / (60 * 60 * 1000)));
        System.out.println("이 달의 마지막 날 : " + today.getActualMaximum(Calendar.DATE));
    }
}

getInstance()를 통해서 얻은 인스턴스는 기본적으로 현재 시스템의 날짜와 시간에 대한 정보를 담고 있다.
원하는 날짜나 시간으로 설정하려면 set메서드를 사용하면 된다.

void set(int field, int value)
void set(int year, int month, int date)
void set(int year, int month, int date, int hourOfDay, int minute)
void set(int year, int month, int date, int hourOfDay, int minute, int second)

add(int field, int amount) // 원하는 만큼 증가 또는 감소, 즉 일정기간 전후의 날짜와 시간

roll(int field, int amount) // add와 비슷하나 차이점은 다른 필드에 영향을 미치지 않는다는 것

예를 들면, add 메서드로 날짜필드(Calendar.DATE)의 값을 31만큼 증가시켰다면 다음 달로 넘어가므로 월 필드(Calendar.MONTH)의 값도 1증가하지만,

roll 메서드 는 월 필드의 값은 변하지 않고 일 필드의 값만 바뀐다.

  • 예외는 일 필드가 말일(end of month) 일 때, 월 필드(Calendar.MONTH)를 변경하면 일 필드(Calendar.DATE)에 영향을 미칠 수 있다. (...경우에 따라)

날짜 계산을 위한 몇 가지 메서드 구현

public class CalendarEx9 {
    public static void main(String[] args) {
        System.out.println("2014. 5. 31 : " + getDayOfWeek(2014, 5, 31));
        System.out.println("2012. 6. 1 : " + getDayOfWeek(2012, 6, 1));
        System.out.println("2014. 5. 1 - 2014. 4. 28 : " + dayDiff(2014, 5, 1, 2014, 4, 28));
        System.out.println("2015. 6. 29 : " + convertDateToDay(2015, 6, 29));
        System.out.println("735778 : " + convertDateToDay(735778));
    }

    // 각 달의 마지막 일
    public static int[] endOfMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    // 매개변수 year가 윤년이면 true를 그렇지 않으면 false를 반환한다.
    public static boolean isLeapYear(int year) {
        return ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0));
    }

    // 두 날짜간의 차이를 일단위로 반환한다.
    public static int dayDiff(int y1, int m1, int d1, int y2, int m2, int d2) {
        return convertDateToDay(y1, m1, d1) - convertDateToDay(y2, m2, d2);
    }

    // 지정한 날짜의 요일을 반환한다.(1~7, 1이 일요일)
    public static int getDayOfWeek(int year, int month, int day) {
        return convertDateToDay(year, month, day) % 7 + 1;
    }

    // 일단위의 값을 년월일의 형태의 문자열로 변환하여 반환한다.
    public static String convertDateToDay(int day) {
        int year = 1;
        int month = 0;

        while (true) {
            int aYear = isLeapYear(year) ? 366 : 365;
            if (day > aYear) {
                day -= aYear;
                year++;
            } else {
                break;
            }
        }

        while (true) {
            int endDay = endOfMonth[month];
            // 윤년이고 윤달이 포함되어 있으면, 1일을 더한다.
            if (isLeapYear(year) && month == 1) endDay++;

            if (day > endDay) {
                day -= endDay;
                month++;
            } else {
                break;
            }
        }

        return year + "-" + (month + 1) + "-" + day;
    }

    // 년월일을 입력받아서 일단위로 변환한다.
    public static int convertDateToDay(int year, int month, int day) {
        int numOfLeapYear = 0; // 윤년의 수

        // 전년도까지의 윤년의 수를 구한다.
        for (int i = 1; i < year; i++) {
            if (isLeapYear(i))
                numOfLeapYear++;
        }

        // 전년도까지의 일수를 구한다.
        int tolastYeardaySum = (year - 1) * 365 + numOfLeapYear;

        // 올해의 현재 월까지의 일수 계산
        int thisyearDaySum = 0;

        for (int i = 0; i < month - 1; i++)
            thisyearDaySum += endOfMonth[i];

        // 윤년이고, 2월이 포함되어 있으면 1일을 증가시킨다.
        if (month > 2 && isLeapYear(year))
            thisyearDaySum++;

        thisyearDaySum += day;

        return tolastYeardaySum + thisyearDaySum;
    }
}



2. 형식화 클래스

성적의 일정한 처리나 날짜를 형식에 맞게 출력하려면 꽤나 복잡하다. 이러한 문제들을 쉽게 해결할 수 있는 방법을 제공하는 것이 바로 형식화 클래스이다.

java.text 패키지에 포함되어 있으며 숫자, 날짜, 텍스트 데이터를 일정한 형식에 맞게 표현할 수 있는 방법을 객체지향적으로 설계하여 표준화하였다.

백문이 불여일견 예제를 통해 이해하자

2.1 DecimalFormat

형식화 클래스 중에서 숫자를 형식화 하는데 사용되는 것이 DecimalFormat

  • 숫자 데이터를 정수, 부동소수점, 금액 드으이 다양한 형식으로 표현 가능
  • 반대로 일정한 형식의 텍스트 데이터를 숫자로 쉽게 변환하는 것도 가능
기호의미패턴결과(1234567.89)
010진수(값이 없을 때는 0)0
0.0
0000000000.0000
1234568
1234567.9
0001234567.8900
#10진수#
#.#
##########.####
1234568
1234567.9
1234567.89
.소수점#.#1234567.9
-음수부호#.#-
-#.#
1234567.9-
-1234567.9
,단위 구분자#,###.##
#,####.##
1,234,567.89
123,4567.89
E지수기호#E0
0E0
##E0
00E0
####E0
0000E0
#.#E0
0.0E0
0.000000000E0
00.00000000E0
000.0000000E0
#.#########E0
##.########E0
###.#######E0
.1E7
1E6
1.2E6
12E5
123.5E4
1235E3
1.2E6
1.2E6
1.234567890E6
12.34567890E5
123.4567890E4
1.23456789E6
1.23456789E6
1.23456789E6
;패턴구분자#,###.##+;#,###.##-1,234,567.89+(양수일 때)
1,234,567.89-(음수일 때)
%퍼센트#.#%123456789%
\u2030퍼밀(퍼센트 x 10)#.#\u20301234567890‰
\u00A4통화\u00A4 #,###\ 1,234,568
\escape 문자\#\#,###
\\#,###
#1,234,568
\1,234,568

음... 예제까진 필요없고 필요할 때 찾아서 쓰면 될 것 같다..


2.2 SimpleDateFormat

이제는 날짜를 출력하는 방법에 대해서 배울 차례이다.

기호의미보기
G연대(BC,AD)AD
Y년도2006
M월(1~12 또는 1월 12월)10 또는 10월, OCT
w년의 몇 번째 주 (1~53)50
W월의 몇 번째 주 (1~5)4
D년의 몇 번째 일 (1~366)100
d월의 몇 번째 일 (1~31)15
F월의 몇 번째 요일 (1~5)1
E요일
a오전/오후(AM, PM)PM
H시간(0~23)20
k시간(1~24)13
K시간(0~11)10
h시간(1~12)11
m분(0~59)35
s초(0~59)55
S천분의 일초(0~999)253
zTime zone(General time zone)GMT+9:00
ZTime zone(RFC 822 time zone)+0900
\escape문자(특수문자를 표현하는데 사용)없음

원하는 출력형식의 패턴을 작성하여 SimpleDateFormat 인스턴스를 생성한 다음, 출력하고자 하는 Date 인스턴스를 가지고 format(Date d)를 호출하면 지정한 출력형식에 맞게 변환된 문자열을 얻게 된다.

Date today = new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");

// 오늘 날짜를 yyyy-MM-dd 형태로 변환하여 반환한다.
String result = df.format(today);

// 추가 예시)
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DateFormatEx2 {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        cal.set(2005, 9, 3);  // 2005년 10월 3일 - Month는 0~11의 범위를 갖는다.

        Date day = cal.getTime();

        SimpleDateFormat sdf1, sdf2, sdf3, sdf4;
        sdf1 = new SimpleDateFormat("yyyy-MM-dd");
        sdf2 = new SimpleDateFormat("yy-MM-dd E요일");
        sdf3 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        sdf4 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss a");

        System.out.println(sdf1.format(day));
        System.out.println(sdf2.format(day));
        System.out.println(sdf3.format(day));
        System.out.println(sdf4.format(day));
    }
}

2.3 ChoiceFormat

ChoiceFormat 은 특정 범위에 속하는 값을 문자열로 변환해준다.

연속적 또는 불연속적인 범위의 값들을 처리하는 데 있어서 if문, switch문이 적절하지 않을 때,

잘 사용하면 복잡하게 처리될 수 밖에 없었던 코드를 간단하고 직관적으로 만들 수 있다.

import java.text.ChoiceFormat;

public class ChoiceFormatEx1 {
    public static void main(String[] args) {
        double[] limits = {60, 70, 80, 90};    // 낮은 값부터 큰 값의 순서로 적어야한다.
        // limits, grades 간의 순서와 개수를 맞추어야 한다.
        String[] grades = {"D", "C", "B", "A"};

        int[] scores = {100, 95, 88, 70, 52, 60, 70};

        ChoiceFormat form = new ChoiceFormat(limits, grades);

        for (int i = 0; i < scores.length; i++) {
            System.out.println(scores[i] + ":" + form.format(scores[i]));
        }
    }
}

limts 배열은 범위의 경계값을 저장하는데 사용하였고, grades 배열 은 범위에 포함된 값을 치환할 문자열을 저장하는데 사용되었다.

경계값은 double형으로 반드시 모두 오름차순으로 정렬되어 있어야 하며, 치환 될 문자열의 개수는 경계값에 의해 정의된 범위의 개수와 일치해야한다.

-> 그렇지 않으면 IllegalArgumentException이 발생한다.


2.4 MessageFormat

MessageFormat 은 데이터를 정해진 양식에 맞게 출력할 수 있도록 도와준다.

C#에서 사용하는 format 형식과 비슷한 느낌이다.

import java.text.MessageFormat;

public class MessageFormatEx1 {
    public static void main(String[] args) {
        String msg = "Name: {0} \nTel: {1} \nAge: {2} \nBirthday: {3}";

        Object[] arguments = {
                "이자바", "02-123-1234", "27", "07-09"
        };

        String result = MessageFormat.format(msg, arguments);
        System.out.println(result);
    }
}

MessageFormat 에 사용할 양식인 문자열 msg를 작성할 때 {숫자} 로 표시된 부분이 데이터가 출력될 자리이다.

순차적일 필요는 없고 여러 번 반복해서 사용할 수도 있다.



👉 3. java.time 패키지

Date와 Calendar가 가지고 있던 단점들을 해소하기 위해 JDK 1.8부터 java.time 패키지가 추가되었다.

패키지설명
java.time날짜와 시간을 다루는데 필요한 핵심 클래스들을 제공
java.time.chrono표준(ISO)이 아닌 달력 시스템을 위한 클래스들을 제공
java.time.format날짜와 시간을 파싱하고, 형식화하기 위한 클래스들을 제공
java.time.temporal날짜와 시간의 필드(field)와 단위(unit)를 위한 클래스들을 제공
java.time.zone시간대(time-zone)와 관련된 클래스들을 제공

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

java.time 패키지 에서는 날짜와 시간을 별도의 클래스로 분리해 놓았다.

  • 시간을 표현할 때는 LocalTime 클래스 / 날짜를 표현할 때는 LocalDate 클래스 를 사용

  • 둘 모두 필요할 때는 LocalDateTime 클래스 를 사용하면 된다.

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


Period 와 Duration

날짜와 시간의 간격을 표현하기 위한 클래스로

  • Period 는 두 날짜간의 차이를 표현하기 위한 것이고,
  • Duration 은 시간의 차이를 표현하기 위한 것이다.

객체 생성하기 - now(), of()

now()는 현재 날짜와 시간을 저장하는 객체를 생성한다.

LocalDate date = LocalDate.now();				// 2015-11-23
LocalTime time = LocalTime.now();				// 21:54:01.875
LocalDateTime dateTime = LocalDateTime.now();	// 2015-11-23T21:54:01.975
ZonedDateTime dateTimeInKr = ZonedDateTime.now();
												// 2015-11-23T21:54:01.875+09:00[Asia/Seoul]

of()는 단순히 해당 필드의 값을 순서대로 지정해 주기만 하면 된다.

LocalDate date = LocalDate.of(2015, 11, 23);	// 2015년 11월 23일
LocalTime time = LocalTime.of(23, 59, 59);		// 23시 59분 59초

LocalDateTime dateTime = LocalDateTime.of(date, time);
ZonedDateTime zDateTime = ZonedDateTIme.of(dateTime, ZoneId.of("Asia/Seoul"));

Temporal 과 TemporalAmount

  • LocalDate, LocalTime, LocalDateTime, ZonedDateTime 등 날짜와 시간을 표현하기 위한 클래스들은 모두 Temporal, TemporalAccessor, TemporalAdjuster 인터페이스를 구현했고,

  • Duration 과 Period 는 TemporalAmount 인터페이스를 구현했다.

메서드 중에서 매개변수의 타입이 Temporal로 시작하는 것들은 대부분 날짜와 시간을 위한 것이므로,

✔ TemporalAmount 인지 아닌지만 확인하면 된다.


TemporalUnit 과 TemporalField

날짜와 시간의 단위를 정의해 놓은 것이 TemporalUnit 인터페이스이고, 이 인터페이스를 구현한 것이 열거형 ChronoUnit 이다.

TemporalField 는 년, 월, 일 등 날짜와 시간의 필드를 정의해 놓은 것으로, 열거형 ChronoField가 이 인터페이스를 구현했다.

LocalTime now = LocalTime.now();	// 현재 시간
int minute = now.getMinute();		// 현재 시간에서 분(minute)만 뽑아낸다.
// int minute = now.get(ChronoField.MINUTE_OF_HOUR);	// 위의 문장과 동일

LocalDate today = LocalDate.now();	// 오늘
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);	// 오늘에 1일을 더한다.
LocalDate tomorrow = today.plusDays(1);		// 위의 문장과 동일

아래는 get()과 plus()의 정의

int get(TemporalField field)
LocalDate plus(long amountToAdd, TemporalUnit unit)

3.2 LocalDate 와 LocalTime

java.time 패키지 의 가장 기본이 되는 클래스, 이 둘만 이해하면 나머지는 아주 쉬워진다.

객체를 생성하는 방법은 static 메서드인 now()of()를 사용한다.

LocalDate today = LocalDate.now();	// 오늘의 날짜
LocalTime now = LocalDate.now();	// 현재 시간

LocalDate birthDate = LocalDate.of(1999, 10, 13);	// 1999년 10월 13일
LocalTime birthTime = LocalTime.of(23, 59, 59);		// 23시 59분 59초

/* ----------------------------다음과 같이도 가능------------------------------ */
LocalDate birthDate = LocalDate.ofYearDay(1999, 365);	// 1999년 12월 31일
LocalTime birthTime = LocalTime.ofSecondDay(86399);		// 23시 59분 59초

LocalDate birthDate = LocalDate.parse("1999-12-31");	// 1999년 12월 31일
LocalTime birthTime = LocalTime.parse("23:59:59");		// 23시 59분 59초

특정 필드의 값 가져오기 - get(), getXXX()

클래스메서드설명(1999-12-31 23:59:59)
LocalDateint getYear()년도(1999)
int getMonthValue()월(12)
Month getMonth()월(DECEMBER) getMonth.getValue() = 12
int getDayOfMonth()일(31)
int getDayOfYear()같은 해의 1월 1일부터 몇번쨰 일(365)
DayOfWeek getDayOfWeek()요일(FRIDAY) getDayOfWeek().getValue() = 5
int lengthOfMonth()같은 달의 총 일수(31)
int lengthOfYear()같은 해의 총 일수(365), 윤년이면 365
boolean isLeapYear()윤년여부 확인(false)
LocalTimeint getHour()시(23)
int getMinute()분(59)
int getSecond()초(59)
int getNano()나노초(0)

  • 메서드들의 매개변수로 사용할 수 있는 필드의 목록
TemporalField(ChronoField)설명
ERA시대
YEAR_OF_ERA, YEAR
MONTH_OF_YEAR
DAY_OF_WEEK요일(1:월요일, 2:화요일, ... 7:일요일)
DAY_OF_MONTH
AMPM_OF_DAY오전/오후
HOUR_OF_DAY시간(0~23)
CLOCK_HOUR_OF_DAY시간(1~24)
HOUR_OF_AMPM시간(0~11)
CLOCK_HOUR_OF_AMPM시간(1~12)
MINUTE_OF_HOUR
SECOND_OF_HOUR
MILLI_OF_SECOND천분의 일초(=10^-3초)
MICRO_OF_SECOND *백만분의 일초(=10^-6초)
NANO_OF_SECOND *10억분의 일초(=10^-9초)
DAY_OF_YEAR그 해의 몇번째 날
EPOCH_DAY *EPOCH(1970.1.1)부터 몇번째 날
MINUTE_OF_DAY그 날의 몇 번째 분(시간을 분으로 환산)
SECOND_OF_DAY그 날의 몇 번째 초(시간을 초로 환산)
MILLI_OF_DAY그 날의 몇 번째 밀리초(=10^-3초)
MICRO_OF_DAY *그 날의 몇 번째 마이크로초(=10^-6)
NANO_OF_DAY *그 날의 몇 번째 나노초(=10^-9)
ALIGNED_WEEK_OF_MONTH그 달의 n번째 주(1~7일 1주, 8~14 2주, ...)
ALIGNED_WEEK_OF_YEAR그 해의 n번째 주(1월 1~7일 1주, 8~14일 2주, ...)
ALIGNED_DAY_OF_WEEK_IN_MONTH요일(그 달의 1일을 월요일로 간주하여 계산)
ALIGNED_DAY_OF_WEEK_IN_YEAR요일(그 해의 1월 1일을 월요일로 간주하여 계산)
INSTANT_SECONDS년월일을 초단위로 환산(1970-01-01 00:00:00 UTC를 0초로 계산)
Instant에만 사용가능
OFFSET_SECONDSUTC와의 시차. ZoneOffset에만 사용가능
PROLEPTIC_MONTH년월을 월단위로 환산(2015년 11월 = 2015*12 + 11)

이런거 다 쓸 일이 있을진 모르겠지만 그냥 한번씩 훑어보며 적어두기..


필드의 값 변경하기 - with(), plus(), minus()

날짜와 시간에서

  • 특정 필드 값을 변경하려면 with로 시작하는 메서드를 사용
  • 특정 필드 값을 더하거나 빼려면 plus() 와 minus() 를 사용
LocalDate with(TemporalField field, long newValue)

LocalDate withYear(int year)
LocalDate withMonth(int month)
LocalDate withDayOfMonth(int dayOfMonth)
LocalDate withDayOfYear(int dayOfYear)

LocalTime withHour(int hour)
LocalTime withMinute(int minute)
LocalTime withSecond(int second)
LocalTime withNano(int nanoOfSecond)

// plus()와 minus()도 비슷하다. 쓰기 귀찮앙..

날짜와 시간의 비교 - isAfter(), isBefore(), isEqual()

우선, LocalDate와 LocalTime도 compareTo() 가 적절히 오버라이딩되어 있어서, 아래와 같이 compareTo()로 비교할 수 있다.

보다 편리하게 비교할 수 있는 메서드들도 추가로 제공

int result = date1.compareTo(date2);	// 같으면 0, date1이 이전이면 -1, 이후면 1

// 추가 메서드
boolean isAfter(ChronoLocalDate other)
boolean isBefore(ChronoLocalDate other)
boolean isEqual(ChronoLocalDate other)	// LocalDate에만 있음

equals()가 있는데도, isEqual()을 제공하는 이유는 연표(chronology)가 다른 두 날짜를 비교하기 위해서이다.

LocalDate kDate = LocalDate.of(1999, 12, 31);
JapaneseDate jDate = JapaneseDate.of(1999, 12, 31);

System.out.println(kDate.equals(jDate));	// false , YEAR_OF_ERA가 다름
System.out.println(kDate.isEqual(jDate));	// true

Instant

Instant 는 에포크 타임(EPOCH TIME, 1970-01-01 00:00:00 UTC) 부터 경과된 시간을 나노초 단위로 표현한다.

사람에겐 불편하지만, 단일 진법으로만 다루기 때문에 계산하기 쉽다.

Instant now = Instant.now();
Instant now2 = Instant.ofEpochSecond(now.getEpochSecond());
Instant now3 = Instant.ofEpochSecond(now.getEpochSecond(), now.getNano());

Instant는 항상 UTC(+00:00)를 기준으로 하기 때문에, LocalTime과 차이가 있을 수 있다.

  • UTC : Coordinated Universal Time, 세계 협정시, 1972년 1월 1일부터 시행된 국제 표준시
  • 한국은 시간대가 +09:00

Instant 와 Date간의 변환

static Date		from(Instant instant)	// Instant -> Date
Instant			toInstant()				// Date -> Instant

3.4 LocalDateTime 과 ZonedDateTime

위에서 말한 내용의 추가적인 부분 !

LocalDate 와 LocalTime으로 LocalDateTime 만들기

LocalDate date = LocalDate.of(2015, 12, 31);
LocalTime time = LocalTime.of(12, 34, 56);

LocalDateTime dt = LocalDateTime.of(date, time);
LocalDateTime dt2 = date.atTime(time);
LocalDateTime dt3 = time.atDate(date);
LocalDateTime dt4 = date.atTime(12, 34, 56);
LocalDateTime dt5 = time.atDate(LocalDate.of(2015, 12, 31));
LocalDateTime dt6 = date.atStartOfDay();	// dt6 = date.atTime(0,0,0);

물론 LocalDateTime에도 of()와 now()가 정의되어 있다.


반대로 LocalDateTime -> LocalDate, LocalTime

LocalDateTime dt = LocalDateTime.of(2015,12,31, 12,34,56);

LocalDate date = dt.toLocalDate();	// LocalDateTime -> LocalDate
LocalTime time = dt.toLocalTime();	// LocalDateTime -> LocalTime

LocalDateTime -> ZonedDateTime

LocalDate에 시간 정보를 추가하는 atTime()을 써서 LocalDateTime을 얻는 것 처럼, LocalDateTime에 atZone() 으로 시간대 정보를 추가하면, ZonedDateTime 을 얻을 수 있다.

ZoneId zid = ZoneId.of("Asia/Seoul");
ZonedDateTime zdt = dateTime.atZone(zid);

System.out.println(zdt);	// 2015-11-27T17:47:50.451+09:00[Asia/Seoul]

ZoneOffset

UTC로부터 얼마만큼 떨어져 있는지를 표현

ZoneOffset krOffset = ZonedDateTime.now().getOffset();
// ZoneOffset krOffset = ZoneOffset.of("+9");	// +-h, +-hh, +-hhmm, +-hh:mm
int krOffsetInsec = krOffset.get(ChronoField.OFFSET_SECONDS);	// 32400초

OffsetDateTime

ZonedDateTime은 ZoneId로 구역을 표현하는데, ZoneOffset 을 사용하는 것이 OffsetDateTime 이다.

ZonedDateTime zdt = ZonedDateTime.of(date, time, zid);
OffsetDateTime odt = OffsetDateTime.of(date, time, krOffset);

// ZonedDateTime -> OffsetDateTime
OffsetDateTime odt = zdt.toOffsetDateTime();

3.5 TemporalAdjusters

지난 주 토요일이 며칠인지, 또는 이번 달의 3번째 금요일이 며칠인지와 같은 날짜계산을 plus(), minus() 대신에 더 편하게 정의해놓은 클래스

메서드설명
firstDayOfNextYear()다음 해의 첫 날
firstDayOfNextMonth()다음 달의 첫 날
firstDayOfYear()올 해의 첫 날
firstDayOfMonth()이번 달의 첫 날
lastDayOfYear()올 해의 마지막 날
lastDayOfMonth()이번 달의 마지막 날
firstInMonth (DayOfWeek dayOfWeek)이번 달의 첫 번째 ?요일
lastInMonth (DayOfWeek dayOfWeek)이번 달의 마지막 ?요일
previous (DayOfWeek dayOfWeek)지난 ?요일(당일 미포함)
previousOrSame (DayOfWeek dayOfWeek)지난 ?요일(당일 포함)
next (DayOfWeek dayOfWeek)다음 ?요일(당일 미포함)
nextOrSame (DayOfWeek dayOfWeek)다음 ?요일(당일 포함)
dayOfWeekInMonth (int ordinal, DayOfWeek dayOfWeek)이번 달의 n번째 ?요일

TempralAdjuster 직접 구현하기

예시로, 특정 날짜로부터 2일 후의 날짜를 계산하는 DayAfterTomorrow는 다음과 같이 작성

class DayAfterTommorrow implements TemporalAdjuster {
	@Override
    public Temporal adjustInto(Temporal temporal) {
    	return temporal.plus(2, ChronoUnit.DAYS);	// 2일을 더한다.
    }
}

LocalDate today = LocalDate.now();
LocalDate date = today.with(new DayAfterTomorrow());	// 이렇게 사용하면 됨.

3.6 Period 와 Duration

Period 는 날짜의 차이를, Duration 은 시간의 차이를 계산하기 위한 것이다.


between()

LocalDate date1 = LocalDate.of(2014, 1, 1);
LocalDate date2 = LocalDate.of(2015, 12, 31);

Period pe = Period.between(date1, date2);
/*-------------------------------------------*/
LocalTime time1 = LocalTime.of(00,00,00);
LocalTime time2 = LocalTime.of(12,34,56);	// 12시 34분 56초

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

between()과 until()

둘은 거의 같은 일을 한다. between()은 static 메서드이고, until() 은 인스턴스 메서드라는 차이가 있다.

// Period pe = Period.between(today, myBirthDay);
Period pe = today. until(myBirthDay);
long dday = today.until(myBirthDay, ChronoUnit.DAYS);

Period는 년월일을 분리해서 저장하기 때문에, D-day를 구하려는 경우에는 두 개의 매개변수를 받는 until()을 사용하는 것이 낫다.


of(), with()

Period 에는 of(), ofYears(), ofMonths, ofWeeks(), ofDays() 가 있고,
Duration 에는 of(), ofDays(), ofHours(0, ofMinutes(), ofSeconds() 등이 있다.


사칙연산, 비교연산 기타 메서드

pe = pe.minusYears(1).multipliedBy(2);	// 1년을 빼고, 2배를 곱한다.
du = du.plusHours(1).dividedBy(60);		// 1시간을 더하고 60으로 나눈다.

boolean sameDate = Period.between(date1, date2).isZero();		// 0인지 확인
boolean isBefore = Duration.between(time1, time2).isNegative();	// 음수인지 확인

du = du.abs();		// 부호를 없애는 abs()
du = du.negated();	// 부호를 반대로 변경하는 negate()

pe = Period.of(1,13,32).normalized();	// 1년 13개월 32일 -> 2년 1개월 32일

다른 단위로 변환 - toTotalMonths(), toDays(), toHours(), toMinutes()

클래스메서드설명
Periodlong toTotalMonths()년월일을 월단위로 변환해서 반환(일 단위는 무시)
Durationlong toDays()일단위로 변환해서 반환
long toHours()시간단위로 변환해서 반환
long toMinutes()분단위로 변환해서 반환
long toMillis()천분의 일초 단위로 변환해서 반환
long toNanos()나노초 단위로 변환해서 반환

3.7 파싱과 포맷

날짜와 시간을 원하는 형식으로 출력하고 해석(파싱, parsing)하는 방법 !

형식화(formatting)와 관련된 클래스들은 java.time.format패키지 에 들어있는데, DateTimeFormatter 가 핵심이다.

LocalDate date = LocalDate.of(2016, 1, 2);

String yyyymmdd = DateTimeFormatter.ISO_LOCAL_DATE.format(date);	// "2016-01-02"
String yyyymmdd = date.format(DateTimeFormatter.ISO_LOCAL_DATE);	// "2016-01-02"

로케일에 종속된 형식화

DateTimeFormatter의 static 메서드 ofLocalizedDate(), ofLocalizedTime(), ofLocalizedDateTime() 은 로케일(locale)에 종속적인 포맷터를 생성한다.

FormatStyle의 종류에 따른 출력 형태 :

FormatStyle날짜시간
FULL2015년 11월 28일 토요일N/A
LONG2015년 11월 28일 (토)오후 9시 15분 13초
MEDIUM2015. 11. 28오후 9:15:13
SHORT15. 11. 28오후 9:15

출력형식 직접 정의하기

DateTimeFormatter의 ofPattern() 으로 원하는 출력형식을 직접 작성할 수도 있다.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");

-> DateTimeFormatter의 패턴에 사용되는 기호

문자열을 날짜와 시간으로 파싱하기

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

public class DateFormatterEx2 {
    public static void main(String[] args) {
        LocalDate newYear = LocalDate.parse("2016-01-01", DateTimeFormatter.ISO_LOCAL_DATE);

        LocalDate date = LocalDate.parse("2001-01-01");
        LocalTime time = LocalTime.parse("23:59:59");
        LocalDateTime dateTime = LocalDateTime.parse("2001-01-01T23:59:59");

        DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime endOfYear = LocalDateTime.parse("2015-12-31 23:59:59", pattern);

        System.out.println(newYear);
        System.out.println(date);
        System.out.println(time);
        System.out.println(dateTime);
        System.out.println(endOfYear);
    }
}
profile
단단하게 오래가고자 하는 백엔드 개발자

0개의 댓글