관리 메뉴

나구리의 개발공부기록

날짜와 시간, 날짜와 시간의 핵심 인터페이스, 날짜와 시간 조회하고 조작하기, 날짜와 시간 문자열 파싱과 포맷팅 본문

인프런 - 실전 자바 로드맵/실전 자바 - 중급 1편

날짜와 시간, 날짜와 시간의 핵심 인터페이스, 날짜와 시간 조회하고 조작하기, 날짜와 시간 문자열 파싱과 포맷팅

소소한나구리 2025. 1. 22. 10:58

출처 : 인프런 - 김영한의 실전 자바 - 중급1편 (유료) / 김영한님  
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용


1. 날짜와 시간의 핵심 인터페이스

1) TemporalAccessor, Temporal, TemporalAmount

(1) 관계도

  • 특정 시점의 시간: Temporal(TemporalAccessor 포함) 인터페이스를 구현하며 구현으로 LocalDateTime, ZonedDateTime, Instant등이 있음
  • 시간의 간격(기간): TemporalAmount 인터페이스를 구현하며 구현으로 Period, Duration이 있음

(2) 인터페이스 설명

  • TemporalAccessor 인터페이스: 날짜와 시간을 읽기 위한 기본 인터페이스로 해당 인터페이스는 특정 시점의 날짜와 시간 정보를 읽을 수 있는 최소한의 기능을 제공함
  • Temporal 인터페이스: TemporalAccessor의 하위 인터페이스로 날짜와 시간을 조작(추가, 빼기 등)하기 위한 기능을 제공하며 이를 통해 날짜와 시간을 변경하거나 조정할 수 있음
  • TemporalAmount 인터페이스: 시간의 간격(시간의 양, 기간)을 나타내며 날짜와 시간 객체에 적용하여 그 객체를 조정할 수 있음
  • 즉, TemporalAccessor는 읽기 전용 접근을, Temporal은 읽기와 쓰기를 모두를 지원하며 TemporalAmount는 특정 날짜에 기간을 더하거나 빼는데 사용함

2) TemporalUnit, TemporalField

 

(1-1) 시간의 단위 - TemporalUnit, ChronoUnit

  • TemporalUnit 인터페이스: 날짜와 시간을 측정하는 단위를 나타내며 주로 사용되는 구현체는 java.time.temporal.ChronoUnit 열거형으로 구현되어있음
  • ChronoUnit 열거형: 다양한 시간 단위를 제공함

(1-2) 시간 단위

ChronoUnit 설명
NANOS 나노초 단위
MICROS 마이크로초 단위
MILLIS 밀리초 단위
SECONDS 초 단위
MINUTES 분 단위
HOURS 시간 단위

 

(1-3) 날짜 단위

ChronoUnit 설명
DAYS 일 단위
WEEKS 주 단위
MONTHS 월 단위
YEARS 년 단위
DECADES 10년 단위
CENTURIES 세기 단위
MILLENNIA 천년 단위

 

(1-4) 기타 단위

ChronoUnit 설명
ERAS 시대 단위
FOREVER 무한대의 시간 단위

 

(1-5) ChronoUnit의 주요 메서드

메서드 이름 설명
between(Temporal, Temporal) 두 Temporal 객체 사이의 시간을 현재 ChronoUnit 단위로 측정하여 반환함
isDateBased() 현재 ChronoUnit이 날짜 기반 단위인지(일, 주, 월, 년) 여부를 반환함
isTimeBased() 현재 ChronoUnit이 시간 기반 단위인지(시, 분, 초) 여부를 반환함
isSupportedBy(Temporal) 주어진 Temporal 객체가 현재 ChronoUnit 단위를 지원하는지 여부를 반환함
getDuration() 현재 ChronoUnit의 기간을 Duration 객체로 반환

 

(2) ChronoUnitMain

  • ChronoUnit을 사용하면 두 날짜 또는 시간 사이의 차이를 원하는 단위로 쉽게 계산할 수 있음
package time;

