java.time
java.time 패키지 (Java 8 Date and Time API)
java.time 패키지는 Java 8에서 새롭게 도입된 날짜와 시간 API. 기존의 java.util.Date와 java.util.Calendar 클래스의 문제점(가변성, 부족한 기능, 일관성 없는 API, 타임존 처리의 어려움 등)을 해결하고, 더 직관적이고 강력하며 스레드 안전한(thread-safe) 날짜/시간 기능을 제공.
java.time 패키지의 주요 특징:
- 불변성 (Immutability): java.time 패키지의 모든 클래스는 불변(immutable). 즉, 객체의 상태를 변경할 수 없다. 날짜/시간을 변경하는 모든 작업은 새로운 객체를 반환. 이 덕분에 멀티스레드 환경에서 안전하게 사용할 수 있고, 예기치 않은 버그를 방지할 수 있다.
- 명확하고 직관적인 API: 메서드 이름과 동작이 명확하고 일관성이 있어서 사용하기 쉽다. 예를 들어, plusDays(), minusMonths(), withYear()과 같이 직관적인 메서드를 제공.
- 풍부한 기능: 날짜/시간 연산, 포매팅, 파싱, 타임존 처리, 기간(duration) 및 주기(period) 계산 등 다양한 기능을 제공.
- 타임존 지원 강화: ZonedDateTime, ZoneId, ZoneOffset 등의 클래스를 통해 타임존을 명확하게 처리할 수 있다.
- ISO 8601 표준 준수: 날짜와 시간 표현에 대한 국제 표준인 ISO 8601을 따른다.
- 확장 가능성: Temporal 인터페이스를 기반으로 하여 다양한 시간 시스템(예: 다른 달력 시스템)을 지원할 수 있도록 설계됨.
java.time 패키지의 주요 클래스:
- LocalDate: 날짜 (년, 월, 일)를 나타내는 불변 클래스. 시간 정보는 포함하지 않는다.
LocalDate today = LocalDate.now(); // 현재 날짜
LocalDate christmas = LocalDate.of(2024, 12, 25); // 2024년 12월 25일
LocalDate parsedDate = LocalDate.parse("2023-10-27"); // 문자열 파싱
System.out.println(christmas.getYear()); // 년
System.out.println(christmas.getMonth()); // 월 (Month enum)
System.out.println(christmas.getMonthValue()); // 월 (1-12)
System.out.println(christmas.getDayOfMonth()); // 일
System.out.println(christmas.getDayOfWeek()); // 요일(DayOfWeek enum)
System.out.println(christmas.getDayOfYear());//연중 몇번째 일
LocalDate weekAfter = christmas.plusWeeks(1); //1주 뒤
LocalDate yearBefore = christmas.minusYears(1); //1년 전
- LocalTime: 시간 (시, 분, 초, 나노초)을 나타내는 불변 클래스. 날짜 정보는 포함하지 않는다.
LocalTime now = LocalTime.now();
LocalTime lunchTime = LocalTime.of(12, 30); // 12시 30분
LocalTime parsedTime = LocalTime.parse("15:45:30");
System.out.println(lunchTime.getHour());
System.out.println(lunchTime.getMinute());
System.out.println(lunchTime.getSecond());
System.out.println(lunchTime.getNano()); //나노초
LocalTime afterOneHour = now.plusHours(1);
- LocalDateTime: 날짜와 시간을 모두 나타내는 불변 클래스. LocalDate와 LocalTime을 조합한 형태.
LocalDateTime now = LocalDateTime.now();
LocalDateTime meeting = LocalDateTime.of(2024, 10, 29, 14, 0, 0); // 2024-10-29 14:00:00
LocalDateTime parsedDateTime = LocalDateTime.parse("2023-10-27T10:15:30"); // ISO 8601 형식
//날짜와 시간 정보 모두 사용 가능
System.out.println(meeting.getYear()); //년
System.out.println(meeting.getMonth()); //월
System.out.println(meeting.getDayOfMonth()); //일
System.out.println(meeting.getHour()); //시
System.out.println(meeting.getMinute()); //분
LocalDateTime nextWeek = meeting.plusWeeks(1);
- ZonedDateTime: 특정 타임존(TimeZone)의 날짜와 시간을 나타내는 불변 클래스. LocalDateTime에 타임존 정보를 추가한 형태.
ZonedDateTime nowInSeoul = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
ZonedDateTime newYorkTime = ZonedDateTime.of(2024, 10, 29, 10, 0, 0, 0, ZoneId.of("America/New_York"));
ZonedDateTime parsedZonedDateTime = ZonedDateTime.parse("2023-10-27T10:15:30+09:00[Asia/Seoul]");
System.out.println(nowInSeoul); // 2024-10-29T11:17:25.777920+09:00[Asia/Seoul]
System.out.println(newYorkTime);
// 타임존 변환
ZonedDateTime seoulTime = newYorkTime.withZoneSameInstant(ZoneId.of("Asia/Seoul"));
- Instant: 특정 시점(타임스탬프)을 나타내는 불변 클래스. 기계 시간(machine time)을 표현하는 데 사용. 1970년 1월 1일 00:00:00 UTC (에포크)부터 경과한 나노초를 기준으로 합니다.
Instant now = Instant.now(); // 현재 UTC 시점
Instant epoch = Instant.ofEpochSecond(0); // 1970-01-01T00:00:00Z
Instant fromEpochMilli = Instant.ofEpochMilli(1666934800000L); // 밀리초 기준
System.out.println(now);
System.out.println(now.getEpochSecond());//초
System.out.println(now.getNano()); //나노초
- Duration: 두 시점 간의 시간 간격(duration)을 나타내는 불변 클래스. 초와 나노초 단위로 표현.
LocalDateTime start = LocalDateTime.of(2023, 10, 27, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2023, 10, 27, 12, 30, 0);
Duration duration = Duration.between(start, end);
System.out.println(duration.toHours()); // 시간
System.out.println(duration.toMinutes()); // 분
System.out.println(duration.getSeconds()); // 초
LocalTime time1 = LocalTime.of(10, 30, 0);
LocalTime time2 = LocalTime.of(18, 0, 0);
Duration between = Duration.between(time1, time2);
- Period: 두 날짜 간의 기간(period)을 나타내는 불변 클래스입니다. 년, 월, 일 단위로 표현.
LocalDate startDate = LocalDate.of(2023, 1, 1);
LocalDate endDate = LocalDate.of(2024, 10, 29);
Period period = Period.between(startDate, endDate);
System.out.println(period.getYears()); // 년
System.out.println(period.getMonths()); // 월
System.out.println(period.getDays()); // 일
- ZoneId: 타임존을 나타내는 클래스. (예: "Asia/Seoul", "America/New_York")
ZoneId seoul = ZoneId.of("Asia/Seoul");
ZoneId newYork = ZoneId.of("America/New_York");
ZoneId utc = ZoneId.of("UTC");
ZoneId systemDefault = ZoneId.systemDefault();//시스템 기본 타임존
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds(); // 사용 가능한 타임존 ID 목록
- ZoneOffset: UTC로부터의 시간 오프셋(offset)을 나타내는 클래스. (예: +09:00, -05:00)
ZoneOffset seoulOffset = ZoneOffset.of("+09:00");
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
- DayOfWeek: 요일을 나타내는 enum
- Month: 월을 나타내는 enum
java.time.format.DateTimeFormatter (날짜/시간 포매팅 및 파싱):
java.time 패키지의 클래스들은 toString() 메서드를 통해 ISO 8601 형식의 문자열로 표현된다. 하지만 원하는 형식으로 날짜/시간을 출력하거나, 특정 형식의 문자열을 날짜/시간 객체로 파싱하려면 java.time.format.DateTimeFormatter를 사용해야 한다.
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
public class DateTimeFormatterExample {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
// 미리 정의된 포맷터 사용
DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_DATE_TIME;
String formattedIso = now.format(isoFormatter);
System.out.println("ISO Format: " + formattedIso);
// 사용자 정의 패턴
DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedCustom = now.format(customFormatter);
System.out.println("Custom Format: " + formattedCustom);
DateTimeFormatter customFormatter2 = DateTimeFormatter.ofPattern("MMMM dd, yyyy hh:mm a", Locale.US);
String formattedCustom2 = now.format(customFormatter2);
System.out.println("Custom Format2: " + formattedCustom2);
// Localized 포맷 (SHORT, MEDIUM, LONG, FULL)
DateTimeFormatter localizedFormatterShort = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
String formattedShort = now.format(localizedFormatterShort);
System.out.println("Short Format: " + formattedShort); //23. 10. 29. 오후 2:43
DateTimeFormatter localizedFormatterMedium = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
String formattedMedium = now.format(localizedFormatterMedium);
System.out.println("Medium Format: "+ formattedMedium); //2023. 10. 29. 오후 2:44:33
// 문자열 파싱
String dateString = "2023-10-27 15:30:00";
DateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime parsedDateTime = LocalDateTime.parse(dateString, parser);
System.out.println("Parsed DateTime: " + parsedDateTime);
}
}
DateTimeFormatter의 주요 패턴 문자:
- yyyy: 년 (4자리)
- yy: 년 (2자리)
- MM: 월 (2자리, 01-12)
- M: 월 (1자리 또는 2자리, 1-12)
- LLLL: 월 이름(긴 형식)
- MMM: 월 이름(짧은 형식)
- dd: 일 (2자리, 01-31)
- d: 일 (1자리 또는 2자리, 1-31)
- HH: 시 (24시간 형식, 00-23)
- H: 시 (24시간 형식, 0-23)
- hh: 시 (12시간 형식, 01-12)
- h: 시 (12시간 형식, 1-12)
- mm: 분 (00-59)
- ss: 초 (00-59)
- SSS: 밀리초 (000-999)
- n: 나노초
- E: 요일 (짧은 형식, 예: "월")
- EEEE: 요일 (긴 형식, 예: "월요일")
- a: 오전/오후 (AM/PM)
- z: 타임존 이름 (예: "Asia/Seoul")
- Z: 타임존 오프셋 (예: "+0900")
- XXX: ISO 8601 타임존 오프셋 (예: "+09:00")
TemporalAdjuster (날짜/시간 조정):
TemporalAdjuster 인터페이스는 날짜/시간 객체를 특정 규칙에 따라 조정하는 기능을 제공. TemporalAdjusters 클래스는 유용한 TemporalAdjuster 구현체들을 제공.
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
public class TemporalAdjusterExample {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
// 다음 주 월요일
LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
System.out.println("Next Monday: " + nextMonday);
// 이번 달의 마지막 날
LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth());
System.out.println("Last day of month: " + lastDayOfMonth);
// 특정 요일의 첫번째 날
LocalDate firstMondayOfYear = today.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
System.out.println("First Monday of the year: " + firstMondayOfYear);
}
}
ChronoUnit (날짜/시간 단위):
ChronoUnit enum은 날짜/시간 단위를 나타냄 (YEARS, MONTHS, DAYS, HOURS, MINUTES, SECONDS 등). Duration과 Period를 사용할 때, 또는 Temporal 인터페이스의 plus()/minus() 메서드에서 단위를 지정할 때 유용.
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
LocalDate today = LocalDate.now();
LocalDate nextYear = today.plus(1, ChronoUnit.YEARS); // 1년 후
LocalDate tenDaysAgo = today.minus(10, ChronoUnit.DAYS); // 10일 전
java.util 패키지와의 상호 운용성:
java.util.Date와 java.util.Calendar를 java.time 패키지의 클래스로 변환하거나, 그 반대로 변환해야 하는 경우가 있을 수 있다.
- java.util.Date -> java.time.Instant: Date.toInstant()
- java.time.Instant -> java.util.Date: Date.from(Instant)
- java.util.Date -> java.time.LocalDateTime: LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault())
- java.time.LocalDateTime -> java.util.Date: Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant())
- java.util.Calendar -> java.time.ZonedDateTime: calendar.toInstant().atZone(calendar.getTimeZone().toZoneId())
- java.time.ZonedDateTime -> java.util.Calendar: GregorianCalendar.from(zonedDateTime)
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
public class ConversionExample {
public static void main(String[] args) {
// Date -> Instant
Date utilDate = new Date();
Instant instantFromDate = utilDate.toInstant();
System.out.println("Date -> Instant: " + instantFromDate);
// Instant -> Date
Instant instant = Instant.now();
Date dateFromInstant = Date.from(instant);
System.out.println("Instant -> Date: " + dateFromInstant);
// Date -> LocalDateTime
LocalDateTime localDateTimeFromDate = LocalDateTime.ofInstant(utilDate.toInstant(), ZoneId.systemDefault());
System.out.println("Date -> LocalDateTime: " + localDateTimeFromDate);
// LocalDateTime -> Date
LocalDateTime localDateTime = LocalDateTime.now();
Date dateFromLocalDateTime = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("LocalDateTime -> Date: " + dateFromLocalDateTime);
// Calendar -> ZonedDateTime
Calendar calendar = Calendar.getInstance();
ZonedDateTime zonedDateTimeFromCalendar = calendar.toInstant().atZone(calendar.getTimeZone().toZoneId());
System.out.println("Calendar -> ZonedDateTime: " + zonedDateTimeFromCalendar);
// ZonedDateTime -> Calendar
ZonedDateTime zonedDateTime = ZonedDateTime.now();
Calendar calendarFromZonedDateTime = GregorianCalendar.from(zonedDateTime);
System.out.println("ZonedDateTime -> Calendar: " + calendarFromZonedDateTime.getTime()); //Calendar는 .getTime()으로 출력
}
}
java.time 패키지 사용 시 권장 사항:
- java.util.Date와 java.util.Calendar 사용 최소화: 새로운 코드를 작성할 때는 java.time 패키지를 우선적으로 사용하고, 기존 코드와의 호환성을 위해서만 java.util.Date와 java.util.Calendar를 사용.
- 불변성 활용: java.time 객체는 불변이므로, 메서드 호출 시 새로운 객체가 반환됨.
- 명확한 타임존 처리: 타임존을 명시적으로 처리해야 하는 경우에는 ZonedDateTime을 사용하고, ZoneId를 사용하여 타임존을 지정.
- DateTimeFormatter 사용: 날짜/시간을 포매팅하거나 파싱할 때는 DateTimeFormatter를 사용.
SimpleDateFormat은 thread-safe하지 않으므로 주의. - Duration과 Period 구분: 시간 간격(초, 나노초)은 Duration, 기간(년, 월, 일)은 Period를 사용.