Date/Calendar에서 java.time으로 - 마이그레이션 가이드
Java API Reference
import java.util.Date;
public class DateReview {
public static void main(String[] args) {
// 생성
Date now = new Date();
System.out.println("Now: " + now);
// 특정 날짜 (Deprecated!)
@SuppressWarnings("deprecation")
Date date = new Date(2024 - 1900, 11, 25); // 년은 1900 기준, 월은 0부터
System.out.println("Date: " + date);
// Epoch 밀리초
long millis = System.currentTimeMillis();
Date fromMillis = new Date(millis);
// 문제점
System.out.println("\n=== 문제점 ===");
System.out.println("1. 가변 객체");
Date mutableDate = new Date();
System.out.println("Before: " + mutableDate);
mutableDate.setTime(0);
System.out.println("After: " + mutableDate); // 변경됨!
System.out.println("\n2. 이상한 API");
@SuppressWarnings("deprecation")
Date weirdDate = new Date(2024, 0, 1); // 3924-01-01 로 생성!
System.out.println("Weird: " + weirdDate);
System.out.println("\n3. 타입 안정성 부족");
@SuppressWarnings("deprecation")
Date invalid = new Date(2024 - 1900, 15, 1); // 15월도 가능!
System.out.println("Invalid: " + invalid);
}
}import java.util.Calendar;
public class CalendarReview {
public static void main(String[] args) {
// 생성
Calendar cal = Calendar.getInstance();
// 특정 날짜 설정
cal.set(2024, Calendar.DECEMBER, 25);
// 읽기
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH); // 0부터 시작!
int day = cal.get(Calendar.DAY_OF_MONTH);
System.out.println("Year: " + year);
System.out.println("Month: " + month); // 11 (12월인데!)
System.out.println("Day: " + day);
// 수정 (가변!)
cal.add(Calendar.DAY_OF_MONTH, 7);
System.out.println("After: " + cal.getTime());
// 문제점
System.out.println("\n=== 문제점 ===");
System.out.println("1. 여전히 가변");
System.out.println("2. 상수 사용 불편 (Calendar.DECEMBER 등)");
System.out.println("3. 요일이 1(일요일)부터 시작");
System.out.println("4. 직관적이지 않은 API");
}
}import java.text.SimpleDateFormat;
import java.util.Date;
public class SimpleDateFormatReview {
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 포맷팅
String formatted = sdf.format(new Date());
System.out.println("Formatted: " + formatted);
// 파싱
Date parsed = sdf.parse("2024-12-25 14:30:00");
System.out.println("Parsed: " + parsed);
// 문제점
System.out.println("\n=== 문제점 ===");
System.out.println("1. 스레드 안전하지 않음!");
System.out.println("2. 예외 처리 필요 (ParseException)");
System.out.println("3. 타입 안정성 없음");
// 스레드 안전하지 않은 예시
final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
// 여러 스레드에서 동시 사용 시 문제 발생 가능
Thread t1 = new Thread(() -> {
try {
for (int i = 0; i < 100; i++) {
Date d = formatter.parse("2024-12-25");
}
} catch (Exception e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
for (int i = 0; i < 100; i++) {
Date d = formatter.parse("2024-01-01");
}
} catch (Exception e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}public class DateToLocalDateTime {
public static void main(String[] args) {
// 레거시: Date
Date date = new Date();
System.out.println("Date: " + date);
// 신규: LocalDateTime
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("LocalDateTime: " + localDateTime);
// 비교
System.out.println("\n=== 비교 ===");
// Date - 가변
Date mutableDate = new Date();
mutableDate.setTime(0); // 변경 가능!
// LocalDateTime - 불변
LocalDateTime immutableDateTime = LocalDateTime.now();
LocalDateTime changed = immutableDateTime.plusDays(1); // 새 객체 반환
System.out.println("Original: " + immutableDateTime);
System.out.println("Changed: " + changed);
// Date - 직접 날짜/시간 필드 없음
// date.getYear(); // Deprecated
// LocalDateTime - 직접 접근 가능
int year = localDateTime.getYear();
int month = localDateTime.getMonthValue();
int day = localDateTime.getDayOfMonth();
System.out.println("Year: " + year);
System.out.println("Month: " + month); // 1~12 (정상!)
}
}public class CalendarToLocal {
public static void main(String[] args) {
// 레거시: Calendar
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.DECEMBER, 25, 14, 30, 0);
System.out.println("Calendar: " + cal.getTime());
// 신규: LocalDate
LocalDate date = LocalDate.of(2024, 12, 25); // 월이 1~12!
System.out.println("LocalDate: " + date);
// 신규: LocalDateTime
LocalDateTime dateTime = LocalDateTime.of(2024, 12, 25, 14, 30, 0);
System.out.println("LocalDateTime: " + dateTime);
// 비교
System.out.println("\n=== 연산 비교 ===");
// Calendar - 복잡
Calendar cal2 = Calendar.getInstance();
cal2.set(2024, Calendar.DECEMBER, 25);
cal2.add(Calendar.DAY_OF_MONTH, 7);
System.out.println("Calendar +7 days: " + cal2.getTime());
// LocalDate - 간단
LocalDate date2 = LocalDate.of(2024, 12, 25);
LocalDate after7Days = date2.plusDays(7);
System.out.println("LocalDate +7 days: " + after7Days);
}
}public class FormatComparison {
public static void main(String[] args) throws Exception {
// 레거시: SimpleDateFormat
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
String formatted1 = sdf.format(date);
Date parsed1 = sdf.parse("2024-12-25 14:30:00");
System.out.println("SimpleDateFormat: " + formatted1);
// 신규: DateTimeFormatter
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dateTime = LocalDateTime.now();
String formatted2 = dateTime.format(dtf);
LocalDateTime parsed2 = LocalDateTime.parse("2024-12-25 14:30:00", dtf);
System.out.println("DateTimeFormatter: " + formatted2);
// 비교
System.out.println("\n=== 장점 ===");
System.out.println("1. 스레드 안전 (불변)");
System.out.println("2. 예외 처리 불필요 (런타임 예외)");
System.out.println("3. 타입 안정성");
System.out.println("4. 메서드 체이닝");
// 스레드 안전성 테스트
final DateTimeFormatter threadSafeFormatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 여러 스레드에서 안전하게 사용 가능
Runnable task = () -> {
for (int i = 0; i < 100; i++) {
LocalDate d = LocalDate.parse("2024-12-25", threadSafeFormatter);
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Thread-safe execution completed!");
}
}public class DateConversion {
public static void main(String[] args) {
// Date → LocalDateTime
Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime localDateTime = instant.atZone(ZoneId.systemDefault())
.toLocalDateTime();
System.out.println("Date: " + date);
System.out.println("LocalDateTime: " + localDateTime);
// LocalDateTime → Date
LocalDateTime ldt = LocalDateTime.now();
Instant instant2 = ldt.atZone(ZoneId.systemDefault()).toInstant();
Date date2 = Date.from(instant2);
System.out.println("\nLocalDateTime: " + ldt);
System.out.println("Date: " + date2);
// 간단한 헬퍼 메서드
Date fromLocalDateTime = convertToDate(LocalDateTime.now());
LocalDateTime fromDate = convertToLocalDateTime(new Date());
System.out.println("\nConverted Date: " + fromLocalDateTime);
System.out.println("Converted LocalDateTime: " + fromDate);
}
// LocalDateTime → Date
public static Date convertToDate(LocalDateTime localDateTime) {
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
// Date → LocalDateTime
public static LocalDateTime convertToLocalDateTime(Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}
}public class CalendarConversion {
public static void main(String[] args) {
// Calendar → LocalDateTime
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.DECEMBER, 25, 14, 30, 0);
Instant instant = cal.toInstant();
LocalDateTime ldt = instant.atZone(ZoneId.systemDefault())
.toLocalDateTime();
System.out.println("Calendar: " + cal.getTime());
System.out.println("LocalDateTime: " + ldt);
// LocalDateTime → Calendar
LocalDateTime localDateTime = LocalDateTime.of(2024, 12, 25, 14, 30);
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
Calendar calendar = GregorianCalendar.from(zdt);
System.out.println("\nLocalDateTime: " + localDateTime);
System.out.println("Calendar: " + calendar.getTime());
}
}public class TimeZoneConversion {
public static void main(String[] args) {
// TimeZone → ZoneId
TimeZone timeZone = TimeZone.getTimeZone("Asia/Seoul");
ZoneId zoneId = timeZone.toZoneId();
System.out.println("TimeZone: " + timeZone.getID());
System.out.println("ZoneId: " + zoneId);
// ZoneId → TimeZone
ZoneId zoneId2 = ZoneId.of("America/New_York");
TimeZone timeZone2 = TimeZone.getTimeZone(zoneId2);
System.out.println("\nZoneId: " + zoneId2);
System.out.println("TimeZone: " + timeZone2.getID());
}
}public class BasicMigration {
// BEFORE: 레거시 코드
public static class LegacyCode {
public String formatDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date);
}
public Date parseDate(String dateStr) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(dateStr);
}
public Date addDays(Date date, int days) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DAY_OF_MONTH, days);
return cal.getTime();
}
}
// AFTER: 신규 코드
public static class ModernCode {
public String formatDate(LocalDate date) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return date.format(formatter);
}
public LocalDate parseDate(String dateStr) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return LocalDate.parse(dateStr, formatter);
}
public LocalDate addDays(LocalDate date, int days) {
return date.plusDays(days);
}
}
public static void main(String[] args) throws Exception {
System.out.println("=== Legacy Code ===");
LegacyCode legacy = new LegacyCode();
Date date = new Date();
System.out.println("Formatted: " + legacy.formatDate(date));
System.out.println("Added 7 days: " + legacy.addDays(date, 7));
System.out.println("\n=== Modern Code ===");
ModernCode modern = new ModernCode();
LocalDate localDate = LocalDate.now();
System.out.println("Formatted: " + modern.formatDate(localDate));
System.out.println("Added 7 days: " + modern.addDays(localDate, 7));
}
}public class GradualMigration {
// 1단계: 레거시 API 유지하면서 내부는 신규 API 사용
public static class HybridCode {
// 레거시 인터페이스 유지
public Date addDays(Date date, int days) {
// 내부는 신규 API 사용
LocalDateTime ldt = date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
LocalDateTime result = ldt.plusDays(days);
return Date.from(result.atZone(ZoneId.systemDefault()).toInstant());
}
public String formatDate(Date date, String pattern) {
LocalDateTime ldt = date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return ldt.format(formatter);
}
}
// 2단계: 새 메서드 추가 (오버로딩)
public static class TransitionCode {
// 레거시 메서드 (Deprecated)
@Deprecated
public Date addDays(Date date, int days) {
return Date.from(addDays(convertToLocalDate(date), days)
.atStartOfDay(ZoneId.systemDefault())
.toInstant());
}
// 신규 메서드
public LocalDate addDays(LocalDate date, int days) {
return date.plusDays(days);
}
private LocalDate convertToLocalDate(Date date) {
return date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
}
}
public static void main(String[] args) {
System.out.println("=== Hybrid Code (1단계) ===");
HybridCode hybrid = new HybridCode();
Date date = new Date();
Date result = hybrid.addDays(date, 7);
System.out.println("Result: " + result);
System.out.println("\n=== Transition Code (2단계) ===");
TransitionCode transition = new TransitionCode();
// 레거시 사용 (경고 발생)
@SuppressWarnings("deprecation")
Date legacyResult = transition.addDays(new Date(), 7);
// 신규 사용 (권장)
LocalDate modernResult = transition.addDays(LocalDate.now(), 7);
System.out.println("Legacy: " + legacyResult);
System.out.println("Modern: " + modernResult);
}
}public class ExternalAPIAdaptor {
// 외부 API가 Date를 요구하는 경우
public static class ThirdPartyAPI {
public void saveEvent(Date eventDate) {
System.out.println("Saving event: " + eventDate);
}
}
// 내부는 신규 API 사용
public static class EventService {
private ThirdPartyAPI thirdPartyAPI = new ThirdPartyAPI();
public void createEvent(LocalDateTime eventDateTime) {
// 내부에서 변환
Date date = Date.from(eventDateTime
.atZone(ZoneId.systemDefault())
.toInstant());
thirdPartyAPI.saveEvent(date);
}
}
// 어댑터 패턴
public static class DateAdapter {
public static Date toDate(LocalDateTime localDateTime) {
if (localDateTime == null) return null;
return Date.from(localDateTime
.atZone(ZoneId.systemDefault())
.toInstant());
}
public static LocalDateTime toLocalDateTime(Date date) {
if (date == null) return null;
return date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
}
}
public static void main(String[] args) {
EventService service = new EventService();
// 내부는 신규 API 사용
LocalDateTime eventTime = LocalDateTime.of(2024, 12, 25, 14, 30);
service.createEvent(eventTime);
// 어댑터 활용
LocalDateTime ldt = LocalDateTime.now();
Date date = DateAdapter.toDate(ldt);
LocalDateTime converted = DateAdapter.toLocalDateTime(date);
System.out.println("Original: " + ldt);
System.out.println("Converted: " + converted);
}
}// 레거시 코드를 신규 API로 리팩토링
public class Problem1 {
// BEFORE
public static class LegacyBirthdayService {
public int calculateAge(Date birthDate) {
Calendar birth = Calendar.getInstance();
birth.setTime(birthDate);
Calendar today = Calendar.getInstance();
int age = today.get(Calendar.YEAR) - birth.get(Calendar.YEAR);
if (today.get(Calendar.MONTH) < birth.get(Calendar.MONTH)) {
age--;
} else if (today.get(Calendar.MONTH) == birth.get(Calendar.MONTH) &&
today.get(Calendar.DAY_OF_MONTH) < birth.get(Calendar.DAY_OF_MONTH)) {
age--;
}
return age;
}
}
// AFTER - 여기에 코드 작성
public static class ModernBirthdayService {
public int calculateAge(LocalDate birthDate) {
// 여기에 코드 작성
return 0;
}
}
public static void main(String[] args) {
// 테스트
LegacyBirthdayService legacy = new LegacyBirthdayService();
@SuppressWarnings("deprecation")
Date birthDate = new Date(90, 4, 15); // 1990-05-15
System.out.println("Legacy age: " + legacy.calculateAge(birthDate));
ModernBirthdayService modern = new ModernBirthdayService();
LocalDate modernBirthDate = LocalDate.of(1990, 5, 15);
System.out.println("Modern age: " + modern.calculateAge(modernBirthDate));
}
}정답:
정답 보기
public static class ModernBirthdayService {
public int calculateAge(LocalDate birthDate) {
LocalDate today = LocalDate.now();
return Period.between(birthDate, today).getYears();
}
}// SimpleDateFormat을 DateTimeFormatter로 변경
public class Problem2 {
// BEFORE
public static class LegacyFormatter {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public String format(Date date) {
return sdf.format(date);
}
public Date parse(String dateStr) throws ParseException {
return sdf.parse(dateStr);
}
}
// AFTER - 여기에 코드 작성
public static class ModernFormatter {
// 여기에 코드 작성
}
}정답:
정답 보기
public static class ModernFormatter {
private DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public String format(LocalDateTime dateTime) {
return dateTime.format(formatter);
}
public LocalDateTime parse(String dateStr) {
return LocalDateTime.parse(dateStr, formatter);
}
}// Calendar/TimeZone을 ZonedDateTime으로 변경
public class Problem3 {
// BEFORE
public static Date convertTimezone(Date date, String fromZone, String toZone) {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(fromZone));
cal.setTime(date);
cal.setTimeZone(TimeZone.getTimeZone(toZone));
return cal.getTime();
}
// AFTER - 여기에 코드 작성
public static ZonedDateTime convertTimezoneModern(
ZonedDateTime dateTime, String toZone) {
// 여기에 코드 작성
return null;
}
}정답:
정답 보기
public static ZonedDateTime convertTimezoneModern(
ZonedDateTime dateTime, String toZone) {
return dateTime.withZoneSameInstant(ZoneId.of(toZone));
}
// 사용 예시
public static void main(String[] args) {
ZonedDateTime seoul = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
ZonedDateTime newYork = convertTimezoneModern(seoul, "America/New_York");
System.out.println("Seoul: " + seoul);
System.out.println("New York: " + newYork);
}Date/Calendar:
- 가변 객체 (스레드 불안전)
- 혼란스러운 API (월이 0부터 시작 등)
- 타입 안정성 부족
SimpleDateFormat:
- 스레드 불안전
- 예외 처리 필수
- 성능 이슈
LocalDate/LocalTime/LocalDateTime:
- 불변 객체 (스레드 안전)
- 직관적 API
- 타입 안정성
DateTimeFormatter:
- 스레드 안전
- 메서드 체이닝
- 더 나은 성능
// Date → LocalDateTime
LocalDateTime ldt = date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
// LocalDateTime → Date
Date date = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
// Calendar → LocalDateTime
LocalDateTime ldt = cal.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
// LocalDateTime → Calendar
Calendar cal = GregorianCalendar.from(
ldt.atZone(ZoneId.systemDefault()));1. 내부부터 점진적 변경
2. 레거시 인터페이스 유지하면서 내부 구현 변경
3. 새 메서드 추가 (오버로딩)
4. 기존 메서드 Deprecated
5. 외부 API는 어댑터 패턴 사용
| Chapter | 주제 |
|---|---|
| 01. Time API 개요 | 레거시 문제점, 신규 API 소개 |
| 02. Local 클래스 | LocalDate, LocalTime, LocalDateTime |
| 03. Zoned & Instant | ZonedDateTime, Instant, 시간대 |
| 04. Period & Duration | 날짜 기간, 시간 기간, ChronoUnit |
| 05. DateTimeFormatter | 포맷팅, 파싱, 로케일 |
| 06. 레거시 vs 신규 | 마이그레이션 가이드 |