public class ChronoUnitMain {
    public static void main(String[] args) {
        ChronoUnit[] values = ChronoUnit.values();
        for (ChronoUnit value : values) {
            System.out.println("value = " + value);
        }
        System.out.println("HOURS = " + ChronoUnit.HOURS);
        System.out.println("HOURS.duration = " + ChronoUnit.HOURS.getDuration().getSeconds());
        System.out.println("DAYS = " + ChronoUnit.DAYS);
        System.out.println("DAYS.duration = " + ChronoUnit.DAYS.getDuration().getSeconds());

        // 차이 구하기
        LocalTime lt1 = LocalTime.of(1, 10, 0);
        LocalTime lt2 = LocalTime.of(1, 20, 0);

        long secondsBetween = ChronoUnit.SECONDS.between(lt1, lt2);
        System.out.println("secondsBetween = " + secondsBetween);

        long minuteBetween = ChronoUnit.MINUTES.between(lt1, lt2);
        System.out.println("minuteBetween = " + minuteBetween);
    }
}
/* 실행 결과
value 출력 결과 생략
HOURS = Hours
HOURS.duration = 3600
DAYS = Days
DAYS.duration = 86400
secondsBetween = 600
minuteBetween = 10
*/

 

(3) 시간 필드 - TemporalField, ChronoField

  • 날짜 및 시간을 나타내는데 사용되는 열거형으로 다양한 필드를 통해 날짜와 시간의 특정 부분을 나타냄
  • TemporalField 인터페이스: 날짜와 시간을 나타내는데 사용되며 주로 사용되는 구현체는 java.time.temporal.ChronoField 열거형으로 구현되어 있음
  • ChronoField 열거형: 다양한 필드를 통해 날짜와 시간의 특정 부분을 나타내며 연도, 월, 일, 시간, 분 등이 포함됨
  • 여기서 필드(Field)라는 뜻은 날짜와 시간 주에 있는 특정 필드들을 뜻하는데, 예를 들어 2024년 8월 16일이라고 하면 각각의 필드는 아래와 같음
    - YEAR: 2024
    - MONTH_OF_YEAR: 8
    - DAY_OF_MONTH: 16
  • 단순히 시간의 단위 하나하나를 뜻하는 ChronoUnit과는 차이가 있는데, 해당 월의 일, 해당 년도의 월 처럼 날짜와 시간의 각 필드 중에 원하는 데이터를 조회하려면 ChronoField를 사용해야 함

(3-1) 연도 관련 필드

ChronoField 설명
ERA 연대, 서기(AD) 또는 기원전(BC)
YEAR_OF_ERA 연대 내의 연도
YEAR 연도
EPOCH_DAY 1970-01-01부터의 일 수

 

(3-2) 월 관련 필드

ChronoField 설명
MONTH_OF_YEAR 월 (1월 = 1)
PROLEPTIC_MONTH 연도를 월로 확장한 값

 

(3-3) 주 및 일 관련 필드

ChronoField 설명
DAY_OF_WEEK 요일 (월요일 = 1)
ALIGNED_DAY_OF_WEEK_IN_MONTH 월의 첫 번째 요일을 기준으로 정렬된 요일
ALIGNED_DAY_OF_WEEK_IN_YEAR 연의 첫 번째 요일을 기준으로 정렬된 요일
DAY_OF_MONTH 월의 일 (1일 = 1)
DAY_OF_YEAR 연의 일 (1월 1일 = 1)
EPOCH_DAY 유닉스 에폭(1970-01-01)부터의 일 수

 

(3-4) 시간 관련 필드

ChronoField 설명
HOUR_OF_DAY 시간 (0-23)
CLOCK_HOUR_OF_DAY 시계 시간 (1-24)
HOUR_OF_AMPM 오전/오후 시간 (0-11)
CLOCK_HOUR_OF_AMPM 오전/오후 시계 시간 (1-12)
MINUTE_OF_HOUR 분 (0-59)
SECOND_OF_MINUTE 초 (0-59)
NANO_OF_SECOND 초의 나노초 (0-999,999,999)
MICRO_OF_SECOND 초의 마이크로초 (0-999,999)
MILLI_OF_SECOND 초의 밀리초 (0-999)

 

(3-5) 기타 필드

ChronoField 설명
AMPM_OF_DAY 하루의 AM/PM 부분
INSTANT_SECONDS 초를 기준으로 한 시간
OFFSET_SECONDS UTC/GMT에서의 시간 오프셋 초

 

