공공부하자개발 · 영어 학습 노트
자바
중급객체지향과 코어 라이브러리0/9 완료
  • 01상속, 다형성, 오버라이딩
  • 02추상 클래스 vs 인터페이스
  • 03예외 처리
  • 04java.lang 심화
  • 05컬렉션 프레임워크 딥다이브
  • 06메서드 활용 패턴 (중급)
  • 07Object 메서드와 비교
  • 08java.time 실무 날짜 계산
  • 09HTTP 와 JSON 기초
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 중급 › 08 / 9

java.time 실무 날짜 계산

정산일, 영업일, 월말, 시간대, DB 매핑
섹션 7진행 0 / 9
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

3. 코드 예제

소스: java-src/intermediate/08_java_time (BusinessDays, Main). 출력이 매번 같도록 now() 는 한 줄만 쓰고 나머지는 고정 날짜입니다.

bash
cd java-src/intermediate/08_java_time
javac -encoding UTF-8 *.java && java -Dstdout.encoding=UTF-8 Main

예제 1: 타입 다섯 개를 한 시각으로

java
LocalDate d = LocalDate.of(2026, 9, 9);
LocalTime t = LocalTime.of(9, 30);
LocalDateTime dt = LocalDateTime.of(d, t);
ZonedDateTime z = dt.atZone(ZoneId.of("Asia/Seoul"));
Instant i = z.toInstant();
text
LocalDate 2026-09-09 | LocalTime 09:30 | LocalDateTime 2026-09-09T09:30
ZonedDateTime 2026-09-09T09:30+09:00[Asia/Seoul] | Instant 2026-09-09T00:30:00Z | epoch ms 1788913800000
YearMonth 2026-09 (월 단위 정산 키) | DayOfWeek WEDNESDAY | 올해 252번째 날

같은 순간이 LocalDateTime 에서는 09:30, Instant 에서는 00:30Z 입니다. Z 는 UTC 표시입니다.

예제 2: 불변성과 월말 유실

java
LocalDate d = LocalDate.of(2026, 1, 31);
d.plusDays(1);                                                // 반환값을 버림 → d 는 그대로
LocalDate next = d.plusMonths(1);                             // 2월 28일
System.out.println(next.plusMonths(1));                       // 3월 28일: 월말이 사라졌다
System.out.println(YearMonth.from(d).plusMonths(2).atEndOfMonth());   // 3월 31일: YearMonth 로 옮기면 유지
text
d.plusDays(1) 호출 후 d = 2026-01-31  ← 그대로. 반환값을 받아야 한다
1월 31일 + 1개월 = 2026-02-28  ← 2월 31일이 없으니 말일로 당김 (예외 아님)
그 결과 + 1개월 = 2026-03-28  ← 3월 28일. 31→28→28: 월말이 '유실' 된다
월말 유지하려면 YearMonth 로: 2026-03-31
이달 말일 2026-01-31 | 다음 달 첫 월요일 2026-02-02
윤년 2028-02-29 + 1년 = 2029-02-28 | 2026 윤년? false

예제 3: 정산 기간, 지급일 보정, 영업일 더하기

java
BusinessDays biz = new BusinessDays(Set.of(LocalDate.of(2026, 10, 3), LocalDate.of(2026, 10, 9)));   // 개천절, 한글날
YearMonth sep = YearMonth.of(2026, 9);
BusinessDays.Period p = BusinessDays.monthPeriod(sep);                   // 9/1 ~ 9/30
LocalDate payDay = biz.settlementPayDay(sep);                            // 10/10 토요일 → 10/12 월요일
LocalDate fri = LocalDate.of(2026, 10, 8);
biz.addBusinessDays(fri, 1);                                             // 9(한글날)·10·11(주말) 건너 12일
text
9월 정산 기간: 2026-09-01 ~ 2026-09-30 (30일)
9월 정산 지급일(익월 10일, 영업일 보정): 2026-10-12 MONDAY
2026-10-08(THURSDAY) + 1영업일 = 2026-10-12  ← 9일 한글날·10~11일 주말을 건너뜀
2026-10-08 + 3영업일 = 2026-10-14 | -3영업일 = 2026-10-05
10/1 ~ 10/15 영업일 수: 10
분기: 2026-09 은 3분기, 분기 시작월 2026-07

10월 8일 목요일의 "다음 영업일" 이 12일 월요일입니다. 달력으로는 하루 뒤지만 영업일로는 나흘 뒤입니다. 이체 예정일을 이렇게 잡지 않으면 은행 휴일에 이체가 실패합니다.

예제 4: Period, Duration, 자정 넘김

