본문으로 건너뛰기

10.3 날짜/시간 포맷팅 (DateTimeFormatter)

날짜와 시간을 자바 내부에서 계산하는 것은 마쳤습니다. 이제 마지막 관문인 "사람이 읽기 쉬운 형태로 날짜 문자열을 만들거나, 반대로 문자열을 날짜 객체로 바꾸는 방법" 을 배워봅니다.

이를 위해 DateTimeFormatter 클래스를 사용합니다.


1. 날짜 객체 → 문자열 변환: format()

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

LocalDateTime now = LocalDateTime.now();

// 원하는 날짜 형식 패턴을 지정합니다
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH:mm:ss");

// LocalDateTime 객체를 지정한 포맷의 문자열로 변환
String formatted = now.format(formatter);
System.out.println(formatted); // 2026년 03월 23일 14:30:08

주요 패턴 문자 표

패턴의미예시
yyyy연도(4자리)2026
yy연도(2자리)26
MM월(2자리, 01~12)03
M월(12자리, 112)3
dd날짜(2자리, 01~31)07
d날짜(12자리, 131)7
HH시간(24시간제, 00~23)17
hh시간(12시간제, 01~12)05
mm분(00~59)30
ss초(00~59)08
SSS밀리초(000~999)123
E요일 약어
EEEE요일 전체 이름목요일
a오전/오후오전, 오후
z타임존 이름KST
Z타임존 오프셋+0900

다양한 형식 출력 예제

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

LocalDateTime now = LocalDateTime.of(2026, 3, 23, 14, 30, 8);
LocalDate today = now.toLocalDate();