(3-6) 주요 메서드

메서드 이름 반환 타입 설명
getBaseUnit() TemporalUnit 필드의 기본 단위를 반환, ex) 분 필드의 기본 단위는 ChronoUnit.MINUTES임
getRangeUnit() TemporalUnit 필드의 범위 단위를 반환, ex) MONTH_OF_YEAR의 범위 단위는 ChronoUnit.YEARS임
isDateBased() boolean 필드가 주로 날짜를 기반으로 하는지 여부를 나타냄, YEAR와 같은 날짜 기반 필드는 ture
isTimeBased() boolean 필드가 주로 시간을 기반으로 하는지 여부를 나타냄, HOUR_OF_DAY와 같은 시간 필드는 true
range() ValueRange 필드가 가질 수 있는 값의 유효 범위를 ValueRange 객체로 반환, 객체는 최소값과 최대값을 제공

 

(4) ChronoFieldMain

  • 필드의 범위들을 출력해보기
package time;

public class ChronoFieldMain {
    public static void main(String[] args) {
        ChronoField[] values = ChronoField.values();
        for (ChronoField field : values) {
            System.out.println(field + ", field = " + field.range());
        }

        System.out.println("MONTH_OF_YEAR.range() = " + ChronoField.MONTH_OF_YEAR.range());
        System.out.println("DAY_OF_MONTH.range() = " + ChronoField.DAY_OF_MONTH.range());

    }
}
/* 실행 결과
field 결과 생략
MONTH_OF_YEAR.range() = 1 - 12
DAY_OF_MONTH.range() = 1 - 28/31
*/

 

(5) 정리

  • TemporalUnit(ChronoUnit), TemporalField(ChronoField)는 단독으로 사용하기 보다는 주로 날짜와 시간을 조회하거나 조작할 때 사용함

2. 날짜와 시간 조회하고 조작하기

1) 날짜와 시간 조회하고 조작하기

(1) GetTimeMain - 날짜와 시간 조회

  • 날짜와 시간을 조회하기 위해선 날짜와 시간 항목중에 어떤 필드를 조회할 지 선택해야 하므로 날짜와 시간의 필드를 뜻하는 ChronoField가 사용됨
  • TemporalAccessor.get(TemporalField field)
    - 특정 시점의 시간을 제공하는 클래스는 모두 TemporalAccessor 인터페이스를 구현하는데 이 인터페이스는 특정 시점의 시간을 조회하는 기능인 get()메서드를 제공함
    - get(TemporalField field)을 호출할 때 어떤 날짜와 시간 필드를 조회할 지 TemporalField의 구현체인 ChronoField를 인수로 전달하면 됨
  • 편의 메서드 사용
    - get(...)을 사용하면 코드가 길어지고 불편한 점이 있기 때문에 자주 사용하는 조회 필드는 간단하게 사용할 수 있도록 편의 메서드를 제공함
  • 편의 메서드에 없음
    - 그러나 자주 사용하지 않는 특별한 기능은 편의 메서드를 제공하지 않음
    - 편의 메서드를 사용하는 것이 가독성이 좋기 때문에 일반적으로는 편의 메서드를 사용하고 편의 메서드가 없는 경우 get()을 사용하면 됨
package time;

public class GetTimeMain {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2030, 1, 1, 13, 30, 59);

        System.out.println("오리지널 조회");
        System.out.println("YEAR = " + dt.get(ChronoField.YEAR));
        System.out.println("MONTH_OF_YEAR = " + dt.get(ChronoField.MONTH_OF_YEAR));
        System.out.println("DAY_OF_MONTH = " + dt.get(ChronoField.DAY_OF_MONTH));
        System.out.println("HOUR_OF_DAY = " + dt.get(ChronoField.HOUR_OF_DAY));
        System.out.println("MINUTE_OF_HOUR = " + dt.get(ChronoField.MINUTE_OF_HOUR));
        System.out.println("SECOND_OF_MINUTE = " + dt.get(ChronoField.SECOND_OF_MINUTE));

        System.out.println();

        System.out.println("편의 메서드 제공");
        System.out.println("YEAR = " + dt.getYear());
        System.out.println("MONTH_OF_YEAR = " + dt.getMonthValue());
        System.out.println("DAY_OF_MONTH = " + dt.getDayOfMonth());
        System.out.println("HOUR_OF_DAY = " + dt.getHour());
        System.out.println("MINUTE_OF_HOUR = " + dt.getMinute());
        System.out.println("SECOND_OF_MINUTE = " + dt.getSecond());

        System.out.println("편의 메서드 없음");
        System.out.println("MINUTE_OF_DAY = " + dt.get(ChronoField.MINUTE_OF_DAY));
        System.out.println("SECOND_OF_DAY = " + dt.get(ChronoField.SECOND_OF_DAY));

    }
}

 