text
만 나이: 35세 (P35Y8M15D = 35년 8개월 15일)
총 일수: 13042일 | 총 개월 수: 428개월
배치 소요: PT30M = 30분 | 마감 D-day: 113일 남음
자정 넘김 주의: 23:50→00:20 을 시각만 빼면 PT-23H-30M (음수). 날짜를 포함해 계산해야 한다

P35Y8M15D, PT30M 은 ISO 8601 기간 표기입니다. 로그에 이 형태가 찍히면 Period/Duration 의 toString 입니다.

예제 5: 포맷 함정과 파싱 해석 방식

java
DateTimeFormatter compact = DateTimeFormatter.ofPattern("yyyyMMdd");
dt.format(DateTimeFormatter.ofPattern("YYYY-MM-dd"));                    // 대문자 Y: 주 기준 연도
LocalDate.parse("20260231", compact);                                    // SMART: 2월 28일로 조정
LocalDate.parse("20260231", DateTimeFormatter.ofPattern("uuuuMMdd").withResolverStyle(ResolverStyle.STRICT));   // 예외
text
기본 toString(ISO): 2026-12-31T09:05:07 | yyyyMMdd: 20261231 | 사람용: 2026-12-31 09:05:07
YYYY(대문자) 함정: 2027-12-31  ← 주 기준 연도. 12월 31일이 2027년으로 찍힌다. 항상 소문자 yyyy
hh 는 12시간제: 09:05 (HH 는 09:05)
파싱: 2026-09-09 | ISO 는 포매터 없이 2026-09-09
ISO 형식이 아니면 예외: Text '2026-9-9' could not be parsed at index 5
없는 날짜 20260231 파싱: 2026-02-28  ← 예외 없이 2월 28일로 조정(기본 SMART). 엄격히 하려면 STRICT + uuuu
STRICT 파싱: Text '20260231' could not be parsed: Invalid date 'FEBRUARY 31'

이 레슨을 만들며 20260231 파싱이 예외를 낼 것으로 예상하고 코드를 짰는데 그냥 통과했습니다. 기본 해석이 SMART 라는 것을 실행으로 확인한 사례이고, 사용자 입력 날짜를 검증하는 코드라면 STRICT 가 아니면 잘못된 날짜가 조용히 저장됩니다.

예제 6: 시간대

text
UTC   2026-09-09T00:30:00Z = 서울 2026-09-09T09:30 = 뉴욕 2026-09-08T20:30
LocalDateTime 2026-09-09T09:30 을 서울로 해석하면 2026-09-09T00:30:00Z, UTC 로 해석하면 2026-09-09T09:30:00Z  ← 9시간 차이. 어느 쪽인지 코드에 명시해야 한다
DST: 뉴욕 01:30 + 1시간 = 03:30 (02:30 은 존재하지 않는 시각) | 한국은 DST 없음
서버 기본 시간대: Asia/Seoul  ← 클라우드·컨테이너는 UTC 인 경우가 많아 LocalDateTime.now() 가 9시간 어긋난다. ZoneId 를 명시한 now(SEOUL) 을 쓴다
같은 순간 비교: false(equals: 시간대까지 비교) vs true(isEqual: 순간만 비교)

예제 7: JDBC 와 레거시 매핑

java
Timestamp ts = Timestamp.valueOf(dt);           ts.toLocalDateTime();
java.sql.Date sd = java.sql.Date.valueOf(ld);   sd.toLocalDate();
legacyDate.toInstant().atZone(SEOUL).toLocalDateTime();
Instant.ofEpochSecond(1_788_931_828L);          // JWT exp
text
LocalDateTime → Timestamp 2026-09-09 14:05:30.123 → 다시 2026-09-09T14:05:30.123
LocalDate → java.sql.Date 2026-09-09 → 다시 2026-09-09  (Oracle DATE 는 초까지 담으므로 시각이 필요하면 LocalDateTime)
java.util.Date → 2026-09-09T14:30:28  (레거시 API 는 toInstant() 로 건너온다)
epoch 초 1788931828 = 2026-09-09T05:30:28Z = 서울 2026-09-09T14:30:28  (JWT exp 가 이 형식)

예제 직접 실행

아래 폴더를 JDK 21 로 컴파일하고 실행합니다.

cd java-src\intermediate\08_java_time
javac -encoding UTF-8 *.java && java Main
코드 예제
  • 예제 1: 타입 다섯 개를 한 시각으로
  • 예제 2: 불변성과 월말 유실
  • 예제 3: 정산 기간, 지급일 보정, 영업일 더하기
  • 예제 4: Period, Duration, 자정 넘김
  • 예제 5: 포맷 함정과 파싱 해석 방식
  • 예제 6: 시간대
  • 예제 7: JDBC 와 레거시 매핑
이전 섹션2 핵심 원리3 / 7다음 섹션4 응용 변형 예제