🌑 날짜와 시간
✔️ Calander & Date
🔹 Calander · GregorianCalendar
- Calander class는 추상클래스(abstract class)이기 때문에 직접 객체를 생성하지 않고,
getInstance() 메서드를 통해 완전히 구현된 class의 instance를 얻어야 한다.
➠ 최소한의 변경으로 프로그램을 실행시키기 위해 !
// Calendar class를 구현한 class로는 GregorianCalendar와 BuddhistCalendar가 있다.
Calendar cal = Calendar.getInstance(); // Code-1
Calendar cal2 = new GregorianCalendar(); // Code-2
🔻 태국(BuddhistCalendar)을 제외한 나머지 국가에서는 GregorianCalendar의 instance를 반환
🔻 Code-2와 같이 특정 instance를 직접 생성해도 되지만 그렇게 한다면,
새로운 역법(calendar)이 사용되거나 추가될 경우 해당 class를 변경해야 해서 번거롭다
➠ getInstance() 메서드를 이용한다면 getInstance() 메서드의 코드만 수정 !
🔹 Date와 Calender 간의 변환
- Calander class가 추가되면서 Date class의 대부분의 메서드 ➠ 'deprecated' (사용 권장 X 의미)
- Calander를 Date로 또는 그 반대로 변환해야 할 일이 생길 경우
// Calendar를 Date로 변환
Calendar cal = Calendar.getInstance();
Date d = new Date(cal.getTimeInMillis());
// Date를 Calendar로 변환
Date d = new Date();
Calender cal = Calendar.getInstance();
cal.setTime(d);
- getInstance() 메서드를 통해서 얻은 instance ➠ 현재 시스템의 날짜와 시간에 대한 정보를 담고 있다
- set 메서드 ➠ 원하는 날짜나 시간으로 설정
void set(int field, int value)
void set(int year, int month, int date)
void set(int year, int month, int date, int hourOfDay, int minute)
void set(int year, int month, int date, int hourOfDay, int minute, int second)
- add(int field, int amount)
➠ 지정한 field의 값을 원하는 만큼 증가 또는 감소시킨다.
➠ 날짜 field(Calendar.DATE)의 값을 31만큼 증가시키면, 월 field(Calendar.MONTH)의 값도 1 증가
⟺ roll(int field, int amount)
➠ field의 값들이 서로 영향 X
➠ 단, Calendar.DATE가 말일(end of month)일 때에는 Calendar.MONTH를 변경하면 Calendar.DATE에도 영향 O
Calendar sDay = Calendar.getInstance(); // 시작일
Calendar eDay = Calendar.getInstance(); // 끝일
sDay.set(year, month-1, 1); // 현재달의 시작일로 설정 (Calaander.MONTH의 범위 : 0~11)
eDay.set(year, month, 1); // 다음달의 시작일로 설정
// 다음달의 첫날에서 하루를 빼면 현재달의 마지막날이 된다.
eDay.add(Calender.DATE, -1);
- int get(int field) ➠ 원하는 field의 값을 얻어온다
Calender today = Calendar.getInstance();
today.get(Calendar.YEAR);
today.get(Calendar.MONTH); // 얻어오는 값의 범위는2가 아닌 0~11 !
today.get(Calendar.DAY_OF_MONTH); // DATE와 같은 의미
today.get(Calendar.DAY_OF_WEEK); // 1:일요일, 2:월요일, ... 7:토요일
today.get(Calendar.AM_PM) // 0:AM, 1:PM
today.get(Calendar.HOUR);
🔻 get 메서드의 매개변수로 사용되는 int 값 ➠ Calendar에 정의된 static 상수
- getTimeInMillis()
➠ 1/1000초 단위로 값 반환 / 시간 · 날짜간의 차이를 구하는 경우
ex) 초단위 ~ 1000으로 나누기
일단위 ~ 24(시간) * 60(분) * 60(초)로 나누기 - boolean after(Object when) & boolean before(Object when)
➠ 시간상의 전후를 알고 싶은 경우, 두 날짜간의 차이가 양수인지 음수인지로 판단
- 년 · 월 정도의 계산은, 굳이 Calendar를 사용하지 않고 간단히 처리하기 !
String date1 = "20230326";
String date1 = "20230707";
int month1 = Integer.parseInt(date1.substring(0,4) * 12;
+ Integer.parseInt(date1.substring(4));
int month2 = Integer.parseInt(date2.substring(0,4)) * 12;
+ Integer.parseInt(date1.substring(4));
- 날짜를 계산할 때 윤년 · 윤달 주의
- Calendar의 경우 1970년 1월 1일을 기준으로 계산하기에
이 이전의 날짜에 대해 getTimeInMillis()를 호출하면 음수을 반환
🌒 형식화 클래스 ( Format class )
✔️ DecimalFormat
🔸 숫자를 형식화 하는데 사용되는 class
- 숫자 데이터 ⇄ 다양한 형식의 텍스트 데이터 (정수, 부동소수점, 금액)
기호 | 의미 |
0 | 10진수(값이 없을 때는 0) |
# | 10진수 |
. | 소수점 |
+ / - | 양수/음수부호 |
, | 단위 구분자 |
E | 지수기호 |
; | 패턴구분자 |
% | 퍼센트 |
\u2030 | 퍼밀(퍼센트 x 10) |
\u00A4 | 통화 |
` | escape문자 (특수문자를 표현하는데 사용) |
double number = 1234567.89;
DecimalFormat df = new DecimalFormat("#.#E0");
DecimalFormat df2 = new DecimalFormat("\u00A4 #,###");
String result = df.format(number);
String result2 = df2.format(number);
System.out.println(result);
System.out.println(result2);
1.2E6
1,234,568
- 패턴을 이용하여 숫자 변환
DecimalFormat df = new Decimalformat("#,###.##");
DecimalFormat df2 = new DecimalFormat("#.###E0");
try {
Number num = df.parse("1,234,567.89");
System.out.print("1,234,567.89" + " → ");
double d = num.doubleValue();
System.out.print(d + " → ");
System.out.println(df2.format(num));
} catch(Exception e) {}
1,234,567.89 → 1234567.89 → 1.235E6
🔻 Number class는 숫자를 저장하는 Wrapper class의 super class !
🔻 Integer.parseInt 메서드는 콤마(,)가 포함된 문자열을 숫자로 변환 불가능
✔️ SimpleDateFormat
🔸 날짜 데이터를 다양한 형태로 출력하는 class
기호 | 의미 |
G | 연대(BC, AD) |
y | 년도 |
M | 월(1~12 또는 1월~12월) |
w | 년의 몇 번째 주 (1~53) |
W | 월의 몇 번째 주 (1~5) |
D | 년의 몇 번째 일 (1~366) |
d | 월의 몇 번째 일 (1~31) |
F | 월의 몇 번째 요일(1~5) |
E | 요일 |
a | 오전/오후 (AM,PM) |
H | 시간 (0~23) |
k | 시간 (1~21) |
K | 시간 (0~11) |
h | 시간 (1~12) |
m | 분 (0~59) |
s | 초 (0~59) |
S | 천분의 일초 (0~999) |
z | Time zone (General time zone) |
Z | Time zone(RFC 822 time zone) |
` | escape문자 (특수문자를 표현하는데 사용) |
- Date class의 instance만 format 메서드에 사용 가능 !
➠ getTime() 메서드 사용
Calendar cal = Calendar.getInstance();
cal.set(2023, 6, 7);
Date day = cal.getTime(); // Calendar를 Date로 변환
SimpleDateFormat sdf1, sdf2, sdf3;
sdf1 = new SimpleDateFormat("yy-MM-dd E요일"); // 23-07-07 금요일
sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); // 2023-07-07 21:03:37.297
sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss a"); // 2023-07-07 09:03:37 오후
- parse(String source)
➠ 문자열 source ➙ Date class의 instance ( substring 메서드를 이용하는 복잡한 과정 X )
➠ SimpleDateFormat class의 super class인 DateFormat class에 정의되어 있다.
String pattern = "yyyy/MM/dd";
DateFormat df = new SimpleDateFormat(pattern);
Scanner s = new Scanner(System.int);
Date inDate = null;
while(s.hasNextLine()) {
try {
inDate = df.parse(s.nextLine());
break;
} catch(Exception e) {
System.out.println("날짜를 " + patter + "의 형태로 다시 입력해주세요.");
}
}
Calendar cal = Calendar.getInstance();
cal.setTime(inDate); // Date class의 instance를 Calendar class의 instance로 변환
Calendar today = Calendar.getInstance();
// day : 입력한 날짜와 현재 날짜와의 시간 차이
long day = (cal.getTimeInMillis() - today.getTimeInMillis()) / (60*60*1000);
🔻지정된 format과 입력된 format이 일치하지 않는 경우 예외가 발생해 적절한 예외처리 필요 !
✔️ ChoiceFormat
🔸 특정 범위에 속하는 값을 문자열로 변환해주는 class
- 연속적 또는 불연속적인 범위의 값들을 처리할 때 if문이나 switch문이 적절하지 못한 경우 유용하다 !
➠ 간단하고 직관적인 코드 작성 가능
double[] limits = {60, 70. 80, 90}; // 오름차순으로 적어야 한다.
// limits, grades간의 순서와 개수를 맞춰야 한다
// 그렇지 않을 경우에 IllegalArgumentException 발생
String[] grades = {"D", "C", "B", "A"};
ChoiceFormat form = new ChoiceFormat(limits, grades);
String pattern = "60#D|70#C|80<B|90#A";
ChoiceFormat form2 = new ChoiceFormat(pattern);
int[] scores = { 100, 95, 80, 60, 52, 70};
for(int i = 0; i < scores.length; i++) {
System.out.println(scores[i] + " : " + form.format(scores[i]));
}
System.out.println();
for(int i = 0; i < scores.length; i++) {
System.out.println(scores[i] + " : " + form2.format(scores[i]));
}
100 : A
95 : A
80 : B
60 : D
52 : D
70 : C
100 : A
95 : A
80 : C
60 : D
52 : D
70 : C
🔻 구분자 ' # ' ➠ 경계값이 범위에 포함 O / ' < ' ➠ 경게값이 범위에 포함 X
' limit#value ' 형태로 사용
✔️ MessageFormat
🔸 데이터를 정해진 양식에 맞게 출력해주는 class
ex) 같은 안내문 양식에 받는 사람의 이름 같은 데이터만 달라지도록 출력하는 경우
, 하나의 데이터를 다양한 양식으로 출력하는 경우
- 특정 양식에 따라 데이터 출력
String msg = "Name: {0} \nPhone: {1} \nAge:{2} \nBirthday:{3}";
Object[] arguments = { "신민규", "010-1234-5678", "23", "10-16" };
String result = MessageFormat.format(msg, arguments);
System.out.println(result);
Name: 신민규
Phone: 010-1234-5678
Age: 23
Birthday: 10-16
🔻 ' {숫자} ' : 데이터가 출력되는 위치
🔻 배열처럼 index가 0부터 시작하며, n차원 배열을 이용하여 하나 이상의 데이터 집합도 출력 가능
➠ 다수의 데이터를 Database에 저장하기 위한 Insert문으로 변환하는 경우에 유용
🔻 양식에 들어갈 데이터는 객체배열인 arguments에 지정되어 있다.
➠ 객체배열이기 때문에 String 이외에도 다른 객체들이 지정될 수 있다.
- parse(String source)
➠ 출력된 데이터로부터 필요한 데이터만 추출
String[] data = {
"INSERT INTO CUST_INFO VALUES ('신민규', '010-1234-1234', 23, '10-16');,
"INSERT INTO CUST_INFO VALUES ('빵빵이', '010-5678-5678', 1, '07-26');
};
String pattern = "INSERT INTO CUST_INFO VALUES ({0}, {1}, {2}, {3});";
MessageFormat mf = new MessageFormat(pattern);
for(int i = 0; i < data.length; i++) {
Object[] objs = mf.parse(data[i]);
for(int j = 0; j < objs.length; j++) {
System.out.print(objs[j] + ",");
}
System.out.println();
}
'신민규', '010-1234-1234', 23, '10-16',
'빵빵이', '010-5678-5678', 1, '07-26',
🔻 이렇게 한다면, 데이터가 바뀔 때마다 매번 배열을 변경하고 컴파일 해줘야하므로 불편하다.
➠ Scanner를 통해 파일로부터 데이터를 라인별로 읽어서 처리 !
🌓 java.time package
🔸 Date와 Calendar가 가지고 있던 단점들을 해소하기 위해 ' java.time package '가 추가 ( ~ JDK1.8 )
package | 설명 |
java.time | 날짜와 시간을 다루는데 필요한 핵심 class 제공 |
java.time.chrono | 표준(ISO)이 아닌 달력 시스템을 위한 class 제공 |
java.time.format | 날짜와 시간을 parsing하고, format화하기 위한 class 제공 |
java.time.temporal | 날짜와 시간의 field와 unit를 위한 class 제공 |
java.time.zone |
시간대(time-zone)와 관련된 class 제공 |
🔸 이 package에 속하는 class들의 가장 큰 특징은 String class처럼 '불변(immutable)'이다 !
- Multi thread 환경에서 안전
➠ Multi thread 환경에선 동시에 여러 thread가 같은 객체에 접근할 수 있기 때문에,
변경 가능한 객체는 데이터가 잘못될 가능성 O
➠ 이를 thread에 안전(thread - safe)하지 않다고 한다. - 기존에 작성된 프로그램과의 호환성 때문에 Date · Calendar class는 여전히 사용 !
✔️ java.time package의 핵심 class
🔹 time package의 class
- LocalDate ➠ 날짜
LocalTime ➠ 시간
LocalDateTime ➠ 날짜 + 시간
ZonedDateTime ➠ 날짜 + 시간 + 시간대(time-zone) - Instant
➠ Date와 유사한 class
➠ 타임스탬프(time-stamp : 날짜와 시간을 초단위로 표현한 값)으로 표현
➟ 날짜와 시간을 하나의 정수로 표현할 수 있어 날짜와 시간의 차이 계산이나 순서를 비교하는데 유리
➟ DataBase에 많이 사용된다.
🔹 Period & Duration
- Period ➠ 날짜의 차이
- Duration ➠ 시간의 차이
🔹 객체 생성 - now( ), of( )
- now()
LocalDate date = LocalDate.now(); // 2023-07-08
LocalTime time = LocalTime.now(); // 13:07:34.487
LocalDateTime dateTime = LocalDateTime.now(); // 2023-07-08T13:07:34.487
ZonedDateTime dateTimeInKr = ZoneDateTime.now(); // 2023-07-08T13:07:34.487+09:00[Asia/Seoul]
🔻 현재 날짜와 시간을 저장하는 객체를 생성
- of()
LocalDate date = LocalDate.of(); // 2023년 7월 8일
LocalTime time = LocalTime.of(); // 13시 12분 34초
LocalDateTime dateTime = LocalDateTime.of(date, time);
ZonedDateTime dateTimeInKr = ZoneDateTime.of(dateTime, ZoneId.of("Asia/Seoul"));
🔻 해당 field의 값을 순서대로 직접 지정 가능
🔹 Temporal & TemporalAmount
- Temporal, TemporalAccessor, TemporalAdjuster를 구현한 class
➠ LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, etc. - TemporalAmount를 구현한 class
➠ Period, Duration - 'temporal', 'chrono' ➠ 시간(년월시분초) ⟺ 시간(시분초)
🔹 TemporalUnit & TemporalField
- TemporalUnit
➠ 날짜와 시간의 단위을 정의해놓은 interface
➠ 이 interface를 구현한 것이 열거형 ChronoUnit - TemporalField
➠ 년, 월, 일 등 날짜와 시간의 필드를 정의해놓은 interface
➠ 이 interface를 구현한 것이 열거형 ChronoField - 열거형(enumeration)
➠ 서로 관련된 상수를 묶어서 정의해 놓은 것 - 특정 field의 값 가져오기 ➠ get() · getXXX()
➠ int get(TemporalField field)
LocalTime now = LocalTime.now(); // 현재시간
int minute = now.getMinute(); // 현재 시간에서 분(minute)만 뽑아낸다.
int minute = now.get(ChronoField.MINUTE_OF_HOUR); // 위의 문장과 동일
- plus() & minus()
➠ LocalDate plus(long amountToAdd)
LocalDate today = LocalDate.now(); // 오늘
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); // 오늘에 1일을 더한다.
LocalDate tomorrow = today.plusDays(1); // 위의 문장과 동일
- boolean isSupported(TemporalUnit unit)
· boolean isSupported(TemporalField field)➠ 특정 TemporalUnit나 TemporalUnit을 사용할 수 있는지 확인하는 메서드
➠ 날짜와 시간을 표현하는 데 사용하는 모든 class에 포함되어 있다.
✔️ LocalDate & LocalTime
🔹 객체 생성 - now( ), of( )
- 둘다 static 메서드
LocalDate today = LocalDate.now(); // 오늘의 날짜
LocalTime now = LocalTime.now(); // 현재 시간
LocalDate birthDate = LocalDate.of(2001, 10, 16); // 1999년 12월 31일
LocalTime birthTime = LocalTime.of(23, 59, 59); // 23시 59분 59초
static LocalDate of(int year, Month month, int dayOfMonth)
static LocalDate of(int year, int month, int dayOfMonth)
static LocalDate of(int hour, int min)
static LocalDate of(int hour, int min, int sec)
static LocalDate of(int hour, int min, int sec, int nanoOfSecond)
- ofYearDay(), ofSecondDay() ➠ 일, 초단위로 지정
parse() ➠ 문자열을 날짜와 시간으로 변환
LocalDate birthDate = LocalDate.ofYearDay(1999, 365); // 1999년 12월 31일
LocalTime birthTime = LocalTime.ofSecondDay(86399); // 23시 59분 59초
LocalDate birthDate = LocalDate.parse("1999-12-31"); // 1999년 12월 31일
LocalTime birthTime = LocalTime.parse("23:59:59"); // 23시 59분 59초
🔹 특정 필드(field)의 값 가져오기 - get( ), getXXX( )
- 월(month)의 범위 ➠ 1~12 / 요일 ➠ 월 : 1, 화 : 2, ... , 일 : 7
⟺ Calendar - 대부분의 필드는 int 타입의 범위에 속하지만, int 타입의 범위를 넘는 필드 존재
➠ 그럴 땐 get() 대신 getLong() 사용
- 해당 class가 지원하지 않는 field를 사용하면, UnsupportedTemporalTypeException 발생
LocalDate today = LocalDate.now(); // 오늘의 날짜
System.out.println(today.get(ChronoField.MINUTE_OF_HOUR)); // 예외 발생
- 특정 field가 가질 수 있는 값의 범위를 알 수 있는 방법
System.out.println(ChronoField.CLOCK_HOUR_OF_DAY.range()); // 1 - 24
System.out.println(ChronoField.HOUR_OF_DAY.range()); // 0 - 23
🔹 필드(field)의 값 변경 - with( ), plus( ), minus( )
- withXXX() · with()
LocalDate withYear(int year)
...
Local Time with Hour(int hour)
...
LocalDate with(TemporalField field, long newValue)
🔻 field를 변경하는 메서드들은 항상 새로운 객체를 생성하여 반환
➠ 대입연산자(=) 사용 !
date = date.withYear(2023); // 년도를 2023년으로 변경
time = time.withHour(13); // 시간을 12시로 변경
- plus() · plusXXX() & minus() · minusXXX()
LocalTime plus(TemporalAmount amountToAdd)
LocalTime plus(long amountTodAdd, TemporalUnit unit)
LocalDate plus(TemporalAmount amountToAdd)
LocalDate plus(long amountTodAdd, TemporalUnit unit)
LocalDate plusYears(long yearsToAdd)
...
LocalTime plusHours(long hoursToAdd)
...
- truncatedTo()
LocalTime time = LocalTime.of(12, 34, 56); // 12:34:56
time = time.truncatedTo(ChronoUnit.HOURS); // 시(hour)보다 작은 단위를 0으로
System.out.println(time); // 12:00
🔻LocalTime class에만 정의되어 있으며, 매개변수로는 시간과 관련된 field만 사용 가능
🔹 날짜와 시간의 비교 - isAfter( ), isBefore( ), isEqual( )
- LocalDate & LocalTime class에도 compareTo()가 Overriding되어 있어 compareTo()로도 비교 가능
int result = date1.compareTod(date2); // 같으면 0, date1이 이전이면 -1, 이후면 1
- 이보다 편리한 메서드
➠ isAfter() · isBefore() · is Equal()
boolean isAfter(ChronoLocalDate other)
boolean isBefore(ChronoLocalDate other)
boolean isEqual(ChronoLocalDate other) // LocalDate에만 정의
🔻 대부분의 경우 equals()와 isEquals()의 결과는 같지만,
연표(Chronology)가 다를 때 날짜만 비교해야 할 경우 isEquals() 사용 !
✔️ Instant
🔹 Instant class
- 에포크 타임(EPOCH TIME, 1970-01-01 00:00:00 UTC)부터 경과된 시간을 나노초 단위로 표현
➠ 단일 진법으로만 다루기 때문에 계산에 편리 - 객체 생성
➠ now() & ofEpochSecond() - 값 가져오기
long epochSec = now.getEpochSecond();
int nano = now.getNano();
🔻 Instant class는 시간을 초 단위와 나노초 단위로 나누어 저장
➠ Oracle DataBase의 타임스탬프(timestamp)처럼 밀리초 단위의 EPOCHTIME을 필요로 하는 경우
➠ long toEpochMilli() 메서드 사용
- 항상 기준은 UTC(+00:00) ( ' Coordinated Universal Time ' ~ ' 세계 협정시 ' )
➠ LocalTime과 차이 O
➠ 시간대를 고려해야하는 경우 OffsetDateTime을 사용하는 것이 나을 수 있다 !
ex) 시간대가 '+09:00'인 한국의 경우 Instant와 LocalTime 간에는 9시간의 차이 발생
🔹 Instant ⇄ Date
- 기존의 java.util.Date를 대체하기 위해 추가된 것이 Instatnt이며,
JDK1.8부터 Date class에 Instant로 변환할 수 있는 메서드 추가
static Date from(Instant instant) // Instant ➙ Date
Instant toInstant() // Date ➙ Instant
✔️ LocalDateTime & ZonedDateTime
🔹 LocalDateTime class
➠ LocalDate + LocalTime
- LocalDate & LocalTime ➙ LocalDateTime
➠ atXXX()
LocalDate date = LocalDate.of(2023, 10, 16);
LocalTime time = LocalTime.of(23, 10, 16);
LocalDateTime dt = LocalDateTime.of(date, time);
LocalDateTime dt2 = date.atTime(time);
LocalDateTime dt3 = time.atDate(date);
LocalDateTime dt4 = date.atStartOfDay(); // dt4 = date.atTime(0,0,0);
- now() ➠ 객체 생성
of ➠ 객체 생성, 날짜와 시간을 직접 지정
// 2023년 10월 16일 10시 16분 33초
LocalDateTime dateTime = LocalDateTime.of(2023, 10, 16, 10, 16, 33);
LocalDateTime today = LocalDateTime.now();
- LocalDateTime ➙ LocalDate & LocalTime
➠ toXXX()
LocalDateTime dt = LocalDateTime.of(2023, 10, 16, 10, 16, 33);
LocalDate date = dt.toLocalDate();
LocalTime time = dt.toLocalTime();
🔹 ZonedDateTime class
➠ LocalDateTime + 시간대(time-zone)
- ZoneId ➠ 일광 절약시간(DST, Daylight Saving Time)을 자동적으로 처리해주는 class
- LocalTime & DateTime & LocalDateTime ➙ ZonedDateTime
➠ atZone()
ZoneId zid = ZoneId.of("Asia/Seoul");
ZonedDateTime zdt = dateTime.atZone(zid);
System.out.println(zdt); // 2023-07-08T15:33:33.456+09:00[Asia/Seoul]
ZonedDateTime zdt = ZonedDateTime.of(date, time, zid);
🔻 LocalDate와 LocatTime에 ZoneId를 더하여 ZonedDateTime의 instance 얻기
ZonedDateTime zdt = LocalDate.now().atStartOfDay(zid);
System.out.println(zdt); // 2023-07-08T00:00:00+09:00[Asia/Seoul]
🔻 LocalDate class의 atStartOfDay() 메서드에 매개변수로 ZoneId class의 instance를 지정하여 얻는 방법
➠ 단, 시간이 0시 0분 0초 !
ZoneId nyId = ZoneId.of("America/New_York");
ZonedDateTime nyTime = ZonedDateTime.now().withZoneSameInstant(nyId);
🔻 현재 특정 시간대(time-zone)의 시간을 얻기 위한 방법
🔻 now() 대신 of()를 사용하면 날짜와 시간도 지정 가능
- LocalDateTime처럼 날짜와 시간에 관련된 다른 class로 변환하는 메서드 존재
LocalDate toLocalDate()
LocalTime toLocalTime()
LocalDateTime toLocalDateTime()
OffsetDateTime toOffsetDateTime()
long toEpochSecond()
Instant toInstant()
- GregorianCalendar와 가장 유사한 class
// ZonedDateTime ➙ GregorianCalendar
GregorianCalendar from(ZonedDateTime zdt)
// GregorianCalendar ➙ ZonedDateTime
ZonedDateTime toZonedDateTime();
🔻 변환 방법
🔹 ZoneOffSet class
- UTC와 얼마만큼의 시간차이가 있는지 표현하는 class
ex) 서울은 '+0', 즉 UTC보다 9시간(32400초)이 빠르다
ZoneOffset krOffset = ZonedDateTime.now().getOffset();
// ZoneOffset krOffset = ZoneOffset.of("+9"); // ±h, ±hh, ±hhmm, ±hh:mm
int krOffsetInSec = krOffset.get(ChronoField.OFFSET_SECONDS); // 32400초
🔹 OffsetDateTime class
- ZoneOffset을 사용하여 구역을 표현하는 class
~ ZoneId로 구역을 표현하는 class가 ZonedDateTime
➠ 오직 시간대를 시간의 차이로만 구분 (ZoneId와 달리 'DST' 같은 시간대와 관련된 규칙 포함 X) - 같은 지역 내의 컴퓨터간에 데이터를 주고받은 경우 LocalDateTime으로 충분 !
➠ 서로 다른 시간대에 존재하는 컴퓨터간의 통신에는 OffsetDateTime 필요 ! - 객체 생성
OffsetDateTime odt = OffsetDateTime.of(date, time, zoneOffSet);
// ZonedDateTime ➙ OffsetDateTime
OffsetDateTime odt = zdt.toOffsetDateTime();
🔻 LocalDate & LocatTime에 ZoneOffset 더하기
🔻 ZonedDateTime에 toOffsetDateTime() 메서드 호출
✔️ TemporalAdjusters
🔹 TemporalAdjusters class
- 자주 쓰일만한 날짜 계산들을 대신 해주는 메서드를 정의해놓은 class
LocalDate today = LocalDate.now();
LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
- TemporalAdjusters class에 정의된 메서드 말고도 자주 사용되는 날짜계산이 있다면
LocatDate class의 with() 메서드를 이용하여 직접 만들자
LocalDate with(TemporalAdjuster adjuster)
🔻 TemporalAdjuster interface를 구현한 class의 객체를 매개변수로 제공
🔻 with()는 LocalDate class 말고도 대부분의 날짜와 시간에 관련된 class에 포함되어 있다.
🔹 TemporalAdjuster interface
- 추상 메서드 하나만 정의되어 있어 이 adjustInto() 메서드만 구현하면 된다.
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
🔻 실제로 구현해야 하는 추상메서드는 adjustInto()지만,
TemporalAdjuster와 같이 사용해야 하는 메서드는 with()
➠ 두 메서드 모두 상관 없지만, adjustInto()는 내부적으로 사용할 의도로 작성된 것 !
class DayAfterTomorrow implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
return temporal.plus(2, ChronoUnit.Days); // 2일을 더한다.
}
}
🔻 날짜와 시간에 관련된 대부분의 class는 Temporal interface가 구현되어 있다.
➠ 따라서, adjustInto()의 매개변수가 될 수 있다.
✔️ Period & Duration
🔹 객체 생성 - between()
- 날짜의 차이 - Period class
LocalDate date1 = LocalDate.of(2023, 7, 8);
LocalDate date2 = LocalDate.of(2023, 10, 16);
Period pe = Period.between(date1, date2);
🔻 date1이 date2보다 날짜 상으로 이전이면 양수, 이후면 음수로 pe에 저장
- 시간의 차이 - Duration class
LocalTime time1 = LocalTime.of(00, 00, 00);
LocalTime time2 = LocalTime.of(16, 51, 45);
Duration du = Duration.between(time1, time2);
🔻 Period class와 유사
🔹 특정 field의 값 얻기 - get()
long year = pe.get(ChronoUnit.YEARS); // int getYears()
long month = pe.get(ChronoUnit.MONTHS); // int getMonths()
long day = pe.get(ChronoUnit.DAYS); // int getDays()
long sec = du.get(ChronoUnit.SECONDS); // long getSeconds()
int nano = du.get(ChronoUnit.NANOS); // int getNano()
🔻Duration class에는 getHours(), getMinites() 같은 메서드 X
➠ Duration class에는 Chrono.SECONDS와 Chrono.NANOS밖에 사용할 수 없다.
➠ Duration을 LocalTime으로 변환한 다음에, LocalTime class의 get 메서드를 사용하자 !
🔹 between( ) & until( )
- between() ➠ static 메서드 / until ➠ instance 메서드 (두 메서드는 거의 같은 일을 한다.)
// Period pe = Period.between(today, myBirthDay);
Period pe = today.until(myBirthDay);
long dday = today.until(myBirthDay, ChronoUnit.DAYS);
🔻 Period는 년원일을 분리하여 저장하기 때문에, D-day를 구하려는 경우 두 매개변수를 받는 until()을 사용 !
- 날짜가 아닌 시간에도 until()을 사용할 수 있지만, until()은 Duration 타입을 반환하지 못한다
🔹 of( ) & with( )
- Period
➠ of(), ofYears(), ofMonths(), ofWeeks(), ofDays(), etc. - Duration
➠ of(), ofDays(), ofHours(), ofMinutes(), ofSeconds(), etc.
Period pe = Period.of(1, 12, 31); // 1년 12개월 31일
Duration du = Duration.of(60, ChronoUnit.SECONDS); // 60초
// Duration du = Duration.ofSeconds(60); // 위 코드와 동일
- 특정 field의 값 변경 - with()
pe = pe.withYears(2); // 1년에서 2년으로 변경. withMonths(), withDays()
du = du.withSeconds(120); // 60초에서 120초로 변경. withNanos()
🔹 사칙연산, 비교연산, 기타 메서드
- plus() & minus()
- multipliedBy()
pe = pe.minusYears(1).multipliedBy(2); // 1년을 빼고, 2배를 곱한다.
- dividedBy()
➠ 날짜의 기간을 표현하는 Period엔 나눗셈 메서드 X
du = du.plusHours(1).dividedBy(60); // 1시간을 더하고 60으로 나눈다.
- isNegative() ➠ 음수인지 확인
isZero() ➠ 0인지 확인
boolean sameDate = Period.between(date1, date2).isZero();
boolean isBefore = Duration.between(time1, time2).isNegative();
🔻 날짜 또는 시간을 비교할 때, 어느 쪽이 앞인지 또는 같은지 알아내는데 유용
- negate() ➠ 부호를 반대로 변경
abs() ➠ 부호를 제거
du = du.abs();
if(pe.isNegative())
pe = pe.negated();
🔻 Period class에는 abs() 메서드 X
- Period normalized()
pe = Period.of(1, 13, 32).normalized(); // 1년 13개월 32일 ➙ 2년 1개월 32일
🔻 일(day)의 길이는 일정하지 않아 그대로 놔둔다.
🔹 다른 단위로 변환 - toXXX()
- Period class
- long toTotalMonths()
➠ 년원일을 월단위로 변환해서 반환 (일 단위 무시)
- long toTotalMonths()
- Duration class
- long toDays()
toHours()
toMinutes()
toMillis()
toNanos()
- long toDays()
- LocalDate class의 toEpochDay() 메서드를 이용하면
Epoch Day 이후의 날짜들은 Period class를 이용하지 않고도 두 날짜간의 일수 계산 가능
LocalDate date1 = LocalDate.of(2023, 7, 8);
LocalDate date2 = LocalDate.of(2023, 10, 16);
long period = date2.toEpochDay() - date1.toEpochDay();
🔻 LocalTime에도 ' int toSecondOfDay() ', ' long toNanoOfDay() '와 같은 메서드가 있어
Duration class를 사용하지 않고도 뺄셈으로 시간차이 계산 가능
✔️ Parsing & Format
🔸날짜와 시간을 원하는 형식으로 출력 & 해석(parsing)하는 방법
🔹 DataTimeFormatter
- 형식화(formatting)와 관련된 class들은 java.time.format package에 들어 있는데, 이 중 하나의 class
- 자주 쓰이는 형식들이 정의되어 있고, 추가 정의 가능
LocalDate date = LocalDate.of(2023, 7, 8);
String yyyymmdd = DataTimeFormatter.ISO_LOCAL_DATE.format(date); // "2023-07-08"
String yyyymmdd = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // "2023-07-08"
🔻 format() 메서드는 LocalDate나 LocalTime같은 class에도 정의되어 있어 편한 쪽 선택
🔹 Locale에 종속된 형식화(formatting)
- DateTimeFormatter class의 static 메서드
ofLocalizedDate(), ofLocalizedTime(), ofLocalizedDateTime() ➠ Locale에 종속적인 formatter 생성
// 열거형 FormatStyle에 따라 저장된다.
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
String shortFormat = formatter.format(LocalDate.now()); // 날짜 ➙ 23.07.08 / 시간 ➙ 오후 5:43
🔹 출력형식 직접 정의 - ofPattern( )
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
🔹 문자열을 날짜와 시간으로 parsing
- parse()
➠ 날짜와 시간을 표현하는데 사용되는 class에는 거의 다 포함되어 있다.
static LocalDateTime parse(CharSequence text)
static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter)
🔻 오버로딩된 메서드 중 자주 쓰이는 메서드
- DateTimeFormatter에 상수로 정의된 형식을 사용하여 parsing
LocalDate date = LocalDate.parse("2023-07-08", DateTimeFormatter.ISO_LOCAL_DATE);
- 자주 사용되는 기본적인 형식의 문자열은 위처럼 ' ISO_LOCAL_DATE '와 같은
형식화 상수를 사용하지 않고 parsing 가능
LocalDate newDate = LocalDate.parse("2023-07-08");
LocalTime newTime = LocalTime.parse("17:53:50");
LocalDateTime newDateTime = LocalDateTime.parse("2023-07-08T17:53:50");
- ofPattern()을 이용한 parsing
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime endOfYear = LocalDateTime.parse("2023-07-08 17:53:50", pattern);
// "2023-07-08T17:53:50"