(2) ChangeTimePlusMain - 날짜와 시간 조작

  • 날짜와 시간을 조작하려면 어떤 시간 단위(Unit)를 변경할 지 선택해야 하므로 날짜와 시간의 단위를 뜻하는 ChronoUnit이 사용됨
  • Temporal.plus(long amountToAdd, TemporalUnit unit)
    - 특정 시점의 시간을 제공하는 클래스는 모두 Temporal 인터페이스를 구현하므로 Temporal이 제공하는 특정 시점의 시간을 조작하는 기능을 사용할 수 있음
    - plus(long amountToAdd, TemporalUnit unit)를 호출할 때 더하기 할 숫자와 시간의 단위(Unit)를 전달하면되며 시간의 단위는 TemporalUnit의 구현인 ChronoUnit을 인수로 전달하면됨
    - 불변이므로 반환 값을 받아야 하며 minus()도 존재함
  • 편의 메서드 사용
    - 조회와 마찬가지로 자주 사용하는 메서드는 편의 메서드가 제공되므로 가독성이 좋은 편의 메서드를 사용하면 됨
  • Period를 사용한 조작
    - Period나 Duration은 기간을 뜻하므로 특정 시점의 시간에 기간을 더할 수 있음
package time;

public class ChangeTimePlusMain {
    public static void main(String[] args) {

        LocalDateTime dt = LocalDateTime.of(2018, 1, 1, 13, 30, 59);
        System.out.println("dt = " + dt);

        LocalDateTime plusDt1 = dt.plus(1, ChronoUnit.YEARS);
        System.out.println("plusDt1 = " + plusDt1);

        LocalDateTime plusDt2 = dt.plusYears(10);
        System.out.println("plusDt2 = " + plusDt2);

        Period period = Period.ofYears(10);
        LocalDateTime plusDt3 = dt.plus(period);
        System.out.println("plusDt3 = " + plusDt3);
    }
}

 

(3) 정리

  • 시간을 조회하고 조작하는 부분을 보면 TemporalAccessor.get(), Temporal.plus()와 같은 인터페이스를 통해 특정 구현 클래스와 무관하게 아주 일관성 있는 시간 조회, 조작 기능을 제공하고 있음
  • 덕분에 수 많은 구현에 관계 없이 일관성 있는 방법으로 시간을 조회하고 조작할 수 있음

(4-1) IsSupportedMain1

  • 하지만 LocalDate와 같이 날짜 정보만 가지고 있고 시, 분, 초에 대한 정보가 없는곳에 시, 분, 초에 대한 정보를 조회하려고하면 예외가 발생함
  • 인텔리제이를 사용하면 이러한 부분도 노란줄로 경고를 띄워줌
package time;

public class IsSupportedMain1 {
    public static void main(String[] args) {
        LocalDate now = LocalDate.now();
        int minute = now.get(ChronoField.SECOND_OF_MINUTE);
        System.out.println("minute = " + minute);
    }
}
/* 실행 결과
UnsupportedTemporalTypeException 예외 발생
*/

 

(4-2) IsSupportedMain2

  • 위와 같은 문제를 예방하기 위해서 TemporalAccessor와 Temporal 인터페이스는 현재 타입에서 특정 시간 단위나 필드를 사용할 수 있는지 확인할 수 있는 메서드를 제공함
  • LocalDate는 SECOND_OF_MINUTE 필드를 지원하지 않으므로 isSupported()의 결과가 false로 반환되며 이를 통해 동적으로 날짜를 조작할 수 있음
package time;