// 한국식
System.out.println(now.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일")));
// 2026년 03월 23일

System.out.println(now.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH시 mm분")));
// 2026년 03월 23일 14시 30분

// 미국식
System.out.println(now.format(DateTimeFormatter.ofPattern("MM/dd/yyyy")));
// 03/23/2026

// 유럽식
System.out.println(now.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")));
// 23.03.2026

// 요일 포함
System.out.println(now.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 (EEEE)")));
// 2026년 03월 23일 (월요일)

// ISO 8601 표준 형식 (API 통신에 자주 사용)
System.out.println(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")));
// 2026-03-23T14:30:08

// 시간 포함
System.out.println(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 2026-03-23 14:30:08

// 12시간제 + 오전/오후
System.out.println(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss a")));
// 2026-03-23 02:30:08 오후

// 밀리초 포함
System.out.println(now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
// 2026-03-23 14:30:08.000
패턴 내 리터럴 문자 처리

패턴 문자가 아닌 텍스트는 작은따옴표('')로 감싸야 합니다.

// 'T'는 그대로 출력되는 리터럴 문자
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

// '년', '월', '일'도 마찬가지로 감싸야 할 것 같지만,
// 한글 등 패턴에 사용되지 않는 문자는 그냥 써도 동작합니다
DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");

2. 미리 정의된 포매터 (ISO 표준 포매터)

DateTimeFormatter에는 자주 쓰는 형식이 미리 정의되어 있습니다. 직접 패턴을 쓰지 않아도 됩니다.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

LocalDate date = LocalDate.of(2026, 3, 23);
LocalDateTime dt = LocalDateTime.of(2026, 3, 23, 14, 30, 8);

// ISO_DATE: 2026-03-23
System.out.println(date.format(DateTimeFormatter.ISO_DATE));

// ISO_DATE_TIME: 2026-03-23T14:30:08
System.out.println(dt.format(DateTimeFormatter.ISO_DATE_TIME));

// ISO_LOCAL_DATE: 2026-03-23
System.out.println(date.format(DateTimeFormatter.ISO_LOCAL_DATE));

// ISO_LOCAL_TIME: 14:30:08
System.out.println(dt.toLocalTime().format(DateTimeFormatter.ISO_LOCAL_TIME));

// RFC_1123_DATE_TIME (HTTP 날짜 형식): Mon, 23 Mar 2026 14:30:08 +0000
ZonedDateTime zdt = ZonedDateTime.of(dt, ZoneId.of("UTC"));
System.out.println(zdt.format(DateTimeFormatter.RFC_1123_DATE_TIME));

3. 문자열 → 날짜 객체 변환: parse()

반대로, "2026-03-23" 같은 텍스트로 된 날짜 문자열을 LocalDate 객체로 변환 할 때는 parse() 메서드를 사용합니다. 웹 폼이나 API 등에서 날짜를 텍스트로 받아올 때 필수적으로 씁니다.

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

// ISO 기본 형식 (포매터 없이 파싱 가능)
LocalDate d1 = LocalDate.parse("2026-03-23");
LocalDateTime dt1 = LocalDateTime.parse("2026-03-23T14:30:00");
System.out.println(d1); // 2026-03-23
System.out.println(dt1); // 2026-03-23T14:30

// 커스텀 형식 파싱
DateTimeFormatter krFormat = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
LocalDate d2 = LocalDate.parse("2026년 03월 23일", krFormat);
System.out.println(d2); // 2026-03-23

// 미국식 날짜 파싱
DateTimeFormatter usFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy");
LocalDate d3 = LocalDate.parse("03/23/2026", usFormat);
System.out.println(d3); // 2026-03-23

// 날짜+시간 파싱
DateTimeFormatter dtFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt2 = LocalDateTime.parse("2026-03-23 14:30:08", dtFormat);
System.out.println(dt2); // 2026-03-23T14:30:08
System.out.println("연도: " + dt2.getYear()); // 2026
System.out.println("월: " + dt2.getMonthValue()); // 3
System.out.println("일: " + dt2.getDayOfMonth()); // 23

파싱 오류 처리

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

public static LocalDate parseSafe(String dateStr, String pattern) {
try {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
return LocalDate.parse(dateStr, fmt);
} catch (DateTimeParseException e) {
System.out.println("날짜 파싱 실패: '" + dateStr + "' (패턴: " + pattern + ")");
System.out.println("에러: " + e.getMessage());
return null;
}
}

// 사용 예
LocalDate result1 = parseSafe("2026-03-23", "yyyy-MM-dd"); // 성공
LocalDate result2 = parseSafe("23/03/2026", "yyyy-MM-dd"); // 실패 (형식 불일치)
LocalDate result3 = parseSafe("2026-13-45", "yyyy-MM-dd"); // 실패 (잘못된 날짜값)

4. 로케일(Locale)별 포맷

로케일을 지정하면 요일이나 월 이름이 해당 언어로 출력됩니다.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;

LocalDate date = LocalDate.of(2026, 3, 23);
LocalDateTime dt = LocalDateTime.of(2026, 3, 23, 14, 30, 0);

// 한국어 로케일
DateTimeFormatter korFull = DateTimeFormatter.ofPattern("yyyy년 M월 d일 EEEE", Locale.KOREAN);
System.out.println(date.format(korFull)); // 2026년 3월 23일 월요일

// 영어 로케일
DateTimeFormatter engFull = DateTimeFormatter.ofPattern("MMMM d, yyyy (EEEE)", Locale.ENGLISH);
System.out.println(date.format(engFull)); // March 23, 2026 (Monday)

// 일본어 로케일
DateTimeFormatter jpFull = DateTimeFormatter.ofPattern("yyyy年MM月dd日(EEEE)", Locale.JAPANESE);
System.out.println(date.format(jpFull)); // 2026年03月23日(月曜日)

// 시스템 로케일에 맞는 자동 스타일 (FormatStyle)
DateTimeFormatter localFull = DateTimeFormatter
.ofLocalizedDate(FormatStyle.FULL)
.withLocale(Locale.KOREAN);
System.out.println(date.format(localFull)); // 2026년 3월 23일 월요일

DateTimeFormatter localMedium = DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.ENGLISH);
System.out.println(date.format(localMedium)); // Mar 23, 2026

5. DateTimeFormatter 재사용 (스레드 안전)

DateTimeFormatter불변(Immutable) 이고 스레드 안전 합니다. SimpleDateFormat(구세대)과 달리 전역 상수로 선언해서 재사용해도 안전합니다.

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

// 좋은 예: 상수로 선언하여 재사용
public class DateFormatConstants {
public static final DateTimeFormatter DATE_KR =
DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");

public static final DateTimeFormatter DATE_ISO =
DateTimeFormatter.ofPattern("yyyy-MM-dd");

public static final DateTimeFormatter DATETIME_DEFAULT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

public static final DateTimeFormatter DATETIME_KR =
DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH시 mm분 ss초");
}

// 사용
LocalDate d = LocalDate.now();
System.out.println(d.format(DateFormatConstants.DATE_KR)); // 2026년 03월 23일
System.out.println(d.format(DateFormatConstants.DATE_ISO)); // 2026-03-23
SimpleDateFormat 사용 금지

구세대 API인 SimpleDateFormat스레드 안전하지 않습니다. 멀티스레드 환경에서 공유하면 날짜가 뒤섞이는 치명적인 버그가 발생합니다. 반드시 DateTimeFormatter를 사용하세요.


6. 레거시 Date/Calendar와 변환

기존 코드에서 java.util.Datejava.util.Calendar를 사용하고 있다면, java.time API와 변환하는 방법을 알아야 합니다.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.Instant;
import java.util.Date;
import java.util.Calendar;

// --- Date → LocalDateTime 변환 ---
Date legacyDate = new Date();

LocalDateTime fromDate = legacyDate.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
System.out.println("Date → LocalDateTime: " + fromDate);

LocalDate fromDateOnly = legacyDate.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
System.out.println("Date → LocalDate: " + fromDateOnly);

// --- LocalDateTime → Date 변환 (레거시 API 호환 시 필요) ---
LocalDateTime ldt = LocalDateTime.of(2026, 3, 23, 14, 30, 0);
Date toDate = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("LocalDateTime → Date: " + toDate);

// --- Calendar → LocalDate 변환 ---
Calendar cal = Calendar.getInstance();
LocalDate fromCalendar = cal.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
System.out.println("Calendar → LocalDate: " + fromCalendar);

// --- Instant 활용 (타임스탬프) ---
Instant now = Instant.now();
System.out.println("Instant: " + now); // 2026-03-23T05:30:08.123Z (UTC 기준)

LocalDateTime fromInstant = LocalDateTime.ofInstant(now, ZoneId.of("Asia/Seoul"));
System.out.println("Instant → KST: " + fromInstant);

7. 실전 예제: 다양한 날짜 형식 파싱과 포맷 변환

실무에서는 외부 시스템이나 사용자 입력에서 다양한 형식의 날짜 문자열이 들어옵니다. 이를 통일된 형식으로 변환하는 유틸리티 클래스를 만들어 봅니다.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.List;

public class DateConverter {

// 지원하는 입력 포맷 목록
private static final List<DateTimeFormatter> SUPPORTED_FORMATS = Arrays.asList(
DateTimeFormatter.ofPattern("yyyy-MM-dd"),
DateTimeFormatter.ofPattern("yyyy/MM/dd"),
DateTimeFormatter.ofPattern("yyyy.MM.dd"),
DateTimeFormatter.ofPattern("MM/dd/yyyy"),
DateTimeFormatter.ofPattern("dd-MM-yyyy"),
DateTimeFormatter.ofPattern("yyyyMMdd"),
DateTimeFormatter.ofPattern("yyyy년 MM월 dd일"),
DateTimeFormatter.ofPattern("yyyy년 M월 d일")
);

// 표준 출력 포맷
private static final DateTimeFormatter OUTPUT_ISO = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter OUTPUT_KR = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
private static final DateTimeFormatter OUTPUT_FULL = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 (EEEE)",
java.util.Locale.KOREAN);

/**
* 다양한 형식의 날짜 문자열을 LocalDate로 파싱 (자동 감지)
*/
public static LocalDate parse(String dateStr) {
if (dateStr == null || dateStr.isBlank()) {
throw new IllegalArgumentException("날짜 문자열이 비어있습니다.");
}

String trimmed = dateStr.trim();

for (DateTimeFormatter fmt : SUPPORTED_FORMATS) {
try {
return LocalDate.parse(trimmed, fmt);
} catch (DateTimeParseException e) {
// 다음 포맷으로 시도
}
}

throw new DateTimeParseException(
"지원하지 않는 날짜 형식: " + dateStr, dateStr, 0);
}

/**
* LocalDate를 원하는 형식의 문자열로 변환
*/
public static String format(LocalDate date, String pattern) {
return date.format(DateTimeFormatter.ofPattern(pattern));
}

/**
* 날짜 문자열을 다른 형식으로 변환
*/
public static String convert(String inputDate, String outputPattern) {
LocalDate parsed = parse(inputDate);
return format(parsed, outputPattern);
}

public static void main(String[] args) {
System.out.println("=== 다양한 입력 형식 파싱 테스트 ===");

String[] inputs = {
"2026-03-23",
"2026/03/23",
"2026.03.23",
"03/23/2026",
"23-03-2026",
"20260323",
"2026년 03월 23일",
"2026년 3월 23일"
};

for (String input : inputs) {
try {
LocalDate date = parse(input);
System.out.printf("입력: %-25s → ISO: %-12s | 한국식: %s%n",
input,
date.format(OUTPUT_ISO),
date.format(OUTPUT_KR));
} catch (DateTimeParseException e) {
System.out.println("파싱 실패: " + input + " - " + e.getMessage());
}
}

System.out.println("\n=== 형식 변환 ===");
System.out.println(convert("2026-03-23", "MM/dd/yyyy")); // 03/23/2026
System.out.println(convert("03/23/2026", "yyyy년 MM월 dd일")); // 2026년 03월 23일
System.out.println(convert("20260323", "yyyy-MM-dd")); // 2026-03-23

System.out.println("\n=== 요일 포함 출력 ===");
LocalDate date = LocalDate.of(2026, 3, 23);
System.out.println(date.format(OUTPUT_FULL)); // 2026년 03월 23일 (월요일)

System.out.println("\n=== 여러 로케일 출력 ===");
System.out.println("한국어: " + date.format(
DateTimeFormatter.ofPattern("yyyy년 M월 d일 EEEE", java.util.Locale.KOREAN)));
System.out.println("영어: " + date.format(
DateTimeFormatter.ofPattern("MMMM d, yyyy EEEE", java.util.Locale.ENGLISH)));
}
}

[실행 결과]

=== 다양한 입력 형식 파싱 테스트 ===
입력: 2026-03-23 → ISO: 2026-03-23 | 한국식: 2026년 03월 23일
입력: 2026/03/23 → ISO: 2026-03-23 | 한국식: 2026년 03월 23일
입력: 2026.03.23 → ISO: 2026-03-23 | 한국식: 2026년 03월 23일
입력: 03/23/2026 → ISO: 2026-03-23 | 한국식: 2026년 03월 23일
입력: 23-03-2026 → ISO: 2026-03-23 | 한국식: 2026년 03월 23일
입력: 20260323 → ISO: 2026-03-23 | 한국식: 2026년 03월 23일
입력: 2026년 03월 23일 → ISO: 2026-03-23 | 한국식: 2026년 03월 23일
입력: 2026년 3월 23일 → ISO: 2026-03-23 | 한국식: 2026년 03월 23일

=== 형식 변환 ===
03/23/2026
2026년 03월 23일
2026-03-23

=== 요일 포함 출력 ===
2026년 03월 23일 (월요일)

=== 여러 로케일 출력 ===
한국어: 2026년 3월 23일 월요일
영어: March 23, 2026 Monday

마치며

이것으로 자바에서 날짜와 시간을 다루는 핵심 기술을 모두 마스터했습니다.

작업사용 도구핵심 메서드
날짜/시간 객체 생성LocalDate, LocalDateTimenow(), of(), parse()
날짜/시간 간격 계산Period, Duration, ChronoUnitbetween()
날짜/시간 조작LocalDate, LocalDateTimeplus/minus/with
복잡한 날짜 조정TemporalAdjustersnext(), lastDayOfMonth()
날짜 ↔ 문자열DateTimeFormatterformat(), parse()
레거시 변환Instant, ZoneIdtoInstant(), atZone()
  • DateTimeFormatter는 불변이므로 상수로 선언해서 재사용 하는 것이 좋습니다.
  • parse() 호출 시 항상 DateTimeParseException을 처리해 안전한 코드를 작성하세요.
  • 다음 Phase에서는 자바 컬렉션 프레임웍(List, Set, Map)을 배워보겠습니다!