개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › Java › 자주 쓰는 클래스

날짜와 시간

1월 31일에 한 달을 더하면 며칠이 되나

java.time의 날짜와 시간 객체(LocalDate, ZonedDateTime 등)는 String처럼 내용이 바뀌지 않습니다. plusMonths 같은 메서드는 새 객체를 돌려주고, 달마다 날수가 달라서 "한 달 뒤"의 계산이 생각과 다를 때가 있습니다. 같은 순간이라도 시간대에 따라 다른 날짜와 시각으로 적히며, 두 시점 사이의 간격은 날짜 단위(Period)와 시간 단위(Duration)로 따로 잽니다.

실행으로 대조 · JDK 21에서 135가지 조합(서로 다른 경우 열한 가지)을 실행해 대조

이 실습에 나오는 개념

java.time(날짜와 시간 API)
Java 8부터 들어 있는 날짜와 시간 클래스 묶음이다. 날짜만 담는 LocalDate, 날짜와 시각을 담는 LocalDateTime, 여기에 시간대까지 담는 ZonedDateTime, 시간대와 상관없는 한 순간을 담는 Instant 등이 있다. 이 객체들은 모두 불변이라서 plusDays, plusMonths 같은 메서드는 원래 객체를 바꾸지 않고 새 객체를 돌려준다. 그래서 돌려준 값을 변수에 받아야 결과를 쓸 수 있다. 달을 더한 결과가 달력에 없는 날짜이면(예: 2월 31일) 예외를 내지 않고 그 달의 마지막 날로 맞춘다.
시간대와 UTC
UTC는 전 세계가 기준으로 삼는 시각이고, 시간대는 한 지역이 UTC와 얼마나 차이 나는 시각을 쓰는지에 대한 규칙이다. 한국(Asia/Seoul)은 UTC보다 9시간 빠르다. 같은 순간이라도 시간대에 따라 적히는 시각이 다르고, 날짜가 달라지기도 한다. 서머타임을 쓰는 지역은 철에 따라 UTC와의 차이가 바뀌므로, 고정된 시간 차이보다 America/New_York 같은 지역 이름으로 시간대를 지정하는 것이 안전하다. 어느 한 순간을 저장할 때는 시간대와 상관없는 값(Instant)으로 두고, 보여 줄 때 보는 사람의 시간대로 바꿔 적는 것이 보통이다.
Period와 Duration
java.time에서 두 시점 사이의 간격을 나타내는 두 클래스다. Period는 "1년 2개월 3일"처럼 달력의 년, 월, 일로 간격을 나타내고, Duration은 "90분"처럼 흐른 시간의 길이를 초 단위로 나타낸다. 한 달의 길이는 달마다 다르므로 Period는 정해진 시간으로 바꿀 수 없고, Duration에는 달이나 년이라는 단위가 없다. Period의 getDays()는 전체 일수가 아니라 일 부분의 값만 돌려주므로, 전체 일수는 ChronoUnit.DAYS.between으로 구한다. Duration.between에는 시각이 있는 값을 넘겨야 하며, 날짜만 있는 LocalDate를 넘기면 실행 중에 예외가 난다.
불변 객체
생성된 뒤에는 상태가 바뀌지 않는 객체다. 값이 바뀌지 않으므로 여러 스레드가 동시에 사용해도 안전하고, 다른 곳에 넘겨도 변경될 걱정이 없으며, 캐싱하거나 해시 컬렉션의 키로 쓰기에 적합하다. 필드를 private final로 선언하고 상태를 바꾸는 메서드를 두지 않으며, 클래스를 final로 해 하위 클래스가 동작을 바꾸지 못하게 한다. 필드가 가변 객체(List, Date 등)를 가리키면 생성자와 getter에서 복사본을 주고받아(방어적 복사) 밖에서 내부를 바꾸지 못하게 한다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. LocalDate d = LocalDate.of(2024, 1, 31): 2024년 1월 31일을 나타내는 날짜 객체를 만들고 d가 가리킵니다. LocalDate는 시각과 시간대 없이 날짜만 담습니다.
  2. LocalDate first = d: first도 같은 객체를 가리킵니다. 처음 객체가 나중에 바뀌는지 보려고 둡니다.
  3. d = d.plusMonths(1): 1월 31일의 한 달 뒤는 2월 31일이어야 하지만 그런 날은 없습니다. 이럴 때는 그 달의 마지막 날로 맞춥니다. 2024년 2월은 29일까지 있으므로 2024-02-29입니다. 이 새 객체를 d가 가리킵니다.
  4. System.out.println(d): 2024-02-29 한 줄이 출력됩니다.
  5. System.out.println(first): 2024-01-31 한 줄이 출력됩니다. 처음 객체의 내용은 바뀌지 않았습니다.
  6. d == first: d는 새로 만들어진 객체를, first는 처음 객체를 가리키므로 false입니다.

실행 결과

코드

LocalDate d = LocalDate.of(2024, 1, 31);
LocalDate first = d;                 // 처음 객체를 기억해 둔다

d = d.plusMonths(1);

System.out.println(d);
System.out.println(first);
System.out.println(d == first);      // 같은 객체인가

이 모형의 전제

135가지 조합(주제마다 설정을 하나만 쓰므로 서로 다른 경우는 열한 가지)의 출력을 이 PC의 JDK 21에서 같은 코드를 실행한 결과와 대조했습니다. 코드는 고정된 날짜와 이름으로 지정한 시간대만 쓰므로 오늘 날짜나 PC의 시간대 설정과 상관없이 같은 결과가 나오며, PC의 기본 시간대를 다른 값으로 바꿔 실행해도 출력이 같음을 확인했습니다. 시간대별 시각은 JDK에 들어 있는 시간대 자료를 따릅니다. 뉴욕이 UTC보다 5시간 느린 것은 이 날짜(겨울) 기준이며, 서머타임 기간에는 4시간입니다. 서머타임이 시작하거나 끝나는 날의 계산은 이 모형에서 다루지 않습니다. 윤년이 아닌 해의 결과(예: 2월 28일)는 실행해 대조한 범위에 들어 있지 않습니다.

앞 실습: String, StringBuilder · 다음 실습: Optional

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요