public class IsSupportedMain2 {
    public static void main(String[] args) {
        LocalDate now = LocalDate.now();
        boolean supported = now.isSupported(ChronoField.SECOND_OF_MINUTE);
        System.out.println("minute = " + supported);
        if (supported) {
            int minute = now.get(ChronoField.SECOND_OF_MINUTE);
            System.out.println("second = " + minute);
        }
    }
}
/* 실행 결과
minute = false
*/

 

(5) ChangeTimeWithMain

  • Temporal with(TemporalField field, long newValue): 날짜와 시간의 특정 필드의 값만 변경할 수 있으며 불변이므로 반환값을 받아야 함
  • 편의 메서드: with()메서드도 자주 사용하는 메서드는 편의 메서드가 제공됨
  • TemporalAdjuster 인터페이스 사용
    - with()는 아주 간단한 날짜만 변경할 수 있으므로 다음 금요일, 이번 달의 마지막 일요일 같은 복잡한 날짜를 계산을 할때 사용함
    - 원래대로면 인터페이스를 구현해야하지만 자바는 이미 필요한 구현체들을 TemporalAdjusters에 모두 만들어 두었기 때문에 개발자는 이 구현체를 사용하면 됨
    - TemporalAdjusters.next(DayOfWeek.FRIDAY): 다음 금요일을 구함
    TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY): 이번 달의 마지막 일요일을 구함
  • DayOfWeek: 월, 화, 수, 목, 금, 토, 일을 나타내는 열거형
package time;

public class ChangeTimeWithMain {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2018, 1, 1, 13, 30, 59);
        System.out.println("dt = " + dt);

        LocalDateTime changeDt1 = dt.with(ChronoField.YEAR, 2020);
        System.out.println("changeDt1 = " + changeDt1);

        LocalDateTime changeDt2 = dt.withYear(2020);
        System.out.println("changeDt2 = " + changeDt2);

        // TemporalAdjuster 사용
        // 다음주 금요일
        LocalDateTime with1 = dt.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
        System.out.println("기준 날짜: " + dt);
        System.out.println("다음 금요일 날짜: " + with1);

        // 이번 달의 마지막 일요일
        LocalDateTime with2 = dt.with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));
        System.out.println("같은 달의 마지막 일요일: " + with2);
    }
}
/* 실행 결과
dt = 2018-01-01T13:30:59
changeDt1 = 2020-01-01T13:30:59
changeDt2 = 2020-01-01T13:30:59
기준 날짜: 2018-01-01T13:30:59
다음 금요일 날짜: 2018-01-05T13:30:59
같은 달의 마지막 일요일: 2018-01-28T13:30:59
*/

 

(6) TemporalAdjusters 클래스가 제공하는 주요 기능

메서드 설명
dayOfWeekInMonth 주어진 요일이 몇 번째인지에 따라 날짜를 조정
firstDayOfMonth 해당 월의 첫째 날로 조정
firstDayOfNextMonth 다음 달의 첫째 날로 조정
firstDayOfNextYear 다음 해의 첫째 날로 조정
firstDayOfYear 해당 해의 첫째 날로 조정
firstInMonth 주어진 요일 중 해당 월의 첫 번째 요일로 조정
lastDayOfMonth 해당 월의 마지막 날로 조정
lastDayOfNextMonth 다음 달의 마지막 날로 조정
lastDayOfNextYear 다음 해의 마지막 날로 조정
lastDayOfYear 해당 해의 마지막 날로 조정
lastInMonth 주어진 요일 중 해당 월의 마지막 요일로 조정
next 주어진 요일 이후의 가장 가까운 요일로 조정
nextOrSame 주어진 요일 이후의 가장 가까운 요일로 조정하되 현재 날짜가 주어진 요일인 경우 현재 날짜를 반환
previous 주어진 요일 이전의 가장 가까운 요일로 조정
previousOrSame 주어진 요일 이전의 가장 가까운 요일로 조정하되 현재 날짜가 주어진 요일인 경우 현재 날짜를 반환

3. 날짜와 시간 문자열 파싱과 포맷팅

1) 날짜와 시간 문자열 파싱과 포맷팅

(1) 파싱과 포맷팅

  • 포맷팅: 날짜와 시간 데이터를 원하는 포맷의 문자열로 변경하는 것, Date -> String
  • 파싱: 문자열을 날짜와 시간 데이터를 변경하는 것, String -> Date

(2) FormattingMain1

  • 날짜 객체를 원하는 형태로 변경하려면 DateTimeFormatter의 ofPattern() 메서드로 원하는 포맷을 지정하면 됨
  • 문자열을 날짜 객체로 파싱을 할 때도 DateTimeFormatter를 사용하는데, 문자열의 어떤 부분이 년이고 월이고 일인지 위치를 정해서 읽어야 하므로 입력된 문자열이 ofPattern()으로 정의한 포맷과 일치해야 함
package time;

public class FormattingMain1 {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2024, 12, 31);
        System.out.println("date = " + date);

        // 포맷팅
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
        String formattedDate = date.format(formatter);
        System.out.println("날짜와 시간 포맷팅 = " + formattedDate);

        // 파싱: 문자를 날짜로, formatter의 형식과 같아야함
        String input = "2030년 01월 01일";
        LocalDate parsedDate = LocalDate.parse(input, formatter);
        System.out.println("문자열 파싱 날짜와 시간 = " + parsedDate);
    }
}
/* 실행 결과
date = 2024-12-31
날짜와 시간 포맷팅 = 2024년 12월 31일
문자열 파싱 날짜와 시간 = 2030-01-01
*/

 

(3) FormattingMain2

  • 날짜와 시간까지 포함하여 문자열을 포맷팅하고 파싱
  • 포맷팅과 파싱 방법은 동일함
package time;

public class FormattingMain2 {
    public static void main(String[] args) {

        LocalDateTime now = LocalDateTime.of(2024, 12, 31, 13, 30, 59);

        // 포맷팅
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String formattedDateTime = now.format(formatter);
        System.out.println("날짜와 시간 포맷팅 = " + formattedDateTime);

        // 파싱
        String dateTimeString = "2030-01-01 11:30:00";
        LocalDateTime dateTime = LocalDateTime.parse(dateTimeString, formatter);
        System.out.println("문자열 파싱 날짜와 시간 = " + dateTime);
    }
}

/* 실행 결과
날짜와 시간 포맷팅 = 2024-12-31 13:30:59
문자열 파싱 날짜와 시간 = 2030-01-01T11:30
*/

 

(4) DateTimeFormatter 패턴

더보기
기호 의미 표현 방식 예시
G 연호 텍스트 AD; Anno Domini; A
u 연도 연도 2004; 04
y 연대의 연도 연도 2004; 04
D 연중 일수 숫자 189
M/L 연중 월 숫자/텍스트 7; 07; Jul; July; J
d 월의 일수 숫자 10
Q/q 분기 숫자/텍스트 3; 03; Q3; 3rd quarter
Y 주 기준 연도 연도 1996; 96
w 주 기준 연중 주 숫자 27
W 월의 주 숫자 4
E 요일 텍스트 Tue; Tuesday; T
e/c 지역화된 요일 숫자/텍스트 2; 02; Tue; Tuesday; T
F 월의 주 숫자 3
a 오전/오후 텍스트 PM
h 오전/오후 시(1-12) 숫자 12
K 오전/오후 시(0-11) 숫자 0
k 24시간제 시(1-24) 숫자 0
H 24시간제 시(0-23) 숫자 0
m 숫자 30
s 숫자 55
S 초의 일부 분수 978
A 일의 밀리초 숫자 1234
n 초의 나노초 숫자 987654321
N 일의 나노초 숫자 1234000000
V 시간대 ID 시간대 ID America/Los_Angeles; Z; -08:30
z 시간대 이름 시간대 이름 Pacific Standard Time; PST
O 지역화된 시간대 오프셋 오프셋-O GMT+8; GMT+08:00; UTC-08:00;
X 'Z'로 표시된 시간대 오프셋 오프셋-X Z; -08; -0830; -08:30; -083015; -08:30:15;
x 시간대 오프셋 오프셋-x +0000; -08; -0830; -08:30; -083015; -08:30:15;
Z 시간대 오프셋 오프셋-Z +0000; -0800; -08:00;
p 다음 패딩 패드 수정자 1
' 텍스트를 위한 이스케이프 구분자  
" 단일 인용 부호 리터럴 '

4. 문제와 풀이

1) 날짜 더하기

(1) 문제 설명

  • 2024년 1월 1일 0시 0분 0초에 1년 2개월 3일 4시간 후의 시각을 찾아라
  • TestPlus 클래스를 생성
더보기

실행 결과

기준 시각: 2024-01-01T00:00

1 2개월 3 4시간 후의 시각: 2025-03-04T04:00

(2) 정답

더보기
package time.test;

public class TestPlus {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2024, 1, 1, 0, 0, 0);
        System.out.println("기준 시각: " + dt);
        
        LocalDateTime plusDateTime = dt.plusDays(1).plusMonths(2).plusDays(3).plusHours(4);
        System.out.println("1년 2개월 3일 4시간 후의 시각: " + plusDateTime);

    }
}

2) 날짜 간격 반복 출력하기

(1) 문제 설명

  • 2024년 1월 1일부터 2주 간격으로 5번 반복하여 날짜를 출력하는 코드를 작성
  • TestLoopPlus 클래스를 작성
더보기

실행결과

날짜 1: 2024-01-01

날짜 2: 2024-01-15

날짜 3: 2024-01-29

날짜 4: 2024-02-12

날짜 5: 2024-02-26

 

(2) 정답

더보기

직접 푼 버전

package time.test;

public class TestLoopPlus {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.of(2024, 1, 1);

        for (int i = 1; i <= 5; i++) {
            System.out.println("날짜 " + i +": " + localDate);
            localDate = localDate.plusWeeks(2);
        }
    }
}

 

강의 버전

package time.test;

public class TestLoopPlus2 {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.of(2024, 1, 1);

        for (int i = 0; i < 5; i++) {
            localDate = localDate.plusWeeks(2 * i);
            System.out.println("날짜 " + (i + 1) + ": " + localDate);
        }
    }
}

3) 디데이 구하기

(1) 문제 설명

  • 시작 날짜와 목표 날짜를 입력하여 남은 기간과 디데이를 구하기
  • 남은 기간: x년 x개월 x일 형식으로 출력
  • 디데이: x일 남은 형식으로 출력
더보기
package time.test;

import java.time.LocalDate;

public class TestBetween {
    
    public static void main(String[] args) {
        LocalDate startDate = LocalDate.of(2024, 1, 1);
        LocalDate endDate = LocalDate.of(2024, 11, 21);
        // 코드 작성
    }
}

 

실행 결과

시작 날짜: 2024-01-01

목표 날짜: 2024-11-21

남은 기간: 0 10개월 20

디데이: 325 남음

 

(2) 정답

더보기
package time.test;

public class TestBetween {

    public static void main(String[] args) {
        LocalDate startDate = LocalDate.of(2024, 1, 1);
        LocalDate endDate = LocalDate.of(2024, 11, 21);

        Period period = Period.between(startDate, endDate);
        long dayBetween = ChronoUnit.DAYS.between(startDate, endDate);

        System.out.println("시작 날짜: " + startDate);
        System.out.println("목표 날짜: " + endDate);
        System.out.println("남은 기간: " + period.getYears() + "년 " + period.getMonths() + "개월 " + period.getDays() + "일");
        System.out.println("디데이: " + dayBetween + "일 남음");
    }
}

 

4) 시작 요일, 마지막 요일 구하기

(1) 문제 설명

  • 제공되는 월의 첫날 요일과 마지막 요일을 구하기
더보기
package time.test;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class TestAdjusters {
    public static void main(String[] args) {
        int year = 2024;
        int month = 1;
        // 코드 작성
    }
}

 

실행 결과

firstDayOfWeek = MONDAY

lastDayOfWeek = WEDNESDAY

 

(2) 정답

더보기
package time.test;

public class TestAdjusters {
    public static void main(String[] args) {
        int year = 2024;
        int month = 1;

        LocalDate date = LocalDate.of(year, month, 1);
        DayOfWeek firstDayOfWeek = date.getDayOfWeek();
        DayOfWeek lastDayOfWeek = date.with(TemporalAdjusters.lastDayOfMonth()).getDayOfWeek();

        System.out.println("firstDayOfWeek = " + firstDayOfWeek);
        System.out.println("lastDayOfWeek = " + lastDayOfWeek);
    }
}

5) 국제 회의 시간

(1) 문제 설명

  • 서울의 회의 시간은 2024년 1월 1일 오전9시 일때 런던, 뉴욕의 회의 시간을 구하기
  • 실행 결과를 참고하여 TestZone 클래스를 작성
더보기

실행 결과

서울의 회의 시간: 2024-01-01T09:00+09:00[Asia/Seoul]

런던의 회의 시간: 2024-01-01T00:00Z[Europe/London]

뉴욕의 회의 시간: 2023-12-31T19:00-05:00[America/New_York]

 

(2) 정답

더보기
package time.test;

public class TestZone {
    public static void main(String[] args) {

        ZonedDateTime seoulTime = ZonedDateTime.of(2024, 1, 1, 9, 0, 0, 0, ZoneId.of("Asia/Seoul"));
        ZonedDateTime londonTime = seoulTime.withZoneSameInstant(ZoneId.of("Europe/London"));
        ZonedDateTime newyorkTime = seoulTime.withZoneSameInstant(ZoneId.of("America/New_York"));

        System.out.println("서울의 회의 시간: " + seoulTime);
        System.out.println("런던의 회의 시간: " + londonTime);
        System.out.println("뉴욕의 회의 시간: " + newyorkTime);

    }
}

6) 달력 출력하기

(1) 문제 설명

  • 실행 결과를 참고해서 달력을 출력
  • 입력 조건: 년도, 월
  • 실행시 날짜의 간격에는 신경쓰지 말고 간격을 맞추는 부분은 정답을 참고하여 수정해도 됨
더보기

실행 결과

 

(2) 정답

더보기

직접 푼 버전

package time.test;

public class TestCalender {
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        System.out.print("년도를 입력하세요: ");
        int year = sc.nextInt();

        System.out.print("월을 입력하세요: ");
        int month = sc.nextInt();

        printCalender(year, month);
    }

    private static void printCalender(int year, int month) {
        LocalDate startDate = LocalDate.of(year, month, 1);
        LocalDate endDate = startDate.plusMonths(1);

        int space = startDate.getDayOfWeek().getValue();
        long days = ChronoUnit.DAYS.between(startDate, endDate);

        System.out.println("Su\tMo\tTu\tWe\tTh\tFr\tSa");

        for (int i = 0; i < space; i++) {
            if (space == 7) {
                break;
            }
            System.out.print("\t");
        }

        for (int i = 1; i <= days; i++) {
            System.out.printf("%2d\t", i);
            LocalDate plusDay = startDate.plusDays(i);
            if(plusDay.getDayOfWeek() == DayOfWeek.SUNDAY) {
                System.out.println();
            }
        }
    }
}

 

강의 정답 버전

  • while문과 isBefore를 활용
  • 달력 시작 시 공백 찍는 로직을 % 연산을 활용하여 공백찍는 횟수를 산정
package time.test;

public class TestCalendarPrinter {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("년도를 입력하세요: ");
        int year = scanner.nextInt();

        System.out.print("월을 입력하세요: ");
        int month = scanner.nextInt();

        LocalDate firstDayOfMonth = LocalDate.of(year, month, 1);
        LocalDate firstDayOfNextMonth = firstDayOfMonth.plusMonths(1);

        // 월요일 = 1 ... 일요일 = 7, 7로 나눈 나머지만큼 공백을 출력
        int offsetWeekDays = firstDayOfMonth.getDayOfWeek().getValue() % 7;

        System.out.println("Su Mo Tu We Th Fr Sa ");

        for (int i = 0; i < offsetWeekDays; i++) {
            System.out.print("   "); // 공백 3번
        }

        LocalDate dayIterator = firstDayOfMonth;    // 변수 이름을 명시적으로
        while (dayIterator.isBefore(firstDayOfNextMonth)) {

            System.out.printf("%2d ", dayIterator.getDayOfMonth());
            if (dayIterator.getDayOfWeek() == DayOfWeek.SATURDAY) {
                System.out.println();
            }
            dayIterator = dayIterator.plusDays(1);
        }
    }
}